summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-04-23 18:00:21 +0200
committerMichael Biebl <biebl@debian.org>2018-04-23 18:00:21 +0200
commitf60117b41d5433be1b4a96d82cd11d0c3dce9b63 (patch)
tree2dd55c4ab0fdcbe9cddb26adb4a554b1a45c73c8 /src
parent7e9ff09fcb2366b383b7ebbec80d2f6fff117290 (diff)
New upstream version 1.11.3 upstream/1.11.3
Diffstat (limited to 'src')
-rw-r--r--src/NetworkManagerUtils.c185
-rw-r--r--src/NetworkManagerUtils.h21
-rw-r--r--src/devices/adsl/meson.build34
-rw-r--r--src/devices/adsl/nm-device-adsl.c46
-rw-r--r--src/devices/bluetooth/meson.build45
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c4
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.c1
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c2
-rw-r--r--src/devices/bluetooth/nm-device-bt.c77
-rw-r--r--src/devices/meson.build22
-rw-r--r--src/devices/nm-acd-manager.c489
-rw-r--r--src/devices/nm-acd-manager.h43
-rw-r--r--src/devices/nm-arping-manager.c474
-rw-r--r--src/devices/nm-arping-manager.h43
-rw-r--r--src/devices/nm-device-bond.c26
-rw-r--r--src/devices/nm-device-bridge.c26
-rw-r--r--src/devices/nm-device-dummy.c24
-rw-r--r--src/devices/nm-device-ethernet.c90
-rw-r--r--src/devices/nm-device-ethernet.h18
-rw-r--r--src/devices/nm-device-factory.c69
-rw-r--r--src/devices/nm-device-generic.c23
-rw-r--r--src/devices/nm-device-infiniband.c27
-rw-r--r--src/devices/nm-device-ip-tunnel.c78
-rw-r--r--src/devices/nm-device-ip-tunnel.h4
-rw-r--r--src/devices/nm-device-macsec.c59
-rw-r--r--src/devices/nm-device-macsec.h3
-rw-r--r--src/devices/nm-device-macvlan.c42
-rw-r--r--src/devices/nm-device-macvlan.h3
-rw-r--r--src/devices/nm-device-ppp.c93
-rw-r--r--src/devices/nm-device-private.h6
-rw-r--r--src/devices/nm-device-tun.c355
-rw-r--r--src/devices/nm-device-veth.c41
-rw-r--r--src/devices/nm-device-vlan.c29
-rw-r--r--src/devices/nm-device-vlan.h4
-rw-r--r--src/devices/nm-device-vxlan.c110
-rw-r--r--src/devices/nm-device-vxlan.h3
-rw-r--r--src/devices/nm-device.c3950
-rw-r--r--src/devices/nm-device.h42
-rw-r--r--src/devices/nm-lldp-listener.c45
-rw-r--r--src/devices/ovs/meson.build38
-rw-r--r--src/devices/ovs/nm-device-ovs-bridge.c21
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c19
-rw-r--r--src/devices/ovs/nm-device-ovs-port.c21
-rw-r--r--src/devices/ovs/nm-ovsdb.c31
-rw-r--r--src/devices/team/meson.build35
-rw-r--r--src/devices/team/nm-device-team.c29
-rw-r--r--src/devices/tests/meson.build18
-rw-r--r--src/devices/tests/test-acd.c (renamed from src/devices/tests/test-arping.c)97
-rw-r--r--src/devices/tests/test-lldp.c52
-rw-r--r--src/devices/wifi/meson.build51
-rw-r--r--src/devices/wifi/nm-device-iwd.c1921
-rw-r--r--src/devices/wifi/nm-device-iwd.h62
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c38
-rw-r--r--src/devices/wifi/nm-device-wifi.c381
-rw-r--r--src/devices/wifi/nm-device-wifi.h12
-rw-r--r--src/devices/wifi/nm-iwd-manager.c694
-rw-r--r--src/devices/wifi/nm-iwd-manager.h65
-rw-r--r--src/devices/wifi/nm-wifi-ap.c205
-rw-r--r--src/devices/wifi/nm-wifi-ap.h21
-rw-r--r--src/devices/wifi/nm-wifi-common.c205
-rw-r--r--src/devices/wifi/nm-wifi-common.h37
-rw-r--r--src/devices/wifi/nm-wifi-factory.c29
-rw-r--r--src/devices/wifi/nm-wifi-utils.c32
-rw-r--r--src/devices/wifi/nm-wifi-utils.h2
-rw-r--r--src/devices/wifi/tests/meson.build13
-rw-r--r--src/devices/wwan/libnm-wwan.ver2
-rw-r--r--src/devices/wwan/meson.build75
-rw-r--r--src/devices/wwan/nm-device-modem.c96
-rw-r--r--src/devices/wwan/nm-modem-broadband.c65
-rw-r--r--src/devices/wwan/nm-modem-ofono.c26
-rw-r--r--src/devices/wwan/nm-modem.c294
-rw-r--r--src/devices/wwan/nm-modem.h19
-rw-r--r--src/devices/wwan/nm-wwan-factory.c8
-rw-r--r--src/dhcp/meson.build22
-rw-r--r--src/dhcp/nm-dhcp-client.c119
-rw-r--r--src/dhcp/nm-dhcp-client.h49
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c330
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h15
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c59
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c14
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c17
-rw-r--r--src/dhcp/nm-dhcp-helper.c2
-rw-r--r--src/dhcp/nm-dhcp-listener.c84
-rw-r--r--src/dhcp/nm-dhcp-manager.c118
-rw-r--r--src/dhcp/nm-dhcp-manager.h15
-rw-r--r--src/dhcp/nm-dhcp-systemd.c115
-rw-r--r--src/dhcp/nm-dhcp-utils.c8
-rw-r--r--src/dhcp/nm-dhcp-utils.h2
-rw-r--r--src/dhcp/tests/leases/basic.leases31
-rw-r--r--src/dhcp/tests/leases/malformed1.leases15
-rw-r--r--src/dhcp/tests/leases/malformed2.leases15
-rw-r--r--src/dhcp/tests/leases/malformed3.leases15
-rw-r--r--src/dhcp/tests/meson.build19
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c318
-rw-r--r--src/dhcp/tests/test-dhcp-utils.c21
-rw-r--r--src/dns/nm-dns-dnsmasq.c344
-rw-r--r--src/dns/nm-dns-manager.c819
-rw-r--r--src/dns/nm-dns-manager.h33
-rw-r--r--src/dns/nm-dns-plugin.c4
-rw-r--r--src/dns/nm-dns-plugin.h4
-rw-r--r--src/dns/nm-dns-systemd-resolved.c289
-rw-r--r--src/dns/nm-dns-unbound.c2
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.c2
-rw-r--r--src/dnsmasq/tests/meson.build14
-rw-r--r--src/main.c47
-rw-r--r--src/meson.build319
-rw-r--r--src/ndisc/nm-lndp-ndisc.c52
-rw-r--r--src/ndisc/nm-ndisc.c55
-rw-r--r--src/ndisc/nm-ndisc.h33
-rw-r--r--src/ndisc/tests/meson.build23
-rw-r--r--src/nm-act-request.c13
-rw-r--r--src/nm-act-request.h1
-rw-r--r--src/nm-active-connection.c409
-rw-r--r--src/nm-active-connection.h22
-rw-r--r--src/nm-audit-manager.c2
-rw-r--r--src/nm-audit-manager.h1
-rw-r--r--src/nm-auth-manager.c623
-rw-r--r--src/nm-auth-manager.h62
-rw-r--r--src/nm-auth-subject.c39
-rw-r--r--src/nm-auth-subject.h4
-rw-r--r--src/nm-auth-utils.c489
-rw-r--r--src/nm-auth-utils.h25
-rw-r--r--src/nm-bus-manager.c1024
-rw-r--r--src/nm-bus-manager.h93
-rw-r--r--src/nm-checkpoint-manager.c326
-rw-r--r--src/nm-checkpoint-manager.h24
-rw-r--r--src/nm-checkpoint.c292
-rw-r--r--src/nm-checkpoint.h29
-rw-r--r--src/nm-config-data.c307
-rw-r--r--src/nm-config-data.h24
-rw-r--r--src/nm-config.c20
-rw-r--r--src/nm-config.h3
-rw-r--r--src/nm-connectivity.c600
-rw-r--r--src/nm-connectivity.h29
-rw-r--r--src/nm-core-utils.c278
-rw-r--r--src/nm-core-utils.h36
-rw-r--r--src/nm-dbus-manager.c1626
-rw-r--r--src/nm-dbus-manager.h107
-rw-r--r--src/nm-dbus-object.c320
-rw-r--r--src/nm-dbus-object.h202
-rw-r--r--src/nm-dbus-utils.c312
-rw-r--r--src/nm-dbus-utils.h212
-rw-r--r--src/nm-dhcp4-config.c34
-rw-r--r--src/nm-dhcp6-config.c34
-rw-r--r--src/nm-dispatcher.c13
-rw-r--r--src/nm-exported-object.c1055
-rw-r--r--src/nm-exported-object.h85
-rw-r--r--src/nm-firewall-manager.c2
-rw-r--r--src/nm-iface-helper.c115
-rw-r--r--src/nm-ip4-config.c212
-rw-r--r--src/nm-ip4-config.h200
-rw-r--r--src/nm-ip6-config.c216
-rw-r--r--src/nm-ip6-config.h10
-rw-r--r--src/nm-logging.c7
-rw-r--r--src/nm-logging.h12
-rw-r--r--src/nm-manager.c2525
-rw-r--r--src/nm-manager.h37
-rw-r--r--src/nm-netns.c4
-rw-r--r--src/nm-pacrunner-manager.c15
-rw-r--r--src/nm-policy.c178
-rw-r--r--src/nm-session-monitor.c7
-rw-r--r--src/nm-test-utils-core.h32
-rw-r--r--src/nm-types.h23
-rw-r--r--src/org.freedesktop.NetworkManager.conf2
-rw-r--r--src/platform/nm-fake-platform.c1
-rw-r--r--src/platform/nm-linux-platform.c779
-rw-r--r--src/platform/nm-netlink.c1491
-rw-r--r--src/platform/nm-netlink.h511
-rw-r--r--src/platform/nm-platform-utils.c3
-rw-r--r--src/platform/nm-platform.c644
-rw-r--r--src/platform/nm-platform.h117
-rw-r--r--src/platform/nmp-netns.c4
-rw-r--r--src/platform/nmp-object.c94
-rw-r--r--src/platform/nmp-object.h34
-rw-r--r--src/platform/tests/meson.build37
-rw-r--r--src/platform/tests/monitor.c3
-rw-r--r--src/platform/tests/test-address.c5
-rw-r--r--src/platform/tests/test-cleanup.c3
-rw-r--r--src/platform/tests/test-common.c116
-rw-r--r--src/platform/tests/test-common.h30
-rw-r--r--src/platform/tests/test-link.c135
-rw-r--r--src/platform/tests/test-nmp-object.c27
-rw-r--r--src/platform/tests/test-route.c3
-rw-r--r--src/platform/wifi/wifi-utils-nl80211.c372
-rw-r--r--src/platform/wifi/wifi-utils-private.h14
-rw-r--r--src/platform/wifi/wifi-utils-wext.c42
-rw-r--r--src/platform/wifi/wifi-utils.c61
-rw-r--r--src/platform/wifi/wifi-utils.h2
-rw-r--r--src/ppp/meson.build41
-rw-r--r--src/ppp/nm-ppp-manager-call.c2
-rw-r--r--src/ppp/nm-ppp-manager.c302
-rw-r--r--src/ppp/nm-ppp-manager.h1
-rw-r--r--src/ppp/nm-pppd-plugin.c32
-rw-r--r--src/settings/nm-agent-manager.c160
-rw-r--r--src/settings/nm-agent-manager.h2
-rw-r--r--src/settings/nm-secret-agent.c140
-rw-r--r--src/settings/nm-settings-connection.c664
-rw-r--r--src/settings/nm-settings-connection.h61
-rw-r--r--src/settings/nm-settings.c769
-rw-r--r--src/settings/nm-settings.h10
-rw-r--r--src/settings/plugins/ibft/meson.build48
-rw-r--r--src/settings/plugins/ibft/tests/meson.build22
-rw-r--r--src/settings/plugins/ibft/tests/test-ibft.c2
-rw-r--r--src/settings/plugins/ifcfg-rh/meson.build77
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-inotify-helper.c2
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c134
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c162
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c66
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c4
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/meson.build22
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc16
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected16
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-12
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-22
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-32
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-42
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c195
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-connection-parser.c2912
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-connection-parser.h55
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-connection.c233
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-connection.h46
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-net-parser.c734
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-net-parser.h42
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-net-utils.c830
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-net-utils.h71
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-plugin.c524
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-plugin.h38
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-wpa-parser.c584
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-wpa-parser.h41
-rw-r--r--src/settings/plugins/ifnet/tests/net158
-rw-r--r--src/settings/plugins/ifnet/tests/net.all864
-rw-r--r--src/settings/plugins/ifnet/tests/nm-system-settings.conf5
-rw-r--r--src/settings/plugins/ifnet/tests/test-ifnet.c396
-rw-r--r--src/settings/plugins/ifnet/tests/test_ca_cert.pem27
-rw-r--r--src/settings/plugins/ifnet/tests/wpa_supplicant.conf70
-rw-r--r--src/settings/plugins/ifupdown/meson.build53
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c6
-rw-r--r--src/settings/plugins/ifupdown/tests/meson.build17
-rw-r--r--src/settings/plugins/ifupdown/tests/test-ifupdown.c2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c2
-rw-r--r--src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection10
-rw-r--r--src/settings/plugins/keyfile/tests/meson.build21
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile.c88
-rw-r--r--src/settings/plugins/meson.build15
-rw-r--r--src/supplicant/nm-supplicant-config.c71
-rw-r--r--src/supplicant/nm-supplicant-config.h3
-rw-r--r--src/supplicant/nm-supplicant-interface.c42
-rw-r--r--src/supplicant/nm-supplicant-interface.h8
-rw-r--r--src/supplicant/nm-supplicant-manager.c15
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c9
-rw-r--r--src/supplicant/nm-supplicant-types.h6
-rw-r--r--src/supplicant/tests/meson.build14
-rw-r--r--src/supplicant/tests/test-supplicant-config.c196
-rw-r--r--src/systemd/meson.build70
-rw-r--r--src/systemd/sd-adapt/device-nodes.h3
-rw-r--r--src/systemd/sd-adapt/errno-list.h3
-rw-r--r--src/systemd/sd-adapt/locale-util.h3
-rw-r--r--src/systemd/sd-adapt/memfd-util.h3
-rw-r--r--src/systemd/sd-adapt/nm-sd-adapt.h23
-rw-r--r--src/systemd/sd-adapt/procfs-util.h3
-rw-r--r--src/systemd/sd-adapt/terminal-util.h3
-rw-r--r--src/systemd/src/basic/alloc-util.c3
-rw-r--r--src/systemd/src/basic/alloc-util.h16
-rw-r--r--src/systemd/src/basic/async.h3
-rw-r--r--src/systemd/src/basic/escape.c1
-rw-r--r--src/systemd/src/basic/escape.h1
-rw-r--r--src/systemd/src/basic/ether-addr-util.c6
-rw-r--r--src/systemd/src/basic/ether-addr-util.h1
-rw-r--r--src/systemd/src/basic/extract-word.c4
-rw-r--r--src/systemd/src/basic/extract-word.h1
-rw-r--r--src/systemd/src/basic/fd-util.c403
-rw-r--r--src/systemd/src/basic/fd-util.h39
-rw-r--r--src/systemd/src/basic/fileio.c156
-rw-r--r--src/systemd/src/basic/fileio.h15
-rw-r--r--src/systemd/src/basic/fs-util.c471
-rw-r--r--src/systemd/src/basic/fs-util.h28
-rw-r--r--src/systemd/src/basic/hash-funcs.c57
-rw-r--r--src/systemd/src/basic/hash-funcs.h24
-rw-r--r--src/systemd/src/basic/hashmap.c163
-rw-r--r--src/systemd/src/basic/hashmap.h41
-rw-r--r--src/systemd/src/basic/hexdecoct.c214
-rw-r--r--src/systemd/src/basic/hexdecoct.h1
-rw-r--r--src/systemd/src/basic/hostname-util.c103
-rw-r--r--src/systemd/src/basic/hostname-util.h7
-rw-r--r--src/systemd/src/basic/in-addr-util.c3
-rw-r--r--src/systemd/src/basic/in-addr-util.h1
-rw-r--r--src/systemd/src/basic/io-util.c13
-rw-r--r--src/systemd/src/basic/io-util.h1
-rw-r--r--src/systemd/src/basic/list.h4
-rw-r--r--src/systemd/src/basic/log.h83
-rw-r--r--src/systemd/src/basic/macro.h57
-rw-r--r--src/systemd/src/basic/mempool.c1
-rw-r--r--src/systemd/src/basic/mempool.h1
-rw-r--r--src/systemd/src/basic/parse-util.c113
-rw-r--r--src/systemd/src/basic/parse-util.h21
-rw-r--r--src/systemd/src/basic/path-util.c85
-rw-r--r--src/systemd/src/basic/path-util.h15
-rw-r--r--src/systemd/src/basic/prioq.c3
-rw-r--r--src/systemd/src/basic/prioq.h1
-rw-r--r--src/systemd/src/basic/process-util.c504
-rw-r--r--src/systemd/src/basic/process-util.h77
-rw-r--r--src/systemd/src/basic/random-util.c6
-rw-r--r--src/systemd/src/basic/random-util.h1
-rw-r--r--src/systemd/src/basic/refcnt.h1
-rw-r--r--src/systemd/src/basic/set.h13
-rw-r--r--src/systemd/src/basic/signal-util.h18
-rw-r--r--src/systemd/src/basic/socket-util.c163
-rw-r--r--src/systemd/src/basic/socket-util.h19
-rw-r--r--src/systemd/src/basic/sparse-endian.h4
-rw-r--r--src/systemd/src/basic/stdio-util.h7
-rw-r--r--src/systemd/src/basic/string-table.c1
-rw-r--r--src/systemd/src/basic/string-table.h10
-rw-r--r--src/systemd/src/basic/string-util.c152
-rw-r--r--src/systemd/src/basic/string-util.h25
-rw-r--r--src/systemd/src/basic/strv.c62
-rw-r--r--src/systemd/src/basic/strv.h18
-rw-r--r--src/systemd/src/basic/time-util.c31
-rw-r--r--src/systemd/src/basic/time-util.h7
-rw-r--r--src/systemd/src/basic/umask-util.h1
-rw-r--r--src/systemd/src/basic/utf8.c20
-rw-r--r--src/systemd/src/basic/utf8.h3
-rw-r--r--src/systemd/src/basic/util.c382
-rw-r--r--src/systemd/src/basic/util.h25
-rw-r--r--src/systemd/src/libsystemd-network/arp-util.c11
-rw-r--r--src/systemd/src/libsystemd-network/arp-util.h1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.h1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-internal.h1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-lease-internal.h3
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-network.c16
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-option.c11
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-packet.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-protocol.h1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-internal.h67
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-lease-internal.h3
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-network.c5
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-option.c347
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-protocol.h8
-rw-r--r--src/systemd/src/libsystemd-network/lldp-internal.h1
-rw-r--r--src/systemd/src/libsystemd-network/lldp-neighbor.c1
-rw-r--r--src/systemd/src/libsystemd-network/lldp-neighbor.h1
-rw-r--r--src/systemd/src/libsystemd-network/lldp-network.c6
-rw-r--r--src/systemd/src/libsystemd-network/lldp-network.h1
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.c30
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.h4
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c60
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c32
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-client.c292
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-lease.c48
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4acd.c4
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4ll.c2
-rw-r--r--src/systemd/src/libsystemd-network/sd-lldp.c1
-rw-r--r--src/systemd/src/libsystemd/sd-event/sd-event.c56
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.c13
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.h1
-rw-r--r--src/systemd/src/libsystemd/sd-id128/sd-id128.c1
-rw-r--r--src/systemd/src/shared/dns-domain.c32
-rw-r--r--src/systemd/src/shared/dns-domain.h2
-rw-r--r--src/systemd/src/systemd/_sd-common.h1
-rw-r--r--src/systemd/src/systemd/sd-dhcp-client.h6
-rw-r--r--src/systemd/src/systemd/sd-dhcp-lease.h1
-rw-r--r--src/systemd/src/systemd/sd-dhcp6-client.h11
-rw-r--r--src/systemd/src/systemd/sd-dhcp6-lease.h6
-rw-r--r--src/systemd/src/systemd/sd-event.h6
-rw-r--r--src/systemd/src/systemd/sd-id128.h1
-rw-r--r--src/systemd/src/systemd/sd-ipv4acd.h1
-rw-r--r--src/systemd/src/systemd/sd-ipv4ll.h1
-rw-r--r--src/systemd/src/systemd/sd-lldp.h1
-rw-r--r--src/systemd/src/systemd/sd-ndisc.h1
-rw-r--r--src/tests/config/meson.build26
-rw-r--r--src/tests/config/nm-test-device.c2
-rw-r--r--src/tests/config/test-config.c39
-rw-r--r--src/tests/meson.build47
-rw-r--r--src/tests/test-general-with-expect.c126
-rw-r--r--src/tests/test-general.c25
-rw-r--r--src/tests/test-ip6-config.c18
-rw-r--r--src/tests/test-resolvconf-capture.c298
-rw-r--r--src/vpn/nm-vpn-connection.c97
-rw-r--r--src/vpn/nm-vpn-connection.h2
381 files changed, 26435 insertions, 22049 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index 89bd357f..b208ffc6 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -31,7 +31,6 @@
 #include "nm-core-internal.h"
 
 #include "platform/nm-platform.h"
-#include "nm-exported-object.h"
 #include "nm-auth-utils.h"
 
 /*****************************************************************************/
@@ -68,98 +67,93 @@ nm_utils_get_shared_wifi_permission (NMConnection *connection)
 /*****************************************************************************/
 
 static char *
-get_new_connection_name (const GSList *existing,
+get_new_connection_name (NMConnection *const*existing_connections,
                          const char *preferred,
                          const char *fallback_prefix)
 {
-	GSList *names = NULL;
-	const GSList *iter;
-	char *cname = NULL;
-	int i = 0;
-	gboolean preferred_found = FALSE;
+	gs_free const char **existing_names = NULL;
+	guint i, existing_len = 0;
 
 	g_assert (fallback_prefix);
 
-	for (iter = existing; iter; iter = g_slist_next (iter)) {
-		NMConnection *candidate = NM_CONNECTION (iter->data);
-		const char *id;
+	if (existing_connections) {
+		existing_len = NM_PTRARRAY_LEN (existing_connections);
+		existing_names = g_new (const char *, existing_len);
+		for (i = 0; i < existing_len; i++) {
+			NMConnection *candidate;
+			const char *id;
 
-		id = nm_connection_get_id (candidate);
-		g_assert (id);
-		names = g_slist_append (names, (gpointer) id);
+			candidate = existing_connections[i];
+			nm_assert (NM_IS_CONNECTION (candidate));
 
-		if (preferred && !preferred_found && (strcmp (preferred, id) == 0))
-			preferred_found = TRUE;
+			id = nm_connection_get_id (candidate);
+			nm_assert (id);
+
+			existing_names[i] = id;
+
+			if (   preferred
+				&& nm_streq (preferred, id)) {
+				/* the preferred name is already taken. Forget about it. */
+				preferred = NULL;
+			}
+		}
+		nm_assert (!existing_connections[i]);
 	}
 
 	/* Return the preferred name if it was unique */
-	if (preferred && !preferred_found) {
-		g_slist_free (names);
+	if (preferred)
 		return g_strdup (preferred);
-	}
 
 	/* Otherwise find the next available unique connection name using the given
 	 * connection name template.
 	 */
-	while (!cname && (i++ < 10000)) {
+	for (i = 1; TRUE; i++) {
 		char *temp;
-		gboolean found = FALSE;
 
-		/* Translators: the first %s is a prefix for the connection id, such
+		/* TRANSLATORS: the first %s is a prefix for the connection id, such
 		 * as "Wired Connection" or "VPN Connection". The %d is a number
 		 * that is combined with the first argument to create a unique
 		 * connection id. */
-		temp = g_strdup_printf (C_("connection id fallback", "%s %d"),
+		temp = g_strdup_printf (C_("connection id fallback", "%s %u"),
 		                        fallback_prefix, i);
-		for (iter = names; iter; iter = g_slist_next (iter)) {
-			if (!strcmp (iter->data, temp)) {
-				found = TRUE;
-				break;
-			}
-		}
-		if (!found)
-			cname = temp;
-		else
-			g_free (temp);
-	}
 
-	g_slist_free (names);
-	return cname;
+		if (nm_utils_strv_find_first ((char **) existing_names,
+		                              existing_len,
+		                              temp) < 0)
+			return temp;
+
+		g_free (temp);
+	}
 }
 
 static char *
 get_new_connection_ifname (NMPlatform *platform,
-                           const GSList *existing,
+                           NMConnection *const*existing_connections,
                            const char *prefix)
 {
-	int i;
-	char *name;
-	const GSList *iter;
-	gboolean found;
+	guint i, j;
+
+	for (i = 0; TRUE; i++) {
+		char *name;
 
-	for (i = 0; i < 500; i++) {
 		name = g_strdup_printf ("%s%d", prefix, i);
 
 		if (nm_platform_link_get_by_ifname (platform, name))
 			goto next;
 
-		for (iter = existing, found = FALSE; iter; iter = g_slist_next (iter)) {
-			NMConnection *candidate = iter->data;
-
-			if (g_strcmp0 (nm_connection_get_interface_name (candidate), name) == 0) {
-				found = TRUE;
-				break;
+		if (existing_connections) {
+			for (j = 0; existing_connections[j]; j++) {
+				if (nm_streq0 (nm_connection_get_interface_name (existing_connections[j]),
+				               name))
+					goto next;
 			}
 		}
 
-		if (!found)
-			return name;
+		return name;
 
-	next:
+next:
 		g_free (name);
 	}
-
-	return NULL;
 }
 
 const char *
@@ -251,7 +245,7 @@ void
 nm_utils_complete_generic (NMPlatform *platform,
                            NMConnection *connection,
                            const char *ctype,
-                           const GSList *existing,
+                           NMConnection *const*existing_connections,
                            const char *preferred_id,
                            const char *fallback_id_prefix,
                            const char *ifname_prefix,
@@ -278,14 +272,14 @@ nm_utils_complete_generic (NMPlatform *platform,
 
 	/* Add a connection ID if absent */
 	if (!nm_setting_connection_get_id (s_con)) {
-		id = get_new_connection_name (existing, preferred_id, fallback_id_prefix);
+		id = get_new_connection_name (existing_connections, preferred_id, fallback_id_prefix);
 		g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_ID, id, NULL);
 		g_free (id);
 	}
 
 	/* Add an interface name, if requested */
 	if (ifname_prefix && !nm_setting_connection_get_interface_name (s_con)) {
-		ifname = get_new_connection_ifname (platform, existing, ifname_prefix);
+		ifname = get_new_connection_ifname (platform, existing_connections, ifname_prefix);
 		g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, NULL);
 		g_free (ifname);
 	}
@@ -880,58 +874,37 @@ nm_utils_match_connection (NMConnection *const*connections,
 
 /*****************************************************************************/
 
-/**
- * nm_utils_g_value_set_object_path:
- * @value: a #GValue, initialized to store an object path
- * @object: (allow-none): an #NMExportedObject
- *
- * Sets @value to @object's object path. If @object is %NULL, or not
- * exported, @value is set to "/".
- */
-void
-nm_utils_g_value_set_object_path (GValue *value, gpointer object)
-{
-	g_return_if_fail (!object || NM_IS_EXPORTED_OBJECT (object));
-
-	if (object && nm_exported_object_is_exported (object))
-		g_value_set_string (value, nm_exported_object_get_path (object));
-	else
-		g_value_set_string (value, "/");
-}
-
-/**
- * nm_utils_g_value_set_object_path_array:
- * @value: a #GValue, initialized to store an object path
- * @objects: a #GSList of #NMExportedObjects
- * @filter_func: (allow-none): function to call on each object in @objects
- * @user_data: data to pass to @filter_func
- *
- * Sets @value to an array of object paths of the objects in @objects.
- */
-void
-nm_utils_g_value_set_object_path_array (GValue *value,
-                                        GSList *objects,
-                                        NMUtilsObjectFunc filter_func,
-                                        gpointer user_data)
+int
+nm_match_spec_device_by_pllink (const NMPlatformLink *pllink,
+                                const char *match_device_type,
+                                const GSList *specs,
+                                int no_match_value)
 {
-	char **paths;
-	guint i;
-	GSList *iter;
-
-	paths = g_new (char *, g_slist_length (objects) + 1);
-	for (i = 0, iter = objects; iter; iter = iter->next) {
-		NMExportedObject *object = iter->data;
-		const char *path;
-
-		path = nm_exported_object_get_path (object);
-		if (!path)
-			continue;
-		if (filter_func && !filter_func ((GObject *) object, user_data))
-			continue;
-		paths[i++] = g_strdup (path);
+	NMMatchSpecMatchType m;
+
+	/* we can only match by certain properties that are available on the
+	 * platform link (and even @pllink might be missing.
+	 *
+	 * It's still useful because of specs like "*" and "except:interface-name:eth0",
+	 * which match even in that case. */
+	m = nm_match_spec_device (specs,
+	                          pllink ? pllink->name : NULL,
+	                          match_device_type,
+	                          pllink ? pllink->driver : NULL,
+	                          NULL,
+	                          NULL,
+	                          NULL);
+
+	switch (m) {
+	case NM_MATCH_SPEC_MATCH:
+		return TRUE;
+	case NM_MATCH_SPEC_NEG_MATCH:
+		return FALSE;
+	case NM_MATCH_SPEC_NO_MATCH:
+		return no_match_value;
 	}
-	paths[i] = NULL;
-	g_value_take_boxed (value, paths);
+	nm_assert_not_reached ();
+	return no_match_value;
 }
 
-/*****************************************************************************/
+
diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h
index e5f28b27..13bdb67e 100644
--- a/src/NetworkManagerUtils.h
+++ b/src/NetworkManagerUtils.h
@@ -31,7 +31,7 @@ const char *nm_utils_get_shared_wifi_permission (NMConnection *connection);
 void nm_utils_complete_generic (NMPlatform *platform,
                                 NMConnection *connection,
                                 const char *ctype,
-                                const GSList *existing,
+                                NMConnection *const*existing_connections,
                                 const char *preferred_id,
                                 const char *fallback_id_prefix,
                                 const char *ifname_prefix,
@@ -48,21 +48,10 @@ NMConnection *nm_utils_match_connection (NMConnection *const*connections,
                                          NMUtilsMatchFilterFunc match_filter_func,
                                          gpointer match_filter_data);
 
-void nm_utils_g_value_set_object_path (GValue *value, gpointer object);
-
-/**
- * NMUtilsObjectFunc:
- * @object: the object to filter on
- * @user_data: data passed to the function from the caller
- *
- * Returns: %TRUE if the object should be used, %FALSE if not
- */
-typedef gboolean (*NMUtilsObjectFunc) (GObject *object, gpointer user_data);
-
-void nm_utils_g_value_set_object_path_array (GValue *value,
-                                             GSList *objects,
-                                             NMUtilsObjectFunc filter_func,
-                                             gpointer user_data);
+int nm_match_spec_device_by_pllink (const NMPlatformLink *pllink,
+                                    const char *match_device_type,
+                                    const GSList *specs,
+                                    int no_match_value);
 
 /*****************************************************************************/
 
diff --git a/src/devices/adsl/meson.build b/src/devices/adsl/meson.build
new file mode 100644
index 00000000..4b0fade0
--- /dev/null
+++ b/src/devices/adsl/meson.build
@@ -0,0 +1,34 @@
+sources = files(
+  'nm-atm-manager.c',
+  'nm-device-adsl.c'
+)
+
+deps = [
+  libudev_dep,
+  nm_dep
+]
+
+libnm_device_plugin_adsl = shared_module(
+  'nm-device-plugin-adsl',
+  sources: sources,
+  dependencies: deps,
+  link_args: ldflags_linker_script_devices,
+  link_depends: linker_script_devices,
+  install: true,
+  install_dir: nm_pkglibdir
+)
+
+core_plugins += libnm_device_plugin_adsl
+
+run_target(
+  'check-local-devices-adsl',
+  command: [check_exports, libnm_device_plugin_adsl.full_path(), linker_script_devices],
+  depends: libnm_device_plugin_adsl
+)
+
+# FIXME: check_so_symbols replacement
+'''
+check-local-devices-adsl: src/devices/adsl/libnm-device-plugin-adsl.la
+  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/adsl/.libs/libnm-device-plugin-adsl.so "$(srcdir)/linker-script-devices.ver"
+  $(call check_so_symbols,$(builddir)/src/devices/adsl/.libs/libnm-device-plugin-adsl.so)
+'''
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c
index e9bd41ae..91331376 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/devices/adsl/nm-device-adsl.c
@@ -39,8 +39,6 @@
 #include "nm-setting-adsl.h"
 #include "nm-utils.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Adsl.h"
-
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF (NMDeviceAdsl);
 
@@ -116,7 +114,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingAdsl *s_adsl;
@@ -137,8 +135,6 @@ complete_connection (NMDevice *device,
 	                           _("ADSL connection"),
 	                           NULL,
 	                           FALSE); /* No IPv6 yet by default */
-
-
 	return TRUE;
 }
 
@@ -431,8 +427,22 @@ ppp_state_changed (NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_
 }
 
 static void
+ppp_ifindex_set (NMPPPManager *ppp_manager,
+                 int ifindex,
+                 const char *iface,
+                 gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+
+	if (!nm_device_set_ip_ifindex (device, ifindex)) {
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+	}
+}
+
+static void
 ppp_ip4_config (NMPPPManager *ppp_manager,
-                const char *iface,
                 NMIP4Config *config,
                 gpointer user_data)
 {
@@ -440,7 +450,6 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 
 	/* Ignore PPP IP4 events that come in after initial configuration */
 	if (nm_device_activate_ip4_state_in_conf (device)) {
-		nm_device_set_ip_iface (device, iface);
 		nm_device_activate_schedule_ip4_config_result (device, config);
 	}
 }
@@ -499,6 +508,9 @@ act_stage3_ip4_config_start (NMDevice *device,
 	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_STATE_CHANGED,
 	                  G_CALLBACK (ppp_state_changed),
 	                  self);
+	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IFINDEX_SET,
+	                  G_CALLBACK (ppp_ifindex_set),
+	                  self);
 	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IP4_CONFIG,
 	                  G_CALLBACK (ppp_ip4_config),
 	                  self);
@@ -639,10 +651,24 @@ dispose (GObject *object)
 	G_OBJECT_CLASS (nm_device_adsl_parent_class)->dispose (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_adsl = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_ADSL,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Carrier", "b", NM_DEVICE_CARRIER),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_adsl_class_init (NMDeviceAdslClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	object_class->constructed  = constructed;
@@ -650,6 +676,8 @@ nm_device_adsl_class_init (NMDeviceAdslClass *klass)
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_adsl);
+
 	parent_class->get_generic_capabilities = get_generic_capabilities;
 
 	parent_class->check_connection_compatible = check_connection_compatible;
@@ -666,8 +694,4 @@ nm_device_adsl_class_init (NMDeviceAdslClass *klass)
 	                       G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_ADSL_SKELETON,
-	                                        NULL);
 }
diff --git a/src/devices/bluetooth/meson.build b/src/devices/bluetooth/meson.build
new file mode 100644
index 00000000..eb200679
--- /dev/null
+++ b/src/devices/bluetooth/meson.build
@@ -0,0 +1,45 @@
+sources = files(
+  'nm-bluez-device.c',
+  'nm-bluez-manager.c',
+  'nm-bluez4-adapter.c',
+  'nm-bluez4-manager.c',
+  'nm-bluez5-manager.c',
+  'nm-bt-error.c',
+  'nm-device-bt.c'
+)
+
+deps = [
+  libnm_wwan_dep,
+  nm_dep
+]
+
+if enable_bluez5_dun
+  sources += files('nm-bluez5-dun.c')
+
+  deps += bluez5_dep
+endif
+
+libnm_device_plugin_bluetooth = shared_module(
+  'nm-device-plugin-bluetooth',
+  sources: sources,
+  dependencies: deps,
+  link_args: ldflags_linker_script_devices,
+  link_depends: linker_script_devices,
+  install: true,
+  install_dir: nm_pkglibdir
+)
+
+core_plugins += libnm_device_plugin_bluetooth
+
+run_target(
+  'check-local-devices-bluetooth',
+  command: [check_exports, libnm_device_plugin_bluetooth.full_path(), linker_script_devices],
+  depends: libnm_device_plugin_bluetooth
+)
+
+# FIXME: check_so_symbols replacement
+'''
+check-local-devices-bluetooth: src/devices/bluetooth/libnm-device-plugin-bluetooth.la
+  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so "$(srcdir)/linker-script-devices.ver"
+  $(call check_so_symbols,$(builddir)/src/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so)
+'''
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
index bd3cf18a..cc9e38c8 100644
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ b/src/devices/bluetooth/nm-bluez-device.c
@@ -254,7 +254,7 @@ pan_connection_check_create (NMBluezDevice *self)
 		g_assert (connection_compatible (self, added));
 		g_assert (nm_connection_compare (added, connection, NM_SETTING_COMPARE_FLAG_EXACT));
 
-		nm_settings_connection_set_flags (NM_SETTINGS_CONNECTION (added), NM_SETTINGS_CONNECTION_FLAGS_NM_GENERATED, TRUE);
+		nm_settings_connection_set_flags (NM_SETTINGS_CONNECTION (added), NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, TRUE);
 
 		priv->connections = g_slist_prepend (priv->connections, g_object_ref (added));
 		priv->pan_connection = added;
@@ -1186,7 +1186,7 @@ dispose (GObject *object)
 		/* Check whether we want to remove the created connection. If so, we take a reference
 		 * and delete it at the end of dispose(). */
 		if (NM_FLAGS_HAS (nm_settings_connection_get_flags (NM_SETTINGS_CONNECTION (priv->pan_connection)),
-		                  NM_SETTINGS_CONNECTION_FLAGS_NM_GENERATED))
+		                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
 			to_delete = g_object_ref (priv->pan_connection);
 
 		priv->pan_connection = NULL;
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c
index 0f19f998..c8ef7a27 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.c
+++ b/src/devices/bluetooth/nm-bluez4-adapter.c
@@ -25,7 +25,6 @@
 #include <string.h>
 
 #include "nm-dbus-interface.h"
-#include "nm-utils/nm-hash-utils.h"
 #include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
 #include "nm-core-internal.h"
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
index de3f4072..5d3bd23a 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ b/src/devices/bluetooth/nm-bluez5-manager.c
@@ -30,7 +30,7 @@
 
 #include "nm-core-internal.h"
 
-#include "nm-utils/c-list.h"
+#include "c-list/src/c-list.h"
 #include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
 #include "devices/nm-device-bridge.h"
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index 977c1e1a..1d237d92 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -43,8 +43,6 @@
 #include "devices/wwan/nm-modem-manager.h"
 #include "devices/wwan/nm-modem.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Bluetooth.h"
-
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceBt);
 
@@ -217,7 +215,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
@@ -540,11 +538,16 @@ modem_ip4_config_result (NMModem *modem,
 }
 
 static void
-data_port_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
+ip_ifindex_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
 {
-	NMDevice *self = NM_DEVICE (user_data);
+	NMDevice *device = NM_DEVICE (user_data);
 
-	nm_device_set_ip_iface (self, nm_modem_get_data_port (modem));
+	if (!nm_device_set_ip_ifindex (device,
+	                               nm_modem_get_ip_ifindex (modem))) {
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+	}
 }
 
 static gboolean
@@ -640,29 +643,24 @@ component_added (NMDevice *device, GObject *component)
 	NMDeviceBt *self = NM_DEVICE_BT (device);
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 	NMModem *modem;
-	const gchar *modem_data_port;
-	const gchar *modem_control_port;
-	char *base;
 	NMDeviceState state;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-	if (!component || !NM_IS_MODEM (component))
+	if (   !component
+	    || !NM_IS_MODEM (component))
 		return FALSE;
-	modem = NM_MODEM (component);
-
-	modem_data_port = nm_modem_get_data_port (modem);
-	modem_control_port = nm_modem_get_control_port (modem);
-	g_return_val_if_fail (modem_data_port != NULL || modem_control_port != NULL, FALSE);
 
+	modem = NM_MODEM (component);
 	if (!priv->rfcomm_iface)
 		return FALSE;
 
-	base = g_path_get_basename (priv->rfcomm_iface);
-	if (g_strcmp0 (base, modem_data_port) && g_strcmp0 (base, modem_control_port)) {
-		g_free (base);
-		return FALSE;
+	{
+		gs_free char *base = NULL;
+
+		base = g_path_get_basename (priv->rfcomm_iface);
+		if (!nm_streq (base, nm_modem_get_control_port (modem)))
+			return FALSE;
 	}
-	g_free (base);
 
 	/* Got the modem */
 	nm_clear_g_source (&priv->timeout_id);
@@ -696,7 +694,7 @@ component_added (NMDevice *device, GObject *component)
 	g_signal_connect (modem, NM_MODEM_STATE_CHANGED, G_CALLBACK (modem_state_cb), self);
 	g_signal_connect (modem, NM_MODEM_REMOVED, G_CALLBACK (modem_removed_cb), self);
 
-	g_signal_connect (modem, "notify::" NM_MODEM_DATA_PORT, G_CALLBACK (data_port_changed_cb), self);
+	g_signal_connect (modem, "notify::" NM_MODEM_IP_IFINDEX, G_CALLBACK (ip_ifindex_changed_cb), self);
 
 	/* Kick off the modem connection */
 	if (!modem_stage1 (self, modem, &failure_reason))
@@ -753,7 +751,7 @@ bluez_connect_cb (GObject *object,
                   GAsyncResult *res,
                   void *user_data)
 {
-	NMDeviceBt *self = NM_DEVICE_BT (user_data);
+	gs_unref_object NMDeviceBt *self = NM_DEVICE_BT (user_data);
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 	GError *error = NULL;
 	const char *device;
@@ -761,6 +759,9 @@ bluez_connect_cb (GObject *object,
 	device = nm_bluez_device_connect_finish (NM_BLUEZ_DEVICE (object),
 	                                         res, &error);
 
+	if (!nm_device_is_activating (NM_DEVICE (self)))
+		return;
+
 	if (!device) {
 		_LOGW (LOGD_BT, "Error connecting with bluez: %s", error->message);
 		g_clear_error (&error);
@@ -768,7 +769,6 @@ bluez_connect_cb (GObject *object,
 		nm_device_state_changed (NM_DEVICE (self),
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_BT_FAILED);
-		g_object_unref (self);
 		return;
 	}
 
@@ -776,7 +776,13 @@ bluez_connect_cb (GObject *object,
 		g_free (priv->rfcomm_iface);
 		priv->rfcomm_iface = g_strdup (device);
 	} else if (priv->bt_type == NM_BT_CAPABILITY_NAP) {
-		nm_device_set_ip_iface (NM_DEVICE (self), device);
+		if (!nm_device_set_ip_iface (NM_DEVICE (self), device)) {
+			_LOGW (LOGD_BT, "Error connecting with bluez: cannot find device %s", device);
+			nm_device_state_changed (NM_DEVICE (self),
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_BT_FAILED);
+			return;
+		}
 	}
 
 	_LOGD (LOGD_BT, "connect request successful");
@@ -784,7 +790,6 @@ bluez_connect_cb (GObject *object,
 	/* Stage 3 gets scheduled when Bluez says we're connected */
 	priv->have_iface = TRUE;
 	check_connect_continue (self);
-	g_object_unref (self);
 }
 
 static void
@@ -1145,10 +1150,26 @@ finalize (GObject *object)
 	G_OBJECT_CLASS (nm_device_bt_parent_class)->finalize (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_bluetooth = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_BLUETOOTH,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",      "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Name",           "s",  NM_DEVICE_BT_NAME),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("BtCapabilities", "u",  NM_DEVICE_BT_CAPABILITIES),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_bt_class_init (NMDeviceBtClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->constructed = constructed;
@@ -1157,6 +1178,8 @@ nm_device_bt_class_init (NMDeviceBtClass *klass)
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_bluetooth);
+
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->can_auto_connect = can_auto_connect;
 	device_class->deactivate = deactivate;
@@ -1200,8 +1223,4 @@ nm_device_bt_class_init (NMDeviceBtClass *klass)
 	                  G_TYPE_NONE, 2,
 	                  G_TYPE_UINT /*guint32 in_bytes*/,
 	                  G_TYPE_UINT /*guint32 out_bytes*/);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_BLUETOOTH_SKELETON,
-	                                        NULL);
 }
diff --git a/src/devices/meson.build b/src/devices/meson.build
new file mode 100644
index 00000000..2d874659
--- /dev/null
+++ b/src/devices/meson.build
@@ -0,0 +1,22 @@
+subdir('adsl')
+
+if enable_modem_manager
+  subdir('wwan')
+  subdir('bluetooth')
+endif
+
+if enable_wifi
+  subdir('wifi')
+endif
+
+if enable_teamdctl
+  subdir('team')
+endif
+
+if enable_ovs
+  subdir('ovs')
+endif
+
+if enable_tests
+  subdir('tests')
+endif
diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c
new file mode 100644
index 00000000..1bade4ff
--- /dev/null
+++ b/src/devices/nm-acd-manager.c
@@ -0,0 +1,489 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Copyright (C) 2015-2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-acd-manager.h"
+
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "platform/nm-platform.h"
+#include "nm-utils.h"
+#include "NetworkManagerUtils.h"
+#include "n-acd/src/n-acd.h"
+
+/*****************************************************************************/
+
+typedef enum {
+	STATE_INIT,
+	STATE_PROBING,
+	STATE_PROBE_DONE,
+	STATE_ANNOUNCING,
+} State;
+
+typedef struct {
+	in_addr_t address;
+	gboolean duplicate;
+	NMAcdManager *manager;
+	NAcd *acd;
+	GIOChannel *channel;
+	guint event_id;
+} AddressInfo;
+
+enum {
+	PROBE_TERMINATED,
+	LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct {
+	int            ifindex;
+	guint8         hwaddr[ETH_ALEN];
+	State          state;
+	GHashTable    *addresses;
+	guint          completed;
+} NMAcdManagerPrivate;
+
+struct _NMAcdManager {
+	GObject parent;
+	NMAcdManagerPrivate _priv;
+};
+
+struct _NMAcdManagerClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMAcdManager, nm_acd_manager, G_TYPE_OBJECT)
+
+#define NM_ACD_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMAcdManager, NM_IS_ACD_MANAGER)
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN         LOGD_IP4
+#define _NMLOG_PREFIX_NAME    "acd"
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        char _sbuf[64]; \
+        int _ifindex = (self) ? NM_ACD_MANAGER_GET_PRIVATE (self)->ifindex : 0; \
+        \
+        nm_log ((level), _NMLOG_DOMAIN, \
+                nm_platform_link_get_name (NM_PLATFORM_GET, _ifindex), \
+                NULL, \
+                "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                _NMLOG_PREFIX_NAME, \
+                self ? nm_sprintf_buf (_sbuf, "[%p,%d]", self, _ifindex) : "" \
+                _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static const char *
+_acd_event_to_string (unsigned int event)
+{
+	switch (event) {
+	case N_ACD_EVENT_READY:
+		return "ready";
+	case N_ACD_EVENT_USED:
+		return "used";
+	case N_ACD_EVENT_DEFENDED:
+		return "defended";
+	case N_ACD_EVENT_CONFLICT:
+		return "conflict";
+	case N_ACD_EVENT_DOWN:
+		return "down";
+	}
+	return NULL;
+}
+
+#define acd_event_to_string(event) NM_UTILS_LOOKUP_STR (_acd_event_to_string, event)
+
+static const char *
+_acd_error_to_string (int error)
+{
+	if (error < 0)
+		return strerror(-error);
+
+	switch (error) {
+	case _N_ACD_E_SUCCESS:
+		return "success";
+	case N_ACD_E_DONE:
+		return "no more events (engine running)";
+	case N_ACD_E_STOPPED:
+		return "no more events (engine stopped)";
+	case N_ACD_E_PREEMPTED:
+		return "preempted";
+	case N_ACD_E_INVALID_ARGUMENT:
+		return "invalid argument";
+	case N_ACD_E_BUSY:
+		return "busy";
+	}
+	return NULL;
+}
+
+#define acd_error_to_string(error) NM_UTILS_LOOKUP_STR (_acd_error_to_string, error)
+
+/*****************************************************************************/
+
+/**
+ * nm_acd_manager_add_address:
+ * @self: a #NMAcdManager
+ * @address: an IP address
+ *
+ * Add @address to the list of IP addresses to probe.
+
+ * Returns: %TRUE on success, %FALSE if the address was already in the list
+ */
+gboolean
+nm_acd_manager_add_address (NMAcdManager *self, in_addr_t address)
+{
+	NMAcdManagerPrivate *priv;
+	AddressInfo *info;
+
+	g_return_val_if_fail (NM_IS_ACD_MANAGER (self), FALSE);
+	priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+	g_return_val_if_fail (priv->state == STATE_INIT, FALSE);
+
+	if (g_hash_table_lookup (priv->addresses, GUINT_TO_POINTER (address)))
+		return FALSE;
+
+	info = g_slice_new0 (AddressInfo);
+	info->address = address;
+	info->manager = self;
+
+	g_hash_table_insert (priv->addresses, GUINT_TO_POINTER (address), info);
+
+	return TRUE;
+}
+
+static gboolean
+acd_event (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+	AddressInfo *info = data;
+	NMAcdManager *self = info->manager;
+	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+	NAcdEvent *event;
+	char address_str[INET_ADDRSTRLEN];
+	gs_free char *hwaddr_str = NULL;
+	int r;
+
+	if (   n_acd_dispatch (info->acd)
+	    || n_acd_pop_event (info->acd, &event))
+		return G_SOURCE_CONTINUE;
+
+	switch (event->event) {
+	case N_ACD_EVENT_READY:
+		info->duplicate = FALSE;
+		if (priv->state == STATE_ANNOUNCING) {
+			r = n_acd_announce (info->acd, N_ACD_DEFEND_ONCE);
+			if (r) {
+				_LOGW ("couldn't announce address %s on interface '%s': %s",
+				       nm_utils_inet4_ntop (info->address, address_str),
+				       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex),
+				       acd_error_to_string (r));
+			} else {
+				_LOGD ("announcing address %s",
+				       nm_utils_inet4_ntop (info->address, address_str));
+			}
+		}
+		break;
+	case N_ACD_EVENT_USED:
+		info->duplicate = TRUE;
+		break;
+	case N_ACD_EVENT_DEFENDED:
+		_LOGD ("defended address %s from host %s",
+		       nm_utils_inet4_ntop (info->address, address_str),
+		       (hwaddr_str = nm_utils_hwaddr_ntoa (event->defended.sender,
+		                                           event->defended.n_sender)));
+		break;
+	case N_ACD_EVENT_CONFLICT:
+		_LOGW ("conflict for address %s detected with host %s on interface '%s'",
+		       nm_utils_inet4_ntop (info->address, address_str),
+		       (hwaddr_str = nm_utils_hwaddr_ntoa (event->defended.sender,
+		                                           event->defended.n_sender)),
+		       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex));
+		break;
+	default:
+		_LOGD ("event '%s' for address %s",
+		       acd_event_to_string (event->event),
+		       nm_utils_inet4_ntop (info->address, address_str));
+		return G_SOURCE_CONTINUE;
+	}
+
+	if (   priv->state == STATE_PROBING
+	    && ++priv->completed == g_hash_table_size (priv->addresses)) {
+		priv->state = STATE_PROBE_DONE;
+		g_signal_emit (self, signals[PROBE_TERMINATED], 0);
+	}
+
+	return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+acd_probe_start (NMAcdManager *self,
+                 AddressInfo *info,
+                 guint64 timeout)
+{
+	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+	NAcdConfig *config;
+	int r, fd;
+
+	r = n_acd_new (&info->acd);
+	if (r) {
+		_LOGW ("could not create ACD for %s on interface '%s': %s",
+		       nm_utils_inet4_ntop (info->address, NULL),
+		       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex),
+		       acd_error_to_string (r));
+		return FALSE;
+	}
+
+	n_acd_get_fd (info->acd, &fd);
+	info->channel = g_io_channel_unix_new (fd);
+	info->event_id = g_io_add_watch (info->channel, G_IO_IN, acd_event, info);
+
+	config = &(NAcdConfig) {
+		.ifindex = priv->ifindex,
+		.mac = priv->hwaddr,
+		.n_mac = ETH_ALEN,
+		.ip = info->address,
+		.timeout_msec = timeout,
+		.transport = N_ACD_TRANSPORT_ETHERNET,
+	};
+
+	r = n_acd_start (info->acd, config);
+	if (r) {
+		_LOGW ("could not start probe for %s on interface '%s': %s",
+		       nm_utils_inet4_ntop (info->address, NULL),
+		       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex),
+		       acd_error_to_string (r));
+		return FALSE;
+	}
+
+	_LOGD ("start probe for %s", nm_utils_inet4_ntop (info->address, NULL));
+
+	return TRUE;
+}
+
+/**
+ * nm_acd_manager_start_probe:
+ * @self: a #NMAcdManager
+ * @timeout: maximum probe duration in milliseconds
+ * @error: location to store error, or %NULL
+ *
+ * Start probing IP addresses for duplicates; when the probe terminates a
+ * PROBE_TERMINATED signal is emitted.
+ *
+ * Returns: %TRUE if at least one probe could be started, %FALSE otherwise
+ */
+gboolean
+nm_acd_manager_start_probe (NMAcdManager *self, guint timeout)
+{
+	NMAcdManagerPrivate *priv;
+	GHashTableIter iter;
+	AddressInfo *info;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail (NM_IS_ACD_MANAGER (self), FALSE);
+	priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+	g_return_val_if_fail (priv->state == STATE_INIT, FALSE);
+
+	priv->completed = 0;
+
+	g_hash_table_iter_init (&iter, priv->addresses);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info))
+		success |= acd_probe_start (self, info, timeout);
+
+	if (success)
+		priv->state = STATE_PROBING;
+
+	return success;
+}
+
+/**
+ * nm_acd_manager_reset:
+ * @self: a #NMAcdManager
+ *
+ * Stop any operation in progress and reset @self to the initial state.
+ */
+void
+nm_acd_manager_reset (NMAcdManager *self)
+{
+	NMAcdManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_ACD_MANAGER (self));
+	priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+
+	g_hash_table_remove_all (priv->addresses);
+
+	priv->state = STATE_INIT;
+}
+
+/**
+ * nm_acd_manager_destroy:
+ * @self: the #NMAcdManager
+ *
+ * Calls nm_acd_manager_reset() and unrefs @self.
+ */
+void
+nm_acd_manager_destroy (NMAcdManager *self)
+{
+	g_return_if_fail (NM_IS_ACD_MANAGER (self));
+
+	nm_acd_manager_reset (self);
+	g_object_unref (self);
+}
+
+/**
+ * nm_acd_manager_check_address:
+ * @self: a #NMAcdManager
+ * @address: an IP address
+ *
+ * Check if an IP address is duplicate. @address must have been added with
+ * nm_acd_manager_add_address().
+ *
+ * Returns: %TRUE if the address is not duplicate, %FALSE otherwise
+ */
+gboolean
+nm_acd_manager_check_address (NMAcdManager *self, in_addr_t address)
+{
+	NMAcdManagerPrivate *priv;
+	AddressInfo *info;
+
+	g_return_val_if_fail (NM_IS_ACD_MANAGER (self), FALSE);
+	priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+	g_return_val_if_fail (   priv->state == STATE_INIT
+	                      || priv->state == STATE_PROBE_DONE, FALSE);
+
+	info = g_hash_table_lookup (priv->addresses, GUINT_TO_POINTER (address));
+	g_return_val_if_fail (info, FALSE);
+
+	return !info->duplicate;
+}
+
+/**
+ * nm_acd_manager_announce_addresses:
+ * @self: a #NMAcdManager
+ *
+ * Start announcing addresses.
+ */
+void
+nm_acd_manager_announce_addresses (NMAcdManager *self)
+{
+	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+	GHashTableIter iter;
+	AddressInfo *info;
+	int r;
+
+	if (priv->state == STATE_INIT) {
+		/* n-acd can't announce without probing, therefore let's
+		 * start a fake probe with zero timeout and then perform
+		 * the announce. */
+		priv->state = STATE_ANNOUNCING;
+		g_hash_table_iter_init (&iter, priv->addresses);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) {
+			if (!acd_probe_start (self, info, 0)) {
+				_LOGW ("couldn't announce address %s on interface '%s'",
+				       nm_utils_inet4_ntop (info->address, NULL),
+				       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex));
+			}
+		}
+	} else if (priv->state == STATE_PROBE_DONE) {
+		priv->state = STATE_ANNOUNCING;
+		g_hash_table_iter_init (&iter, priv->addresses);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) {
+			if (info->duplicate)
+				continue;
+			r = n_acd_announce (info->acd, N_ACD_DEFEND_ONCE);
+			if (r) {
+				_LOGW ("couldn't announce address %s on interface '%s': %s",
+				       nm_utils_inet4_ntop (info->address, NULL),
+				       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex),
+				       acd_error_to_string (r));
+			} else
+				_LOGD ("announcing address %s", nm_utils_inet4_ntop (info->address, NULL));
+		}
+	} else
+		nm_assert_not_reached ();
+}
+
+static void
+destroy_address_info (gpointer data)
+{
+	AddressInfo *info = (AddressInfo *) data;
+
+	g_clear_pointer (&info->channel, g_io_channel_unref);
+	g_clear_pointer (&info->acd, n_acd_free);
+	nm_clear_g_source (&info->event_id);
+
+	g_slice_free (AddressInfo, info);
+}
+
+/*****************************************************************************/
+
+static void
+nm_acd_manager_init (NMAcdManager *self)
+{
+	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+
+	priv->addresses = g_hash_table_new_full (nm_direct_hash, NULL,
+	                                         NULL, destroy_address_info);
+	priv->state = STATE_INIT;
+}
+
+NMAcdManager *
+nm_acd_manager_new (int ifindex, const guint8 *hwaddr, size_t hwaddr_len)
+{
+	NMAcdManager *self;
+	NMAcdManagerPrivate *priv;
+
+	g_return_val_if_fail (hwaddr, NULL);
+	g_return_val_if_fail (hwaddr_len == ETH_ALEN, NULL);
+
+	self = g_object_new (NM_TYPE_ACD_MANAGER, NULL);
+	priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+	priv->ifindex = ifindex;
+	memcpy (priv->hwaddr, hwaddr, ETH_ALEN);
+
+	return self;
+}
+
+static void
+dispose (GObject *object)
+{
+	NMAcdManager *self = NM_ACD_MANAGER (object);
+	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+
+	g_clear_pointer (&priv->addresses, g_hash_table_destroy);
+
+	G_OBJECT_CLASS (nm_acd_manager_parent_class)->dispose (object);
+}
+
+static void
+nm_acd_manager_class_init (NMAcdManagerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	signals[PROBE_TERMINATED] =
+	    g_signal_new (NM_ACD_MANAGER_PROBE_TERMINATED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL, NULL,
+	                  G_TYPE_NONE, 0);
+}
diff --git a/src/devices/nm-acd-manager.h b/src/devices/nm-acd-manager.h
new file mode 100644
index 00000000..eeede5da
--- /dev/null
+++ b/src/devices/nm-acd-manager.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Copyright (C) 2015-2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_ACD_MANAGER__
+#define __NM_ACD_MANAGER__
+
+#include <netinet/in.h>
+
+#define NM_TYPE_ACD_MANAGER            (nm_acd_manager_get_type ())
+#define NM_ACD_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_ACD_MANAGER, NMAcdManager))
+#define NM_ACD_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_ACD_MANAGER, NMAcdManagerClass))
+#define NM_IS_ACD_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_ACD_MANAGER))
+#define NM_IS_ACD_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_ACD_MANAGER))
+#define NM_ACD_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_ACD_MANAGER, NMAcdManagerClass))
+
+#define NM_ACD_MANAGER_PROBE_TERMINATED  "probe-terminated"
+
+typedef struct _NMAcdManagerClass NMAcdManagerClass;
+
+GType nm_acd_manager_get_type (void);
+
+NMAcdManager *nm_acd_manager_new (int ifindex, const guint8 *hwaddr, size_t hwaddr_len);
+void nm_acd_manager_destroy (NMAcdManager *self);
+gboolean nm_acd_manager_add_address (NMAcdManager *self, in_addr_t address);
+gboolean nm_acd_manager_start_probe (NMAcdManager *self, guint timeout);
+gboolean nm_acd_manager_check_address (NMAcdManager *self, in_addr_t address);
+void nm_acd_manager_announce_addresses (NMAcdManager *self);
+void nm_acd_manager_reset (NMAcdManager *self);
+
+#endif /* __NM_ACD_MANAGER__ */
diff --git a/src/devices/nm-arping-manager.c b/src/devices/nm-arping-manager.c
deleted file mode 100644
index 51f80e08..00000000
--- a/src/devices/nm-arping-manager.c
+++ /dev/null
@@ -1,474 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * Copyright (C) 2015 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-arping-manager.h"
-
-#include <netinet/in.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-
-#include "platform/nm-platform.h"
-#include "nm-utils.h"
-#include "NetworkManagerUtils.h"
-
-/*****************************************************************************/
-
-typedef enum {
-	STATE_INIT,
-	STATE_PROBING,
-	STATE_PROBE_DONE,
-	STATE_ANNOUNCING,
-} State;
-
-typedef struct {
-	in_addr_t address;
-	GPid pid;
-	guint watch;
-	gboolean duplicate;
-	NMArpingManager *manager;
-} AddressInfo;
-
-/*****************************************************************************/
-
-enum {
-	PROBE_TERMINATED,
-	LAST_SIGNAL,
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	int            ifindex;
-	State          state;
-	GHashTable    *addresses;
-	guint          completed;
-	guint          timer;
-	guint          round2_id;
-} NMArpingManagerPrivate;
-
-struct _NMArpingManager {
-	GObject parent;
-	NMArpingManagerPrivate _priv;
-};
-
-struct _NMArpingManagerClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMArpingManager, nm_arping_manager, G_TYPE_OBJECT)
-
-#define NM_ARPING_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMArpingManager, NM_IS_ARPING_MANAGER)
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN         LOGD_IP4
-#define _NMLOG_PREFIX_NAME    "arping"
-#define _NMLOG(level, ...) \
-    G_STMT_START { \
-        char _sbuf[64]; \
-        int _ifindex = (self) ? NM_ARPING_MANAGER_GET_PRIVATE (self)->ifindex : 0; \
-        \
-        nm_log ((level), _NMLOG_DOMAIN, \
-                nm_platform_link_get_name (NM_PLATFORM_GET, _ifindex), \
-                NULL, \
-                "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                _NMLOG_PREFIX_NAME, \
-                self ? nm_sprintf_buf (_sbuf, "[%p,%d]", self, _ifindex) : "" \
-                _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
-    } G_STMT_END
-
-/*****************************************************************************/
-
-/**
- * nm_arping_manager_add_address:
- * @self: a #NMArpingManager
- * @address: an IP address
- *
- * Add @address to the list of IP addresses to probe.
-
- * Returns: %TRUE on success, %FALSE if the address was already in the list
- */
-gboolean
-nm_arping_manager_add_address (NMArpingManager *self, in_addr_t address)
-{
-	NMArpingManagerPrivate *priv;
-	AddressInfo *info;
-
-	g_return_val_if_fail (NM_IS_ARPING_MANAGER (self), FALSE);
-	priv = NM_ARPING_MANAGER_GET_PRIVATE (self);
-	g_return_val_if_fail (priv->state == STATE_INIT, FALSE);
-
-	if (g_hash_table_lookup (priv->addresses, GUINT_TO_POINTER (address))) {
-		_LOGD ("address already exists");
-		return FALSE;
-	}
-
-	info = g_slice_new0 (AddressInfo);
-	info->address = address;
-	info->manager = self;
-
-	g_hash_table_insert (priv->addresses, GUINT_TO_POINTER (address), info);
-
-	return TRUE;
-}
-
-static void
-arping_watch_cb (GPid pid, gint status, gpointer user_data)
-{
-	AddressInfo *info = user_data;
-	NMArpingManager *self = info->manager;
-	NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self);
-	const char *addr;
-
-	info->pid = 0;
-	info->watch = 0;
-	addr = nm_utils_inet4_ntop (info->address, NULL);
-
-	if (WIFEXITED (status)) {
-		if (WEXITSTATUS (status) != 0) {
-			_LOGD ("%s already used in the %s network",
-			       addr, nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex));
-			info->duplicate = TRUE;
-		} else
-			_LOGD ("DAD succeeded for %s", addr);
-	} else {
-		_LOGD ("stopped unexpectedly with status %d for %s", status, addr);
-	}
-
-	if (++priv->completed == g_hash_table_size (priv->addresses)) {
-		priv->state = STATE_PROBE_DONE;
-		nm_clear_g_source (&priv->timer);
-		g_signal_emit (self, signals[PROBE_TERMINATED], 0);
-	}
-}
-
-static gboolean
-arping_timeout_cb (gpointer user_data)
-{
-	NMArpingManager *self = user_data;
-	NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self);
-	GHashTableIter iter;
-	AddressInfo *info;
-
-	priv->timer = 0;
-
-	g_hash_table_iter_init (&iter, priv->addresses);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) {
-		nm_clear_g_source (&info->watch);
-		if (info->pid) {
-			_LOGD ("DAD timed out for %s",
-			       nm_utils_inet4_ntop (info->address, NULL));
-			nm_utils_kill_child_async (info->pid, SIGTERM, LOGD_IP4,
-			                           "arping", 1000, NULL, NULL);
-			info->pid = 0;
-		}
-	}
-
-	priv->state = STATE_PROBE_DONE;
-	g_signal_emit (self, signals[PROBE_TERMINATED], 0);
-
-	return G_SOURCE_REMOVE;
-}
-
-/**
- * nm_arping_manager_start_probe:
- * @self: a #NMArpingManager
- * @timeout: maximum probe duration in milliseconds
- * @error: location to store error, or %NULL
- *
- * Start probing IP addresses for duplicates; when the probe terminates a
- * PROBE_TERMINATED signal is emitted.
- *
- * Returns: %TRUE if at least one probe could be started, %FALSE otherwise
- */
-gboolean
-nm_arping_manager_start_probe (NMArpingManager *self, guint timeout, GError **error)
-{
-	const char *argv[] = { NULL, "-D", "-q", "-I", NULL, "-c", NULL, "-w", NULL, NULL, NULL };
-	NMArpingManagerPrivate *priv;
-	GHashTableIter iter;
-	AddressInfo *info;
-	gs_free char *timeout_str = NULL;
-	gboolean success = FALSE;
-
-	g_return_val_if_fail (NM_IS_ARPING_MANAGER (self), FALSE);
-	g_return_val_if_fail (!error || !*error, FALSE);
-	g_return_val_if_fail (timeout, FALSE);
-
-	priv = NM_ARPING_MANAGER_GET_PRIVATE (self);
-	g_return_val_if_fail (priv->state == STATE_INIT, FALSE);
-
-	argv[4] = nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex);
-	if (!argv[4]) {
-		/* The device was probably just removed. */
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
-		             "can't find a name for ifindex %d", priv->ifindex);
-		return FALSE;
-	}
-
-	priv->completed = 0;
-
-	argv[0] = nm_utils_find_helper ("arping", NULL, NULL);
-	if (!argv[0]) {
-		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
-		                     "arping could not be found");
-		return FALSE;
-	}
-
-	timeout_str = g_strdup_printf ("%u", timeout / 1000 + 2);
-	argv[6] = timeout_str;
-	argv[8] = timeout_str;
-
-	g_hash_table_iter_init (&iter, priv->addresses);
-
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) {
-		gs_free char *tmp_str = NULL;
-
-		argv[9] = nm_utils_inet4_ntop (info->address, NULL);
-		_LOGD ("run %s", (tmp_str = g_strjoinv (" ", (char **) argv)));
-
-		if (g_spawn_async (NULL, (char **) argv, NULL,
-		                   G_SPAWN_STDOUT_TO_DEV_NULL |
-		                   G_SPAWN_STDERR_TO_DEV_NULL |
-		                   G_SPAWN_DO_NOT_REAP_CHILD,
-		                   NULL, NULL, &info->pid, NULL)) {
-			info->watch = g_child_watch_add (info->pid, arping_watch_cb, info);
-			success = TRUE;
-		}
-	}
-
-	if (success) {
-		priv->timer = g_timeout_add (timeout, arping_timeout_cb, self);
-		priv->state = STATE_PROBING;
-	} else {
-		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
-		                     "could not spawn arping process");
-	}
-
-	return success;
-}
-
-/**
- * nm_arping_manager_reset:
- * @self: a #NMArpingManager
- *
- * Stop any operation in progress and reset @self to the initial state.
- */
-void
-nm_arping_manager_reset (NMArpingManager *self)
-{
-	NMArpingManagerPrivate *priv;
-
-	g_return_if_fail (NM_IS_ARPING_MANAGER (self));
-	priv = NM_ARPING_MANAGER_GET_PRIVATE (self);
-
-	nm_clear_g_source (&priv->timer);
-	nm_clear_g_source (&priv->round2_id);
-	g_hash_table_remove_all (priv->addresses);
-
-	priv->state = STATE_INIT;
-}
-
-/**
- * nm_arping_manager_destroy:
- * @self: the #NMArpingManager
- *
- * Calls nm_arping_manager_reset() and unrefs @self.
- */
-void
-nm_arping_manager_destroy (NMArpingManager *self)
-{
-	g_return_if_fail (NM_IS_ARPING_MANAGER (self));
-
-	nm_arping_manager_reset (self);
-	g_object_unref (self);
-}
-
-/**
- * nm_arping_manager_check_address:
- * @self: a #NMArpingManager
- * @address: an IP address
- *
- * Check if an IP address is duplicate. @address must have been added with
- * nm_arping_manager_add_address().
- *
- * Returns: %TRUE if the address is not duplicate, %FALSE otherwise
- */
-gboolean
-nm_arping_manager_check_address (NMArpingManager *self, in_addr_t address)
-{
-	NMArpingManagerPrivate *priv;
-	AddressInfo *info;
-
-	g_return_val_if_fail (NM_IS_ARPING_MANAGER (self), FALSE);
-	priv = NM_ARPING_MANAGER_GET_PRIVATE (self);
-	g_return_val_if_fail (   priv->state == STATE_INIT
-	                      || priv->state == STATE_PROBE_DONE, FALSE);
-
-	info = g_hash_table_lookup (priv->addresses, GUINT_TO_POINTER (address));
-	g_return_val_if_fail (info, FALSE);
-
-	return !info->duplicate;
-}
-
-static void
-send_announcements (NMArpingManager *self, const char *mode_arg)
-{
-	NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self);
-	const char *argv[] = { NULL, mode_arg, "-q", "-I", NULL, "-c", "1", NULL, NULL };
-	int ip_arg = G_N_ELEMENTS (argv) - 2;
-	GError *error = NULL;
-	GHashTableIter iter;
-	AddressInfo *info;
-
-	argv[4] = nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex);
-	if (!argv[4]) {
-		/* The device was probably just removed. */
-		_LOGW ("can't find a name for ifindex %d", priv->ifindex);
-		return;
-	}
-
-	argv[0] = nm_utils_find_helper ("arping", NULL, NULL);
-	if (!argv[0]) {
-		_LOGW ("arping could not be found; no ARPs will be sent");
-		return;
-	}
-
-	g_hash_table_iter_init (&iter, priv->addresses);
-
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) {
-		gs_free char *tmp_str = NULL;
-		gboolean success;
-
-		if (info->duplicate)
-			continue;
-
-		argv[ip_arg] = nm_utils_inet4_ntop (info->address, NULL);
-		_LOGD ("run %s", (tmp_str = g_strjoinv (" ", (char **) argv)));
-
-		success = g_spawn_async (NULL, (char **) argv, NULL,
-		                         G_SPAWN_STDOUT_TO_DEV_NULL |
-		                         G_SPAWN_STDERR_TO_DEV_NULL,
-		                         NULL, NULL, NULL, &error);
-		if (!success) {
-			_LOGW ("could not send ARP for address %s: %s", argv[ip_arg],
-			       error->message);
-			g_clear_error (&error);
-		}
-	}
-}
-
-static gboolean
-arp_announce_round2 (gpointer self)
-{
-	NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE ((NMArpingManager *) self);
-
-	priv->round2_id = 0;
-	send_announcements (self, "-U");
-	priv->state = STATE_INIT;
-	g_hash_table_remove_all (priv->addresses);
-
-	return G_SOURCE_REMOVE;
-}
-
-/**
- * nm_arping_manager_announce_addresses:
- * @self: a #NMArpingManager
- *
- * Start announcing addresses.
- */
-void
-nm_arping_manager_announce_addresses (NMArpingManager *self)
-{
-	NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self);
-
-	g_return_if_fail (   priv->state == STATE_INIT
-	                  || priv->state == STATE_PROBE_DONE);
-
-	send_announcements (self, "-A");
-	nm_clear_g_source (&priv->round2_id);
-	priv->round2_id = g_timeout_add_seconds (2, arp_announce_round2, self);
-	priv->state = STATE_ANNOUNCING;
-}
-
-static void
-destroy_address_info (gpointer data)
-{
-	AddressInfo *info = (AddressInfo *) data;
-
-	nm_clear_g_source (&info->watch);
-
-	if (info->pid) {
-		nm_utils_kill_child_async (info->pid, SIGTERM, LOGD_IP4, "arping",
-		                           1000, NULL, NULL);
-	}
-
-	g_slice_free (AddressInfo, info);
-}
-
-/*****************************************************************************/
-
-static void
-nm_arping_manager_init (NMArpingManager *self)
-{
-	NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self);
-
-	priv->addresses = g_hash_table_new_full (g_direct_hash, g_direct_equal,
-	                                         NULL, destroy_address_info);
-	priv->state = STATE_INIT;
-}
-
-NMArpingManager *
-nm_arping_manager_new (int ifindex)
-{
-	NMArpingManager *self;
-	NMArpingManagerPrivate *priv;
-
-	self = g_object_new (NM_TYPE_ARPING_MANAGER, NULL);
-	priv = NM_ARPING_MANAGER_GET_PRIVATE (self);
-	priv->ifindex = ifindex;
-	return self;
-}
-
-static void
-dispose (GObject *object)
-{
-	NMArpingManager *self = NM_ARPING_MANAGER (object);
-	NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self);
-
-	nm_clear_g_source (&priv->timer);
-	nm_clear_g_source (&priv->round2_id);
-	g_clear_pointer (&priv->addresses, g_hash_table_destroy);
-
-	G_OBJECT_CLASS (nm_arping_manager_parent_class)->dispose (object);
-}
-
-static void
-nm_arping_manager_class_init (NMArpingManagerClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->dispose = dispose;
-
-	signals[PROBE_TERMINATED] =
-	    g_signal_new (NM_ARPING_MANAGER_PROBE_TERMINATED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 0);
-}
diff --git a/src/devices/nm-arping-manager.h b/src/devices/nm-arping-manager.h
deleted file mode 100644
index c8a86af0..00000000
--- a/src/devices/nm-arping-manager.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * Copyright (C) 2015 Red Hat, Inc.
- */
-
-#ifndef __NM_ARPING_MANAGER__
-#define __NM_ARPING_MANAGER__
-
-#include <netinet/in.h>
-
-#define NM_TYPE_ARPING_MANAGER            (nm_arping_manager_get_type ())
-#define NM_ARPING_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_ARPING_MANAGER, NMArpingManager))
-#define NM_ARPING_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_ARPING_MANAGER, NMArpingManagerClass))
-#define NM_IS_ARPING_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_ARPING_MANAGER))
-#define NM_IS_ARPING_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_ARPING_MANAGER))
-#define NM_ARPING_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_ARPING_MANAGER, NMArpingManagerClass))
-
-#define NM_ARPING_MANAGER_PROBE_TERMINATED  "probe-terminated"
-
-typedef struct _NMArpingManagerClass NMArpingManagerClass;
-
-GType nm_arping_manager_get_type (void);
-
-NMArpingManager *nm_arping_manager_new (int ifindex);
-void nm_arping_manager_destroy (NMArpingManager *self);
-gboolean nm_arping_manager_add_address (NMArpingManager *self, in_addr_t address);
-gboolean nm_arping_manager_start_probe (NMArpingManager *self, guint timeout, GError **error);
-gboolean nm_arping_manager_check_address (NMArpingManager *self, in_addr_t address);
-void nm_arping_manager_announce_addresses (NMArpingManager *self);
-void nm_arping_manager_reset (NMArpingManager *self);
-
-#endif /* __NM_ARPING_MANAGER__ */
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index 910dd0bf..2dd9494a 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -32,8 +32,6 @@
 #include "nm-core-internal.h"
 #include "nm-ip4-config.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Bond.h"
-
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceBond);
 
@@ -78,7 +76,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingBond *s_bond;
@@ -607,13 +605,31 @@ nm_device_bond_init (NMDeviceBond * self)
 	nm_assert (nm_device_is_master (NM_DEVICE (self)));
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_bond = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_BOND,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress", "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Carrier",   "b",  NM_DEVICE_CARRIER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Slaves",    "ao", NM_DEVICE_SLAVES),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_bond_class_init (NMDeviceBondClass *klass)
 {
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_BOND_SETTING_NAME, NM_LINK_TYPE_BOND)
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_bond);
+
 	parent_class->is_master = TRUE;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
 	parent_class->check_connection_compatible = check_connection_compatible;
@@ -629,10 +645,6 @@ nm_device_bond_class_init (NMDeviceBondClass *klass)
 	parent_class->release_slave = release_slave;
 	parent_class->can_reapply_change = can_reapply_change;
 	parent_class->reapply_connection = reapply_connection;
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_BOND_SKELETON,
-	                                        NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index 74689aef..c81a0253 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -30,8 +30,6 @@
 #include "nm-device-factory.h"
 #include "nm-core-internal.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Bridge.h"
-
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceBridge);
 
@@ -117,7 +115,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingBridge *s_bridge;
@@ -488,13 +486,31 @@ nm_device_bridge_init (NMDeviceBridge * self)
 	nm_assert (nm_device_is_master (NM_DEVICE (self)));
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_bridge = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_BRIDGE,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress", "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Carrier",   "b",  NM_DEVICE_CARRIER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Slaves",    "ao", NM_DEVICE_SLAVES),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_bridge_class_init (NMDeviceBridgeClass *klass)
 {
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_BRIDGE_SETTING_NAME, NM_LINK_TYPE_BRIDGE)
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_bridge);
+
 	parent_class->is_master = TRUE;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
 	parent_class->check_connection_compatible = check_connection_compatible;
@@ -511,10 +527,6 @@ nm_device_bridge_class_init (NMDeviceBridgeClass *klass)
 	parent_class->enslave_slave = enslave_slave;
 	parent_class->release_slave = release_slave;
 	parent_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_BRIDGE_SKELETON,
-	                                        NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-dummy.c b/src/devices/nm-device-dummy.c
index 085c44e6..f8bc8e75 100644
--- a/src/devices/nm-device-dummy.c
+++ b/src/devices/nm-device-dummy.c
@@ -28,8 +28,6 @@
 #include "nm-setting-dummy.h"
 #include "nm-core-internal.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Dummy.h"
-
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceDummy);
 
@@ -57,7 +55,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingDummy *s_dummy;
@@ -156,13 +154,29 @@ nm_device_dummy_init (NMDeviceDummy *self)
 {
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_dummy = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_DUMMY,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress", "s",  NM_DEVICE_HW_ADDRESS),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_dummy_class_init (NMDeviceDummyClass *klass)
 {
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_DUMMY)
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_dummy);
+
 	device_class->connection_type = NM_SETTING_DUMMY_SETTING_NAME;
 	device_class->complete_connection = complete_connection;
 	device_class->check_connection_compatible = check_connection_compatible;
@@ -171,10 +185,6 @@ nm_device_dummy_class_init (NMDeviceDummyClass *klass)
 	device_class->update_connection = update_connection;
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_DUMMY_SKELETON,
-	                                        NULL);
 }
 
 
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 3ca86e06..9b46545b 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -28,7 +28,6 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <errno.h>
-
 #include <libudev.h>
 
 #include "nm-device-private.h"
@@ -51,8 +50,7 @@
 #include "nm-device-factory.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
-
-#include "introspection/org.freedesktop.NetworkManager.Device.Wired.h"
+#include "nm-utils/nm-udev-utils.h"
 
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceEthernet);
@@ -550,7 +548,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 ();
+	config = nm_supplicant_config_new (FALSE, FALSE);
 
 	security = nm_connection_get_setting_802_1x (connection);
 	if (!nm_supplicant_config_add_setting_8021x (config, security, con_uuid, mtu, TRUE, error)) {
@@ -658,12 +656,9 @@ handle_auth_or_fail (NMDeviceEthernet *self,
                      NMActRequest *req,
                      gboolean new_secrets)
 {
-	NMDeviceEthernetPrivate *priv;
 	const char *setting_name;
 	NMConnection *applied_connection;
 
-	priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
-
 	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
 		return NM_ACT_STAGE_RETURN_FAILURE;
 
@@ -673,13 +668,14 @@ handle_auth_or_fail (NMDeviceEthernet *self,
 
 	applied_connection = nm_act_request_get_applied_connection (req);
 	setting_name = nm_connection_need_secrets (applied_connection, NULL);
-	if (setting_name) {
-		wired_secrets_get_secrets (self, setting_name,
-		                           NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
-		                             | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0));
-	} else
+	if (!setting_name) {
 		_LOGI (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
 
+	wired_secrets_get_secrets (self, setting_name,
+	                             NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+	                           | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0));
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
@@ -742,7 +738,7 @@ supplicant_interface_init (NMDeviceEthernet *self)
 		return FALSE;
 	}
 
-	/* Listen for it's state signals */
+	/* Listen for its state signals */
 	priv->supplicant.iface_state_id = g_signal_connect (priv->supplicant.iface,
 	                                                    NM_SUPPLICANT_INTERFACE_STATE,
 	                                                    G_CALLBACK (supplicant_iface_state_cb),
@@ -956,8 +952,22 @@ ppp_state_changed (NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_
 }
 
 static void
+ppp_ifindex_set (NMPPPManager *ppp_manager,
+                 int ifindex,
+                 const char *iface,
+                 gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+
+	if (!nm_device_set_ip_ifindex (device, ifindex)) {
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+	}
+}
+
+static void
 ppp_ip4_config (NMPPPManager *ppp_manager,
-                const char *iface,
                 NMIP4Config *config,
                 gpointer user_data)
 {
@@ -965,7 +975,6 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 
 	/* Ignore PPP IP4 events that come in after initial configuration */
 	if (nm_device_activate_ip4_state_in_conf (device)) {
-		nm_device_set_ip_iface (device, iface);
 		nm_device_activate_schedule_ip4_config_result (device, config);
 	}
 }
@@ -1012,6 +1021,9 @@ pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *out_
 	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_STATE_CHANGED,
 	                  G_CALLBACK (ppp_state_changed),
 	                  self);
+	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IFINDEX_SET,
+	                  G_CALLBACK (ppp_ifindex_set),
+	                  self);
 	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IP4_CONFIG,
 	                  G_CALLBACK (ppp_ip4_config),
 	                  self);
@@ -1361,7 +1373,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingWired *s_wired;
@@ -1425,7 +1437,9 @@ new_default_connection (NMDevice *self)
 	NMConnection *connection;
 	NMSettingsConnection *const*connections;
 	NMSetting *setting;
+	struct udev_device *dev;
 	const char *perm_hw_addr;
+	const char *uprop = "0";
 	gs_free char *defname = NULL;
 	gs_free char *uuid = NULL;
 	gs_free char *machine_id = NULL;
@@ -1470,6 +1484,26 @@ new_default_connection (NMDevice *self)
 	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)
+		uprop = udev_device_get_property_value (dev, "NM_AUTO_DEFAULT_LINK_LOCAL_ONLY");
+
+	if (nm_udev_utils_property_as_boolean (uprop)) {
+		setting = nm_setting_ip4_config_new ();
+		g_object_set (setting,
+		              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL,
+		              NULL);
+		nm_connection_add_setting (connection, setting);
+
+		setting = nm_setting_ip6_config_new ();
+		g_object_set (setting,
+		              NM_SETTING_IP_CONFIG_METHOD,  NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
+		              NM_SETTING_IP_CONFIG_MAY_FAIL, TRUE,
+		              NULL);
+		nm_connection_add_setting (connection, setting);
+	}
+
 	return connection;
 }
 
@@ -1701,10 +1735,28 @@ set_property (GObject *object, guint prop_id,
 	}
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_wired = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_WIRED,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",       "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("PermHwAddress",   "s",  NM_DEVICE_PERM_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Speed",           "u",  NM_DEVICE_ETHERNET_SPEED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("S390Subchannels", "as", NM_DEVICE_ETHERNET_S390_SUBCHANNELS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Carrier",         "b",  NM_DEVICE_CARRIER),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	g_type_class_add_private (object_class, sizeof (NMDeviceEthernetPrivate));
@@ -1716,6 +1768,8 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_wired);
+
 	parent_class->get_generic_capabilities = get_generic_capabilities;
 	parent_class->check_connection_compatible = check_connection_compatible;
 	parent_class->complete_connection = complete_connection;
@@ -1749,10 +1803,6 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 	                        G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_ETHERNET_SKELETON,
-	                                        NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-ethernet.h b/src/devices/nm-device-ethernet.h
index a50a7d85..22fae293 100644
--- a/src/devices/nm-device-ethernet.h
+++ b/src/devices/nm-device-ethernet.h
@@ -24,12 +24,12 @@
 
 #include "nm-device.h"
 
-#define NM_TYPE_DEVICE_ETHERNET			(nm_device_ethernet_get_type ())
-#define NM_DEVICE_ETHERNET(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernet))
-#define NM_DEVICE_ETHERNET_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetClass))
-#define NM_IS_DEVICE_ETHERNET(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_ETHERNET))
-#define NM_IS_DEVICE_ETHERNET_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_ETHERNET))
-#define NM_DEVICE_ETHERNET_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetClass))
+#define NM_TYPE_DEVICE_ETHERNET                 (nm_device_ethernet_get_type ())
+#define NM_DEVICE_ETHERNET(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernet))
+#define NM_DEVICE_ETHERNET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetClass))
+#define NM_IS_DEVICE_ETHERNET(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_ETHERNET))
+#define NM_IS_DEVICE_ETHERNET_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_ETHERNET))
+#define NM_DEVICE_ETHERNET_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetClass))
 
 #define NM_DEVICE_ETHERNET_SPEED "speed"
 #define NM_DEVICE_ETHERNET_S390_SUBCHANNELS "s390-subchannels"
@@ -37,12 +37,12 @@
 struct _NMDeviceEthernetPrivate;
 
 typedef struct {
-	NMDevice parent;
-	struct _NMDeviceEthernetPrivate *_priv;
+        NMDevice parent;
+        struct _NMDeviceEthernetPrivate *_priv;
 } NMDeviceEthernet;
 
 typedef struct {
-	NMDeviceClass parent;
+        NMDeviceClass parent;
 } NMDeviceEthernetClass;
 
 GType nm_device_ethernet_get_type (void);
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index 97f011c5..d8e30346 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -340,42 +340,16 @@ factories_list_unref (GSList *list)
 	g_slist_free_full (list, g_object_unref);
 }
 
-void
-nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc callback,
-                                          gpointer user_data)
+static void
+load_factories_from_dir (const char *dirname,
+                         NMDeviceFactoryManagerFactoryFunc callback,
+                         gpointer user_data)
 {
 	NMDeviceFactory *factory;
 	GError *error = NULL;
 	char **path, **paths;
 
-	g_return_if_fail (factories_by_link == NULL);
-	g_return_if_fail (factories_by_setting == NULL);
-
-	factories_by_link = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
-	factories_by_setting = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, (GDestroyNotify) factories_list_unref);
-
-#define _ADD_INTERNAL(get_type_fcn) \
-	G_STMT_START { \
-		GType get_type_fcn (void); \
-		_load_internal_factory (get_type_fcn (), \
-		                        callback, user_data); \
-	} G_STMT_END
-
-	_ADD_INTERNAL (nm_bond_device_factory_get_type);
-	_ADD_INTERNAL (nm_bridge_device_factory_get_type);
-	_ADD_INTERNAL (nm_dummy_device_factory_get_type);
-	_ADD_INTERNAL (nm_ethernet_device_factory_get_type);
-	_ADD_INTERNAL (nm_infiniband_device_factory_get_type);
-	_ADD_INTERNAL (nm_ip_tunnel_device_factory_get_type);
-	_ADD_INTERNAL (nm_macsec_device_factory_get_type);
-	_ADD_INTERNAL (nm_macvlan_device_factory_get_type);
-	_ADD_INTERNAL (nm_ppp_device_factory_get_type);
-	_ADD_INTERNAL (nm_tun_device_factory_get_type);
-	_ADD_INTERNAL (nm_veth_device_factory_get_type);
-	_ADD_INTERNAL (nm_vlan_device_factory_get_type);
-	_ADD_INTERNAL (nm_vxlan_device_factory_get_type);
-
-	paths = nm_utils_read_plugin_paths (NMPLUGINDIR, PLUGIN_PREFIX);
+	paths = nm_utils_read_plugin_paths (dirname, PLUGIN_PREFIX);
 	if (!paths)
 		return;
 
@@ -420,3 +394,36 @@ nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc call
 	g_strfreev (paths);
 }
 
+void
+nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc callback,
+                                          gpointer user_data)
+{
+	g_return_if_fail (factories_by_link == NULL);
+	g_return_if_fail (factories_by_setting == NULL);
+
+	factories_by_link = g_hash_table_new_full (nm_direct_hash, NULL, NULL, g_object_unref);
+	factories_by_setting = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, (GDestroyNotify) factories_list_unref);
+
+#define _ADD_INTERNAL(get_type_fcn) \
+	G_STMT_START { \
+		GType get_type_fcn (void); \
+		_load_internal_factory (get_type_fcn (), \
+		                        callback, user_data); \
+	} G_STMT_END
+
+	_ADD_INTERNAL (nm_bond_device_factory_get_type);
+	_ADD_INTERNAL (nm_bridge_device_factory_get_type);
+	_ADD_INTERNAL (nm_dummy_device_factory_get_type);
+	_ADD_INTERNAL (nm_ethernet_device_factory_get_type);
+	_ADD_INTERNAL (nm_infiniband_device_factory_get_type);
+	_ADD_INTERNAL (nm_ip_tunnel_device_factory_get_type);
+	_ADD_INTERNAL (nm_macsec_device_factory_get_type);
+	_ADD_INTERNAL (nm_macvlan_device_factory_get_type);
+	_ADD_INTERNAL (nm_ppp_device_factory_get_type);
+	_ADD_INTERNAL (nm_tun_device_factory_get_type);
+	_ADD_INTERNAL (nm_veth_device_factory_get_type);
+	_ADD_INTERNAL (nm_vlan_device_factory_get_type);
+	_ADD_INTERNAL (nm_vxlan_device_factory_get_type);
+
+	load_factories_from_dir (NMPLUGINDIR, callback, user_data);
+}
diff --git a/src/devices/nm-device-generic.c b/src/devices/nm-device-generic.c
index f6a670b2..27eaf8d6 100644
--- a/src/devices/nm-device-generic.c
+++ b/src/devices/nm-device-generic.c
@@ -26,8 +26,6 @@
 #include "platform/nm-platform.h"
 #include "nm-core-internal.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Generic.h"
-
 /*****************************************************************************/
 
 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
@@ -203,10 +201,25 @@ dispose (GObject *object)
 	G_OBJECT_CLASS (nm_device_generic_parent_class)->dispose (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_generic = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_GENERIC,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",       "s", NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("TypeDescription", "s", NM_DEVICE_GENERIC_TYPE_DESCRIPTION),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_generic_class_init (NMDeviceGenericClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_GENERIC_SETTING_NAME, NM_LINK_TYPE_ANY)
@@ -216,6 +229,8 @@ nm_device_generic_class_init (NMDeviceGenericClass *klass)
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_generic);
+
 	parent_class->realize_start_notify = realize_start_notify;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
 	parent_class->get_type_description = get_type_description;
@@ -229,8 +244,4 @@ nm_device_generic_class_init (NMDeviceGenericClass *klass)
 	                          G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_GENERIC_SKELETON,
-	                                        NULL);
 }
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index 09ad2855..781bbd69 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -32,8 +32,6 @@
 #include "nm-device-factory.h"
 #include "nm-core-internal.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Infiniband.h"
-
 #define NM_DEVICE_INFINIBAND_IS_PARTITION "is-partition"
 
 /*****************************************************************************/
@@ -177,7 +175,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingInfiniband *s_infiniband;
@@ -368,10 +366,25 @@ nm_device_infiniband_init (NMDeviceInfiniband * self)
 {
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_infiniband = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_INFINIBAND,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",       "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Carrier",         "b",  NM_DEVICE_CARRIER),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_infiniband_class_init (NMDeviceInfinibandClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_INFINIBAND_SETTING_NAME, NM_LINK_TYPE_INFINIBAND)
@@ -379,6 +392,8 @@ nm_device_infiniband_class_init (NMDeviceInfinibandClass *klass)
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_infiniband);
+
 	parent_class->create_and_realize = create_and_realize;
 	parent_class->unrealize = unrealize;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
@@ -396,10 +411,6 @@ nm_device_infiniband_class_init (NMDeviceInfinibandClass *klass)
 	                           G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_INFINIBAND_SKELETON,
-	                                        NULL);
 }
 
 /*****************************************************************************/
@@ -433,7 +444,7 @@ create_device (NMDeviceFactory *factory,
 	                                  NM_DEVICE_TYPE_DESC, "InfiniBand",
 	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_INFINIBAND,
 	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_INFINIBAND,
-	                                  /* XXX: Partition should probably be a different link type! */
+	                                  /* NOTE: Partition should probably be a different link type! */
 	                                  NM_DEVICE_INFINIBAND_IS_PARTITION, is_partition,
 	                                  NULL);
 }
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index af3cfe4c..59ca9ed5 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -37,8 +37,6 @@
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.IPTunnel.h"
-
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceIPTunnel);
 
@@ -55,6 +53,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceIPTunnel,
 	PROP_OUTPUT_KEY,
 	PROP_ENCAPSULATION_LIMIT,
 	PROP_FLOW_LABEL,
+	PROP_FLAGS,
 );
 
 typedef struct {
@@ -69,6 +68,7 @@ typedef struct {
 	char *output_key;
 	guint8 encap_limit;
 	guint32 flow_label;
+	NMIPTunnelFlags flags;
 } NMDeviceIPTunnelPrivate;
 
 struct _NMDeviceIPTunnel {
@@ -86,6 +86,30 @@ G_DEFINE_TYPE (NMDeviceIPTunnel, nm_device_ip_tunnel, NM_TYPE_DEVICE)
 
 /*****************************************************************************/
 
+static guint32
+ip6tnl_flags_setting_to_plat (NMIPTunnelFlags flags)
+{
+	G_STATIC_ASSERT (NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT    == IP6_TNL_F_IGN_ENCAP_LIMIT);
+	G_STATIC_ASSERT (NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_TCLASS    == IP6_TNL_F_USE_ORIG_TCLASS);
+	G_STATIC_ASSERT (NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FLOWLABEL == IP6_TNL_F_USE_ORIG_FLOWLABEL);
+	G_STATIC_ASSERT (NM_IP_TUNNEL_FLAG_IP6_MIP6_DEV           == IP6_TNL_F_MIP6_DEV);
+	G_STATIC_ASSERT (NM_IP_TUNNEL_FLAG_IP6_RCV_DSCP_COPY      == IP6_TNL_F_RCV_DSCP_COPY);
+	G_STATIC_ASSERT (NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FWMARK    == IP6_TNL_F_USE_ORIG_FWMARK);
+
+	/* NOTE: "accidentally", the numeric values correspond.
+	 *       For flags added in the future, that might no longer
+	 *       be the case. */
+	return flags & _NM_IP_TUNNEL_FLAG_ALL_IP6TNL;
+}
+
+static NMIPTunnelFlags
+ip6tnl_flags_plat_to_setting (guint32 flags)
+{
+	return flags & ((guint32) _NM_IP_TUNNEL_FLAG_ALL_IP6TNL);
+}
+
+/*****************************************************************************/
+
 static gboolean
 address_equal_pp (int family, const char *a, const char *b)
 {
@@ -129,6 +153,7 @@ update_properties_from_ifindex (NMDevice *device, int ifindex)
 	guint8 ttl = 0, tos = 0, encap_limit = 0;
 	gboolean pmtud = FALSE;
 	guint32 flow_label = 0;
+	NMIPTunnelFlags flags = NM_IP_TUNNEL_FLAG_NONE;
 	char *key;
 
 	if (ifindex <= 0) {
@@ -246,6 +271,7 @@ clear:
 		tos = lnk->tclass;
 		encap_limit = lnk->encap_limit;
 		flow_label = lnk->flow_label;
+		flags = ip6tnl_flags_plat_to_setting (lnk->flags);
 	} else
 		g_return_if_reached ();
 
@@ -307,6 +333,11 @@ out:
 		priv->flow_label = flow_label;
 		_notify (self, PROP_FLOW_LABEL);
 	}
+
+	if (priv->flags != flags) {
+		priv->flags = flags;
+		_notify (self, PROP_FLAGS);
+	}
 }
 
 static void
@@ -327,7 +358,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingIPTunnel *s_ip_tunnel;
@@ -685,6 +716,7 @@ create_and_realize (NMDevice *device,
 		lnk_ip6tnl.encap_limit = nm_setting_ip_tunnel_get_encapsulation_limit (s_ip_tunnel);
 		lnk_ip6tnl.flow_label = nm_setting_ip_tunnel_get_flow_label (s_ip_tunnel);
 		lnk_ip6tnl.proto = nm_setting_ip_tunnel_get_mode (s_ip_tunnel) == NM_IP_TUNNEL_MODE_IPIP6 ? IPPROTO_IPIP : IPPROTO_IPV6;
+		lnk_ip6tnl.flags = ip6tnl_flags_setting_to_plat (nm_setting_ip_tunnel_get_flags (s_ip_tunnel));
 
 		plerr = nm_platform_link_ip6tnl_add (nm_device_get_platform (device), iface, &lnk_ip6tnl, out_plink);
 		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
@@ -814,6 +846,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_FLOW_LABEL:
 		g_value_set_uint (value, priv->flow_label);
 		break;
+	case PROP_FLAGS:
+		g_value_set_uint (value, priv->flags);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -870,10 +905,35 @@ dispose (GObject *object)
 	G_OBJECT_CLASS (nm_device_ip_tunnel_parent_class)->dispose (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_ip_tunnel = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_IP_TUNNEL,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",               "u",  NM_DEVICE_IP_TUNNEL_MODE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Parent",             "o",  NM_DEVICE_PARENT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Local",              "s",  NM_DEVICE_IP_TUNNEL_LOCAL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Remote",             "s",  NM_DEVICE_IP_TUNNEL_REMOTE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Ttl",                "y",  NM_DEVICE_IP_TUNNEL_TTL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Tos",                "y",  NM_DEVICE_IP_TUNNEL_TOS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("PathMtuDiscovery",   "b",  NM_DEVICE_IP_TUNNEL_PATH_MTU_DISCOVERY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("InputKey",           "s",  NM_DEVICE_IP_TUNNEL_INPUT_KEY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("OutputKey",          "s",  NM_DEVICE_IP_TUNNEL_OUTPUT_KEY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("EncapsulationLimit", "y",  NM_DEVICE_IP_TUNNEL_ENCAPSULATION_LIMIT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("FlowLabel",          "u",  NM_DEVICE_IP_TUNNEL_FLOW_LABEL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Flags",              "u",  NM_DEVICE_IP_TUNNEL_FLAGS),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_ip_tunnel_class_init (NMDeviceIPTunnelClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->constructed = constructed;
@@ -881,6 +941,8 @@ nm_device_ip_tunnel_class_init (NMDeviceIPTunnelClass *klass)
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_ip_tunnel);
+
 	device_class->link_changed = link_changed;
 	device_class->can_reapply_change = can_reapply_change;
 	device_class->complete_connection = complete_connection;
@@ -959,11 +1021,13 @@ nm_device_ip_tunnel_class_init (NMDeviceIPTunnelClass *klass)
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
 
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+	obj_properties[PROP_FLAGS] =
+	     g_param_spec_uint (NM_DEVICE_IP_TUNNEL_FLAGS, "", "",
+	                        0, G_MAXUINT32, 0,
+	                        G_PARAM_READWRITE |
+	                        G_PARAM_STATIC_STRINGS);
 
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_IPTUNNEL_SKELETON,
-	                                        NULL);
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-ip-tunnel.h b/src/devices/nm-device-ip-tunnel.h
index 4bff6e33..1109ace4 100644
--- a/src/devices/nm-device-ip-tunnel.h
+++ b/src/devices/nm-device-ip-tunnel.h
@@ -41,9 +41,7 @@
 #define NM_DEVICE_IP_TUNNEL_OUTPUT_KEY          "output-key"
 #define NM_DEVICE_IP_TUNNEL_ENCAPSULATION_LIMIT "encapsulation-limit"
 #define NM_DEVICE_IP_TUNNEL_FLOW_LABEL          "flow-label"
-
-/* defined in the parent class, but exposed on D-Bus by the subclass. */
-#define NM_DEVICE_IP_TUNNEL_PARENT              NM_DEVICE_PARENT
+#define NM_DEVICE_IP_TUNNEL_FLAGS               "flags"
 
 typedef struct _NMDeviceIPTunnel NMDeviceIPTunnel;
 typedef struct _NMDeviceIPTunnelClass NMDeviceIPTunnelClass;
diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index 5ef8f938..895ea34f 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -33,8 +33,6 @@
 #include "supplicant/nm-supplicant-interface.h"
 #include "supplicant/nm-supplicant-config.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Macsec.h"
-
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceMacsec);
 
@@ -186,8 +184,12 @@ update_properties (NMDevice *device)
 		nm_device_parent_set_ifindex (device, props->parent_ifindex);
 
 #define CHECK_PROPERTY_CHANGED(field, prop) \
-	if (props->field != priv->props.field) \
-		_notify (self, prop)
+	G_STMT_START { \
+		if (priv->props.field != props->field) { \
+			priv->props.field = props->field; \
+			_notify (self, prop); \
+		} \
+	} G_STMT_END
 
 	CHECK_PROPERTY_CHANGED (sci, PROP_SCI);
 	CHECK_PROPERTY_CHANGED (cipher_suite, PROP_CIPHER_SUITE);
@@ -202,7 +204,6 @@ update_properties (NMDevice *device)
 	CHECK_PROPERTY_CHANGED (scb, PROP_SCB);
 	CHECK_PROPERTY_CHANGED (replay_protect, PROP_REPLAY_PROTECT);
 
-	priv->props = *props;
 	g_object_thaw_notify ((GObject *) device);
 }
 
@@ -222,7 +223,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 ();
+	config = nm_supplicant_config_new (FALSE, FALSE);
 
 	s_macsec = (NMSettingMacsec *)
 		nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_MACSEC);
@@ -477,12 +478,9 @@ handle_auth_or_fail (NMDeviceMacsec *self,
                      NMActRequest *req,
                      gboolean new_secrets)
 {
-	NMDeviceMacsecPrivate *priv;
 	const char *setting_name;
 	NMConnection *applied_connection;
 
-	priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
-
 	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
 		return NM_ACT_STAGE_RETURN_FAILURE;
 
@@ -492,13 +490,14 @@ handle_auth_or_fail (NMDeviceMacsec *self,
 
 	applied_connection = nm_act_request_get_applied_connection (req);
 	setting_name = nm_connection_need_secrets (applied_connection, NULL);
-	if (setting_name) {
-		macsec_secrets_get_secrets (self, setting_name,
-		                            NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
-		                             | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0));
-	} else
+	if (!setting_name) {
 		_LOGI (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
 
+	macsec_secrets_get_secrets (self, setting_name,
+	                              NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+	                            | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0));
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
@@ -812,10 +811,36 @@ dispose (GObject *object)
 	G_OBJECT_CLASS (nm_device_macsec_parent_class)->dispose (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_macsec = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_MACSEC,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Parent",        "o",  NM_DEVICE_PARENT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Sci",           "t",  NM_DEVICE_MACSEC_SCI),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("IcvLength",     "y",  NM_DEVICE_MACSEC_ICV_LENGTH),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("CipherSuite",   "t",  NM_DEVICE_MACSEC_CIPHER_SUITE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Window",        "u",  NM_DEVICE_MACSEC_WINDOW),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("EncodingSa",    "y",  NM_DEVICE_MACSEC_ENCODING_SA),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Validation",    "s",  NM_DEVICE_MACSEC_VALIDATION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Encrypt",       "b",  NM_DEVICE_MACSEC_ENCRYPT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Protect",       "b",  NM_DEVICE_MACSEC_PROTECT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("IncludeSci",    "b",  NM_DEVICE_MACSEC_INCLUDE_SCI),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Es",            "b",  NM_DEVICE_MACSEC_ES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Scb",           "b",  NM_DEVICE_MACSEC_SCB),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ReplayProtect", "b",  NM_DEVICE_MACSEC_REPLAY_PROTECT),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_macsec_class_init (NMDeviceMacsecClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_MACSEC)
@@ -823,6 +848,8 @@ nm_device_macsec_class_init (NMDeviceMacsecClass *klass)
 	object_class->get_property = get_property;
 	object_class->dispose = dispose;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_macsec);
+
 	parent_class->act_stage2_config = act_stage2_config;
 	parent_class->check_connection_compatible = check_connection_compatible;
 	parent_class->create_and_realize = create_and_realize;
@@ -886,10 +913,6 @@ nm_device_macsec_class_init (NMDeviceMacsecClass *klass)
 	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_MACSEC_SKELETON,
-	                                        NULL);
 }
 
 /*************************************************************/
diff --git a/src/devices/nm-device-macsec.h b/src/devices/nm-device-macsec.h
index 17b33bf5..23e9d2c9 100644
--- a/src/devices/nm-device-macsec.h
+++ b/src/devices/nm-device-macsec.h
@@ -43,9 +43,6 @@
 #define NM_DEVICE_MACSEC_SCB             "scb"
 #define NM_DEVICE_MACSEC_REPLAY_PROTECT  "replay-protect"
 
-/* defined in the parent class, but exposed on D-Bus by the subclass. */
-#define NM_DEVICE_MACSEC_PARENT          NM_DEVICE_PARENT
-
 typedef struct _NMDeviceMacsec NMDeviceMacsec;
 typedef struct _NMDeviceMacsecClass NMDeviceMacsecClass;
 
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index 2a461543..b8e748d6 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -36,8 +36,6 @@
 #include "nm-ip4-config.h"
 #include "nm-utils.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Macvlan.h"
-
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceMacvlan);
 
@@ -197,12 +195,17 @@ update_properties (NMDevice *device)
 	g_object_freeze_notify (object);
 
 	nm_device_parent_set_ifindex (device, plink->parent);
-	if (priv->props.mode != props->mode)
-		_notify (self, PROP_MODE);
-	if (priv->props.no_promisc != props->no_promisc)
-		_notify (self, PROP_NO_PROMISC);
 
-	priv->props = *props;
+#define CHECK_PROPERTY_CHANGED(field, prop) \
+	G_STMT_START { \
+		if (priv->props.field != props->field) { \
+			priv->props.field = props->field; \
+			_notify (self, prop); \
+		} \
+	} G_STMT_END
+
+	CHECK_PROPERTY_CHANGED (mode, PROP_MODE);
+	CHECK_PROPERTY_CHANGED (no_promisc, PROP_NO_PROMISC);
 
 	g_object_thaw_notify (object);
 }
@@ -331,7 +334,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingMacvlan *s_macvlan;
@@ -470,10 +473,27 @@ nm_device_macvlan_init (NMDeviceMacvlan *self)
 {
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_macvlan = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_MACVLAN,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Parent",    "o", NM_DEVICE_PARENT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",      "s", NM_DEVICE_MACVLAN_MODE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("NoPromisc", "b", NM_DEVICE_MACVLAN_NO_PROMISC),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Tab",       "b", NM_DEVICE_MACVLAN_TAP),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_macvlan_class_init (NMDeviceMacvlanClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_MACVLAN, NM_LINK_TYPE_MACVTAP)
@@ -481,6 +501,8 @@ nm_device_macvlan_class_init (NMDeviceMacvlanClass *klass)
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_macvlan);
+
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->complete_connection = complete_connection;
@@ -513,10 +535,6 @@ nm_device_macvlan_class_init (NMDeviceMacvlanClass *klass)
 	                           G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_MACVLAN_SKELETON,
-	                                        NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-macvlan.h b/src/devices/nm-device-macvlan.h
index e6d3a333..c7d4be7c 100644
--- a/src/devices/nm-device-macvlan.h
+++ b/src/devices/nm-device-macvlan.h
@@ -34,9 +34,6 @@
 #define NM_DEVICE_MACVLAN_NO_PROMISC "no-promisc"
 #define NM_DEVICE_MACVLAN_TAP        "tap"
 
-/* defined in the parent class, but exposed on D-Bus by the subclass. */
-#define NM_DEVICE_MACVLAN_PARENT     NM_DEVICE_PARENT
-
 typedef struct _NMDeviceMacvlan NMDeviceMacvlan;
 typedef struct _NMDeviceMacvlanClass NMDeviceMacvlanClass;
 
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
index 639ec44a..94df0cae 100644
--- a/src/devices/nm-device-ppp.c
+++ b/src/devices/nm-device-ppp.c
@@ -26,8 +26,6 @@
 #include "ppp/nm-ppp-manager-call.h"
 #include "ppp/nm-ppp-status.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Ppp.h"
-
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDevicePpp);
 
@@ -35,8 +33,7 @@ _LOG_DECLARE_SELF(NMDevicePpp);
 
 typedef struct _NMDevicePppPrivate {
 	NMPPPManager *ppp_manager;
-	NMIP4Config  *pending_ip4_config;
-	char         *pending_ifname;
+	NMIP4Config  *ip4_config;
 } NMDevicePppPrivate;
 
 struct _NMDevicePpp {
@@ -88,46 +85,52 @@ ppp_state_changed (NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_
 	case NM_PPP_STATUS_DEAD:
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED);
 		break;
-	case NM_PPP_STATUS_RUNNING:
-		nm_device_activate_schedule_stage3_ip_config_start (device);
-		break;
 	default:
 		break;
 	}
 }
 
 static void
+ppp_ifindex_set (NMPPPManager *ppp_manager,
+                 int ifindex,
+                 const char *iface,
+                 gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	gs_free char *old_name = NULL;
+
+	if (!nm_device_take_over_link (device, ifindex, &old_name)) {
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		return;
+	}
+
+	if (old_name)
+		nm_manager_remove_device (nm_manager_get (), old_name, NM_DEVICE_TYPE_PPP);
+
+	nm_device_activate_schedule_stage3_ip_config_start (device);
+}
+
+static void
 ppp_ip4_config (NMPPPManager *ppp_manager,
-                const char *iface,
                 NMIP4Config *config,
                 gpointer user_data)
 {
 	NMDevice *device = NM_DEVICE (user_data);
 	NMDevicePpp *self = NM_DEVICE_PPP (device);
 	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
-	gboolean renamed;
 
 	_LOGT (LOGD_DEVICE | LOGD_PPP, "received IPv4 config from pppd");
 
 	if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
 		if (nm_device_activate_ip4_state_in_conf (device)) {
-			if (!nm_device_take_over_link (device, iface, &renamed)) {
-				nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
-				                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-				return;
-			}
-			if (renamed)
-				nm_manager_remove_device (nm_manager_get (), iface, NM_DEVICE_TYPE_PPP);
-
 			nm_device_activate_schedule_ip4_config_result (device, config);
 			return;
 		}
 	} else {
-		if (priv->pending_ip4_config)
-			g_object_unref (priv->pending_ip4_config);
-		priv->pending_ip4_config = g_object_ref (config);
-		g_free (priv->pending_ifname);
-		priv->pending_ifname = g_strdup (iface);
+		if (priv->ip4_config)
+			g_object_unref (priv->ip4_config);
+		priv->ip4_config = g_object_ref (config);
 	}
 }
 
@@ -146,8 +149,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	s_pppoe = (NMSettingPppoe *) nm_device_get_applied_setting ((NMDevice *) self, NM_TYPE_SETTING_PPPOE);
 	g_return_val_if_fail (s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
 
-	g_clear_object (&priv->pending_ip4_config);
-	nm_clear_g_free (&priv->pending_ifname);
+	g_clear_object (&priv->ip4_config);
 
 	priv->ppp_manager = nm_ppp_manager_create (nm_setting_pppoe_get_parent (s_pppoe), &error);
 
@@ -175,6 +177,9 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_STATE_CHANGED,
 	                  G_CALLBACK (ppp_state_changed),
 	                  self);
+	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IFINDEX_SET,
+	                  G_CALLBACK (ppp_ifindex_set),
+	                  self);
 	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IP4_CONFIG,
 	                  G_CALLBACK (ppp_ip4_config),
 	                  self);
@@ -189,17 +194,12 @@ act_stage3_ip4_config_start (NMDevice *device,
 {
 	NMDevicePpp *self = NM_DEVICE_PPP (device);
 	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
-	gboolean renamed;
 
-	if (priv->pending_ip4_config) {
-		if  (!nm_device_take_over_link (device, priv->pending_ifname, &renamed))
-			return NM_ACT_STAGE_RETURN_FAILURE;
-		if (renamed)
-			nm_manager_remove_device (nm_manager_get (), priv->pending_ifname, NM_DEVICE_TYPE_PPP);
+	if (priv->ip4_config) {
 		if (out_config)
-			*out_config = g_steal_pointer (&priv->pending_ip4_config);
+			*out_config = g_steal_pointer (&priv->ip4_config);
 		else
-			g_clear_object (&priv->pending_ip4_config);
+			g_clear_object (&priv->ip4_config);
 		return NM_ACT_STAGE_RETURN_SUCCESS;
 	}
 
@@ -207,14 +207,6 @@ act_stage3_ip4_config_start (NMDevice *device,
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *self,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	return NM_ACT_STAGE_RETURN_IP_FAIL;
-}
-
 static gboolean
 create_and_realize (NMDevice *device,
                     NMConnection *connection,
@@ -263,33 +255,40 @@ dispose (GObject *object)
 	NMDevicePpp *self = NM_DEVICE_PPP (object);
 	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
 
-	g_clear_object (&priv->pending_ip4_config);
-	nm_clear_g_free (&priv->pending_ifname);
+	g_clear_object (&priv->ip4_config);
 
 	G_OBJECT_CLASS (nm_device_ppp_parent_class)->dispose (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_ppp = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_PPP,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_ppp_class_init (NMDevicePppClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_PPPOE_SETTING_NAME, NM_LINK_TYPE_PPP)
 
 	object_class->dispose = dispose;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_ppp);
+
 	parent_class->act_stage2_config = act_stage2_config;
 	parent_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	parent_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
 	parent_class->check_connection_compatible = check_connection_compatible;
 	parent_class->create_and_realize = create_and_realize;
 	parent_class->deactivate = deactivate;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_PPP_SKELETON,
-	                                        NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index f1486c54..b0d3ffa4 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -45,6 +45,8 @@ enum NMActStageReturn {
 
 NMSettings *nm_device_get_settings (NMDevice *self);
 
+gboolean nm_device_set_ip_ifindex (NMDevice *self, int ifindex);
+
 gboolean nm_device_set_ip_iface (NMDevice *self, const char *iface);
 
 void nm_device_activate_schedule_stage3_ip_config_start (NMDevice *device);
@@ -57,7 +59,7 @@ gboolean nm_device_bring_up (NMDevice *self, gboolean wait, gboolean *no_firmwar
 
 void nm_device_take_down (NMDevice *self, gboolean block);
 
-gboolean nm_device_take_over_link (NMDevice *self, const char *ifname, gboolean *renamed);
+gboolean nm_device_take_over_link (NMDevice *self, int ifindex, char **old_name);
 
 gboolean nm_device_hw_addr_set (NMDevice *device,
                                 const char *addr,
@@ -139,4 +141,4 @@ gboolean nm_device_match_hwaddr (NMDevice *device,
                                  NMConnection *connection,
                                  gboolean fail_if_no_hwaddr);
 
-#endif	/* NM_DEVICE_PRIVATE_H */
+#endif /* NM_DEVICE_PRIVATE_H */
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index a7d7c0bf..c3ce4b73 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
+#include <linux/if_tun.h>
 
 #include "nm-act-request.h"
 #include "nm-device-private.h"
@@ -34,8 +35,6 @@
 #include "nm-setting-tun.h"
 #include "nm-core-internal.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Tun.h"
-
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceTun);
 
@@ -51,8 +50,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceTun,
 );
 
 typedef struct {
-	NMPlatformTunProperties props;
-	const char *mode;
+	NMPlatformLnkTun props;
 } NMDeviceTunPrivate;
 
 struct _NMDeviceTun {
@@ -71,48 +69,62 @@ G_DEFINE_TYPE (NMDeviceTun, nm_device_tun, NM_TYPE_DEVICE)
 /*****************************************************************************/
 
 static void
-update_properties (NMDeviceTun *self)
+update_properties_from_struct (NMDeviceTun *self,
+                               const NMPlatformLnkTun *props)
 {
 	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
-	GObject *object = G_OBJECT (self);
-	NMPlatformTunProperties props;
+	const NMPlatformLnkTun props0 = { };
+
+	if (!props) {
+		/* allow passing %NULL to reset all properties. */
+		props = &props0;
+	}
+
+	g_object_freeze_notify (G_OBJECT (self));
+
+#define CHECK_PROPERTY_CHANGED_VALID(field, prop) \
+	G_STMT_START { \
+		if (   priv->props.field != props->field \
+		    || priv->props.field##_valid != props->field##_valid) { \
+			priv->props.field##_valid = props->field##_valid; \
+			priv->props.field = props->field; \
+			_notify (self, prop); \
+		} \
+	} G_STMT_END
+
+#define CHECK_PROPERTY_CHANGED(field, prop) \
+	G_STMT_START { \
+		if (priv->props.field != props->field) { \
+			priv->props.field = props->field; \
+			_notify (self, prop); \
+		} \
+	} G_STMT_END
+
+	CHECK_PROPERTY_CHANGED_VALID (owner, PROP_OWNER);
+	CHECK_PROPERTY_CHANGED_VALID (group, PROP_GROUP);
+	CHECK_PROPERTY_CHANGED (type, PROP_MODE);
+	CHECK_PROPERTY_CHANGED (pi, PROP_NO_PI);
+	CHECK_PROPERTY_CHANGED (vnet_hdr, PROP_VNET_HDR);
+	CHECK_PROPERTY_CHANGED (multi_queue, PROP_MULTI_QUEUE);
+
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+update_properties (NMDeviceTun *self)
+{
+	NMPlatformLnkTun props_storage;
+	const NMPlatformLnkTun *props = NULL;
 	int ifindex;
 
 	ifindex = nm_device_get_ifindex (NM_DEVICE (self));
-	if (ifindex > 0) {
-		if (!nm_platform_link_tun_get_properties (nm_device_get_platform (NM_DEVICE (self)), ifindex, &props)) {
-			_LOGD (LOGD_DEVICE, "tun-properties: cannot loading tun properties from platform for ifindex %d", ifindex);
-			ifindex = 0;
-		} else if (g_strcmp0 (priv->mode, props.mode) != 0) {
-			/* if the mode differs, we ignore what we loaded. A NMDeviceTun cannot
-			 * change the mode after construction. */
-			_LOGD (LOGD_DEVICE, "tun-properties: loading tun properties yielded tun-mode %s%s%s, but %s%s%s expected (ifindex %d)",
-			       NM_PRINT_FMT_QUOTE_STRING (props.mode),
-			       NM_PRINT_FMT_QUOTE_STRING (priv->mode),
-			       ifindex);
-			ifindex = 0;
-		}
-	} else
-		_LOGD (LOGD_DEVICE, "tun-properties: ignore loading properties due to missing ifindex");
-	if (ifindex <= 0)
-		memset (&props, 0, sizeof (props));
-
-	g_object_freeze_notify (object);
-
-	if (priv->props.owner != props.owner)
-		_notify (self, PROP_OWNER);
-	if (priv->props.group != props.group)
-		_notify (self, PROP_GROUP);
-	if (priv->props.no_pi != props.no_pi)
-		_notify (self, PROP_NO_PI);
-	if (priv->props.vnet_hdr != props.vnet_hdr)
-		_notify (self, PROP_VNET_HDR);
-	if (priv->props.multi_queue != props.multi_queue)
-		_notify (self, PROP_MULTI_QUEUE);
-
-	memcpy (&priv->props, &props, sizeof (NMPlatformTunProperties));
-
-	g_object_thaw_notify (object);
+	if (   ifindex > 0
+	    && nm_platform_link_tun_get_properties (nm_device_get_platform (NM_DEVICE (self)),
+	                                            ifindex,
+	                                            &props_storage))
+		props = &props_storage;
+
+	update_properties_from_struct (self, props);
 }
 
 static NMDeviceCapabilities
@@ -133,7 +145,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingTun *s_tun;
@@ -157,61 +169,57 @@ complete_connection (NMDevice *device,
 	return TRUE;
 }
 
-static int
-tun_mode_from_string (const char *string)
-{
-	if (!g_strcmp0 (string, "tap"))
-		return NM_SETTING_TUN_MODE_TAP;
-	else
-		return NM_SETTING_TUN_MODE_TUN;
-}
-
 static void
 update_connection (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceTun *self = NM_DEVICE_TUN (device);
-	NMSettingTun *s_tun = nm_connection_get_setting_tun (connection);
-	NMPlatformTunProperties props;
+	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
+	NMSettingTun *s_tun;
 	NMSettingTunMode mode;
-	gint64 user, group;
-	char *str;
+	char s_buf[100];
+	const char *str;
 
-	if (!s_tun) {
-		s_tun = (NMSettingTun *) nm_setting_tun_new ();
-		nm_connection_add_setting (connection, (NMSetting *) s_tun);
-	}
+	/* Note: since we read tun properties from sysctl for older kernels,
+	 *       we don't get proper change notifications. Make sure that all our
+	 *       tun properties are up to date at this point. We should not do this,
+	 *       if we would entirely rely on netlink events. */
+	update_properties (NM_DEVICE_TUN (device));
 
-	if (!nm_platform_link_tun_get_properties (nm_device_get_platform (device), nm_device_get_ifindex (device), &props)) {
-		_LOGW (LOGD_PLATFORM, "failed to get TUN interface info while updating connection.");
+	switch (priv->props.type) {
+	case IFF_TUN: mode = NM_SETTING_TUN_MODE_TUN; break;
+	case IFF_TAP: mode = NM_SETTING_TUN_MODE_TAP; break;
+	default:
+		/* Huh? */
 		return;
 	}
 
-	mode = tun_mode_from_string (props.mode);
+	s_tun = nm_connection_get_setting_tun (connection);
+	if (!s_tun) {
+		s_tun = (NMSettingTun *) nm_setting_tun_new ();
+		nm_connection_add_setting (connection, (NMSetting *) s_tun);
+	}
 
 	if (mode != nm_setting_tun_get_mode (s_tun))
-		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_MODE, mode, NULL);
-
-	user = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_owner (s_tun), 10, 0, G_MAXINT32, -1);
-	group = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_group (s_tun), 10, 0, G_MAXINT32, -1);
+		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_MODE, (guint) mode, NULL);
 
-	if (props.owner != user) {
-		str = props.owner >= 0 ? g_strdup_printf ("%" G_GINT32_FORMAT, (gint32) props.owner) : NULL;
+	str = priv->props.owner_valid
+	      ? nm_sprintf_buf (s_buf, "%" G_GINT32_FORMAT, priv->props.owner)
+	      : NULL;
+	if (!nm_streq0 (str, nm_setting_tun_get_owner (s_tun)))
 		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_OWNER, str, NULL);
-		g_free (str);
-	}
 
-	if (props.group != group) {
-		str = props.group >= 0 ? g_strdup_printf ("%" G_GINT32_FORMAT, (gint32) props.group) : NULL;
+	str = priv->props.group_valid
+	      ? nm_sprintf_buf (s_buf, "%" G_GINT32_FORMAT, priv->props.group)
+	      : NULL;
+	if (!nm_streq0 (str, nm_setting_tun_get_group (s_tun)))
 		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_GROUP, str, NULL);
-		g_free (str);
-	}
 
-	if ((!props.no_pi) != nm_setting_tun_get_pi (s_tun))
-		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_PI, !props.no_pi, NULL);
-	if (props.vnet_hdr != nm_setting_tun_get_vnet_hdr (s_tun))
-		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_VNET_HDR, props.vnet_hdr, NULL);
-	if (props.multi_queue != nm_setting_tun_get_multi_queue (s_tun))
-		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_MULTI_QUEUE, props.multi_queue, NULL);
+	if (priv->props.pi != nm_setting_tun_get_pi (s_tun))
+		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_PI, (gboolean) priv->props.pi, NULL);
+	if (priv->props.vnet_hdr != nm_setting_tun_get_vnet_hdr (s_tun))
+		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_VNET_HDR, (gboolean) priv->props.vnet_hdr, NULL);
+	if (priv->props.multi_queue != nm_setting_tun_get_multi_queue (s_tun))
+		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_MULTI_QUEUE, (gboolean) priv->props.multi_queue, NULL);
 }
 
 static gboolean
@@ -222,23 +230,42 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
+	NMPlatformLnkTun props = { };
 	NMPlatformError plerr;
 	NMSettingTun *s_tun;
-	gint64 user, group;
+	gint64 owner, group;
 
 	s_tun = nm_connection_get_setting_tun (connection);
-	g_assert (s_tun);
+	g_return_val_if_fail (s_tun, FALSE);
+
+	switch (nm_setting_tun_get_mode (s_tun)) {
+	case NM_SETTING_TUN_MODE_TAP: props.type = IFF_TAP; break;
+	case NM_SETTING_TUN_MODE_TUN: props.type = IFF_TUN; break;
+	default:
+		g_return_val_if_reached (FALSE);
+	}
 
-	user = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_owner (s_tun), 10, 0, G_MAXINT32, -1);
+	owner = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_owner (s_tun), 10, 0, G_MAXINT32, -1);
+	if (owner != -1) {
+		props.owner_valid = TRUE;
+		props.owner = owner;
+	}
 	group = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_group (s_tun), 10, 0, G_MAXINT32, -1);
+	if (group != -1) {
+		props.group_valid = TRUE;
+		props.group = group;
+	}
 
-	plerr = nm_platform_link_tun_add (nm_device_get_platform (device), iface,
-	                                  nm_setting_tun_get_mode (s_tun) == NM_SETTING_TUN_MODE_TAP,
-	                                  user, group,
-	                                  nm_setting_tun_get_pi (s_tun),
-	                                  nm_setting_tun_get_vnet_hdr (s_tun),
-	                                  nm_setting_tun_get_multi_queue (s_tun),
-	                                  out_plink);
+	props.pi = nm_setting_tun_get_pi (s_tun);
+	props.vnet_hdr = nm_setting_tun_get_vnet_hdr (s_tun);
+	props.multi_queue = nm_setting_tun_get_multi_queue (s_tun);
+	props.persist = TRUE;
+
+	plerr = nm_platform_link_tun_add (nm_device_get_platform (device),
+	                                  iface,
+	                                  &props,
+	                                  out_plink,
+	                                  NULL);
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create TUN/TAP interface '%s' for '%s': %s",
@@ -252,13 +279,22 @@ create_and_realize (NMDevice *device,
 }
 
 static gboolean
+_same_og (const char *str, gboolean og_valid, guint32 og_num)
+{
+	gint64 v;
+
+	v = _nm_utils_ascii_str_to_int64 (str, 10, 0, G_MAXINT32, -1);
+	return    (!og_valid && (           v  == (gint64) -1))
+	       || ( og_valid && (((guint32) v) == og_num     ));
+}
+
+static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceTun *self = NM_DEVICE_TUN (device);
 	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
 	NMSettingTunMode mode;
 	NMSettingTun *s_tun;
-	gint64 user, group;
 
 	if (!NM_DEVICE_CLASS (nm_device_tun_parent_class)->check_connection_compatible (device, connection))
 		return FALSE;
@@ -268,18 +304,21 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		return FALSE;
 
 	if (nm_device_is_real (device)) {
-		mode = tun_mode_from_string (priv->mode);
-		if (mode != nm_setting_tun_get_mode (s_tun))
+		switch (priv->props.type) {
+		case IFF_TUN: mode = NM_SETTING_TUN_MODE_TUN; break;
+		case IFF_TAP: mode = NM_SETTING_TUN_MODE_TAP; break;
+		default:
+			/* Huh? */
 			return FALSE;
+		}
 
-		user = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_owner (s_tun), 10, 0, G_MAXINT32, -1);
-		group = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_group (s_tun), 10, 0, G_MAXINT32, -1);
-
-		if (user != priv->props.owner)
+		if (mode != nm_setting_tun_get_mode (s_tun))
+			return FALSE;
+		if (!_same_og (nm_setting_tun_get_owner (s_tun), priv->props.owner_valid, priv->props.owner))
 			return FALSE;
-		if (group != priv->props.group)
+		if (!_same_og (nm_setting_tun_get_group (s_tun), priv->props.group_valid, priv->props.group))
 			return FALSE;
-		if (nm_setting_tun_get_pi (s_tun) == priv->props.no_pi)
+		if (nm_setting_tun_get_pi (s_tun) != priv->props.pi)
 			return FALSE;
 		if (nm_setting_tun_get_vnet_hdr (s_tun) != priv->props.vnet_hdr)
 			return FALSE;
@@ -302,7 +341,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		return ret;
 
 	/* Nothing to do for TUN devices */
-	if (g_strcmp0 (priv->mode, "tap"))
+	if (priv->props.type == IFF_TUN)
 		return NM_ACT_STAGE_RETURN_SUCCESS;
 
 	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
@@ -314,16 +353,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 static void
 unrealize_notify (NMDevice *device)
 {
-	NMDeviceTun *self = NM_DEVICE_TUN (device);
-	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
-	guint i;
-
 	NM_DEVICE_CLASS (nm_device_tun_parent_class)->unrealize_notify (device);
-
-	memset (&priv->props, 0, sizeof (NMPlatformTunProperties));
-
-	for (i = 1; i < _PROPERTY_ENUMS_LAST; i++)
-		g_object_notify_by_pspec ((GObject *) self, obj_properties[i]);
+	update_properties_from_struct (NM_DEVICE_TUN (device), NULL);
 }
 
 /*****************************************************************************/
@@ -334,19 +365,25 @@ get_property (GObject *object, guint prop_id,
 {
 	NMDeviceTun *self = NM_DEVICE_TUN (object);
 	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
+	const char *s;
 
 	switch (prop_id) {
 	case PROP_OWNER:
-		g_value_set_int64 (value, priv->props.owner);
+		g_value_set_int64 (value, priv->props.owner_valid ? (gint64) priv->props.owner : (gint64) -1);
 		break;
 	case PROP_GROUP:
-		g_value_set_int64 (value, priv->props.group);
+		g_value_set_int64 (value, priv->props.group_valid ? (gint64) priv->props.group : (gint64) -1);
 		break;
 	case PROP_MODE:
-		g_value_set_string (value, priv->mode);
+		switch (priv->props.type) {
+		case IFF_TUN: s = "tun"; break;
+		case IFF_TAP: s = "tap"; break;
+		default:      s = NULL;  break;
+		}
+		g_value_set_static_string (value, s);
 		break;
 	case PROP_NO_PI:
-		g_value_set_boolean (value, priv->props.no_pi);
+		g_value_set_boolean (value, !priv->props.pi);
 		break;
 	case PROP_VNET_HDR:
 		g_value_set_boolean (value, priv->props.vnet_hdr);
@@ -360,33 +397,6 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMDeviceTun *self = NM_DEVICE_TUN (object);
-	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
-	const char *str;
-
-	switch (prop_id) {
-	case PROP_MODE:
-		/* construct-only */
-		str = g_value_get_string (value);
-
-		/* mode is G_PARAM_STATIC_STRINGS */
-		if (g_strcmp0 (str, "tun") == 0)
-			priv->mode = "tun";
-		else if (g_strcmp0 (str, "tap") == 0)
-			priv->mode = "tap";
-		else
-			g_return_if_fail (FALSE);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
 /*****************************************************************************/
 
 static void
@@ -394,16 +404,37 @@ nm_device_tun_init (NMDeviceTun *self)
 {
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_tun = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_TUN,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Owner",      "x",  NM_DEVICE_TUN_OWNER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Group",      "x",  NM_DEVICE_TUN_GROUP),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",       "s",  NM_DEVICE_TUN_MODE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("NoPi",       "b",  NM_DEVICE_TUN_NO_PI),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("VnetHdr",    "b",  NM_DEVICE_TUN_VNET_HDR),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("MultiQueue", "b",  NM_DEVICE_TUN_MULTI_QUEUE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",  "s",  NM_DEVICE_HW_ADDRESS),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_tun_class_init (NMDeviceTunClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_TUN, NM_LINK_TYPE_TAP)
+	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_TUN)
 
 	object_class->get_property = get_property;
-	object_class->set_property = set_property;
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_tun);
 
 	device_class->connection_type = NM_SETTING_TUN_SETTING_NAME;
 	device_class->link_changed = link_changed;
@@ -428,9 +459,8 @@ nm_device_tun_class_init (NMDeviceTunClass *klass)
 
 	obj_properties[PROP_MODE] =
 	     g_param_spec_string (NM_DEVICE_TUN_MODE, "", "",
-	                          "tun",
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
+	                          NULL,
+	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_NO_PI] =
 	     g_param_spec_boolean (NM_DEVICE_TUN_NO_PI, "", "",
@@ -448,10 +478,6 @@ nm_device_tun_class_init (NMDeviceTunClass *klass)
 	                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_TUN_SKELETON,
-	                                        NULL);
 }
 
 
@@ -467,42 +493,19 @@ create_device (NMDeviceFactory *factory,
                NMConnection *connection,
                gboolean *out_ignore)
 {
-	NMSettingTun *s_tun;
-	NMLinkType link_type = NM_LINK_TYPE_UNKNOWN;
-	const char *mode;
-
-	if (plink) {
-		link_type = plink->type;
-	} else if (connection) {
-		s_tun = nm_connection_get_setting_tun (connection);
-		if (!s_tun)
-			return NULL;
-		switch (nm_setting_tun_get_mode (s_tun)) {
-		case NM_SETTING_TUN_MODE_TUN:
-			link_type = NM_LINK_TYPE_TUN;
-			break;
-		case NM_SETTING_TUN_MODE_TAP:
-			link_type = NM_LINK_TYPE_TAP;
-			break;
-		case NM_SETTING_TUN_MODE_UNKNOWN:
-			g_return_val_if_reached (NULL);
-		}
-	}
-
-	g_return_val_if_fail (link_type != NM_LINK_TYPE_UNKNOWN, NULL);
-	mode = link_type == NM_LINK_TYPE_TUN ? "tun" : "tap";
+	g_return_val_if_fail (!plink || plink->type == NM_LINK_TYPE_TUN, NULL);
+	g_return_val_if_fail (!connection || nm_streq0 (nm_connection_get_connection_type (connection), NM_SETTING_TUN_SETTING_NAME), NULL);
 
 	return (NMDevice *) g_object_new (NM_TYPE_DEVICE_TUN,
 	                                  NM_DEVICE_IFACE, iface,
 	                                  NM_DEVICE_TYPE_DESC, "Tun",
 	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_TUN,
-	                                  NM_DEVICE_LINK_TYPE, link_type,
-	                                  NM_DEVICE_TUN_MODE, mode,
+	                                  NM_DEVICE_LINK_TYPE, (guint) NM_LINK_TYPE_TUN,
 	                                  NULL);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL (TUN, Tun, tun,
-	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_TUN, NM_LINK_TYPE_TAP)
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_TUN)
 	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_TUN_SETTING_NAME),
 	factory_class->create_device = create_device;
 );
diff --git a/src/devices/nm-device-veth.c b/src/devices/nm-device-veth.c
index a8c4bcc8..186173eb 100644
--- a/src/devices/nm-device-veth.c
+++ b/src/devices/nm-device-veth.c
@@ -30,8 +30,6 @@
 #include "platform/nm-platform.h"
 #include "nm-device-factory.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Veth.h"
-
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceVeth);
 
@@ -101,11 +99,18 @@ nm_device_veth_init (NMDeviceVeth *self)
 }
 
 static void
-notify (GObject *object, GParamSpec *pspec)
+parent_changed_notify (NMDevice *device,
+                       int old_ifindex,
+                       NMDevice *old_parent,
+                       int new_ifindex,
+                       NMDevice *new_parent)
 {
-	if (nm_streq (pspec->name, NM_DEVICE_PARENT))
-		_notify (NM_DEVICE_VETH (object), PROP_PEER);
-	G_OBJECT_CLASS (nm_device_veth_parent_class)->notify (object, pspec);
+	NM_DEVICE_CLASS (nm_device_veth_parent_class)->parent_changed_notify (device,
+	                                                                      old_ifindex,
+	                                                                      old_parent,
+	                                                                      new_ifindex,
+	                                                                      new_parent);
+	_notify (NM_DEVICE_VETH (device), PROP_PEER);
 }
 
 static void
@@ -120,7 +125,7 @@ get_property (GObject *object, guint prop_id,
 		peer = nm_device_parent_get_device (NM_DEVICE (self));
 		if (peer && !NM_IS_DEVICE_VETH (peer))
 			peer = NULL;
-		nm_utils_g_value_set_object_path (value, peer);
+		nm_dbus_utils_g_value_set_object_path (value, peer);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -128,19 +133,35 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_veth = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_VETH,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Peer", "o", NM_DEVICE_VETH_PEER),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_veth_class_init (NMDeviceVethClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_VETH)
 
 	object_class->get_property = get_property;
-	object_class->notify = notify;
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_veth);
 
 	device_class->can_unmanaged_external_down = can_unmanaged_external_down;
 	device_class->link_changed = link_changed;
+	device_class->parent_changed_notify = parent_changed_notify;
 
 	obj_properties[PROP_PEER] =
 	    g_param_spec_string (NM_DEVICE_VETH_PEER, "", "",
@@ -149,10 +170,6 @@ nm_device_veth_class_init (NMDeviceVethClass *klass)
 	                         G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_VETH_SKELETON,
-	                                        NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index 81512335..ae6a0f36 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -37,8 +37,6 @@
 #include "nm-core-internal.h"
 #include "platform/nmp-object.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Vlan.h"
-
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceVlan);
 
@@ -134,7 +132,7 @@ parent_hwaddr_maybe_changed (NMDevice *parent,
 		 */
 		s_ip6 = nm_connection_get_setting_ip6_config (connection);
 		if (s_ip6)
-			nm_device_reactivate_ip6_config (NM_DEVICE (self), s_ip6, s_ip6, FALSE);
+			nm_device_reactivate_ip6_config (NM_DEVICE (self), s_ip6, s_ip6);
 	}
 }
 
@@ -381,7 +379,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingVlan *s_vlan;
@@ -577,16 +575,35 @@ nm_device_vlan_init (NMDeviceVlan * self)
 {
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_vlan = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_VLAN,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Carrier",   "b", NM_DEVICE_CARRIER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Parent",    "o", NM_DEVICE_PARENT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("VlanId",    "u", NM_DEVICE_VLAN_ID),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_vlan_class_init (NMDeviceVlanClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_VLAN_SETTING_NAME, NM_LINK_TYPE_VLAN)
 
 	object_class->get_property = get_property;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_vlan);
+
 	parent_class->create_and_realize = create_and_realize;
 	parent_class->link_changed = link_changed;
 	parent_class->unrealize_notify = unrealize_notify;
@@ -608,10 +625,6 @@ nm_device_vlan_class_init (NMDeviceVlanClass *klass)
 	                        | G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_VLAN_SKELETON,
-	                                        NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-vlan.h b/src/devices/nm-device-vlan.h
index 0d788940..375e8fa4 100644
--- a/src/devices/nm-device-vlan.h
+++ b/src/devices/nm-device-vlan.h
@@ -36,12 +36,8 @@ typedef enum {
 	NM_VLAN_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/
 } NMVlanError;
 
-/* D-Bus exported properties */
 #define NM_DEVICE_VLAN_ID         "vlan-id"
 
-/* defined in the parent class, but exposed on D-Bus by the subclass. */
-#define NM_DEVICE_VLAN_PARENT     NM_DEVICE_PARENT
-
 typedef struct _NMDeviceVlan NMDeviceVlan;
 typedef struct _NMDeviceVlanClass NMDeviceVlanClass;
 
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index d9efe840..e1252223 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -35,8 +35,6 @@
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Vxlan.h"
-
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceVxlan);
 
@@ -97,42 +95,40 @@ update_properties (NMDevice *device)
 
 	if (priv->props.parent_ifindex != props->parent_ifindex)
 		nm_device_parent_set_ifindex (device, props->parent_ifindex);
-	if (priv->props.id != props->id)
-		_notify (self, PROP_ID);
-	if (priv->props.local != props->local)
-		_notify (self, PROP_LOCAL);
-	if (memcmp (&priv->props.local6, &props->local6, sizeof (props->local6)) != 0)
-		_notify (self, PROP_LOCAL);
-	if (priv->props.group != props->group)
-		_notify (self, PROP_GROUP);
-	if (memcmp (&priv->props.group6, &props->group6, sizeof (props->group6)) != 0)
-		_notify (self, PROP_GROUP);
-	if (priv->props.tos != props->tos)
-		_notify (self, PROP_TOS);
-	if (priv->props.ttl != props->ttl)
-		_notify (self, PROP_TTL);
-	if (priv->props.learning != props->learning)
-		_notify (self, PROP_LEARNING);
-	if (priv->props.ageing != props->ageing)
-		_notify (self, PROP_AGEING);
-	if (priv->props.limit != props->limit)
-		_notify (self, PROP_LIMIT);
-	if (priv->props.src_port_min != props->src_port_min)
-		_notify (self, PROP_SRC_PORT_MIN);
-	if (priv->props.src_port_max != props->src_port_max)
-		_notify (self, PROP_SRC_PORT_MAX);
-	if (priv->props.dst_port != props->dst_port)
-		_notify (self, PROP_DST_PORT);
-	if (priv->props.proxy != props->proxy)
-		_notify (self, PROP_PROXY);
-	if (priv->props.rsc != props->rsc)
-		_notify (self, PROP_RSC);
-	if (priv->props.l2miss != props->l2miss)
-		_notify (self, PROP_L2MISS);
-	if (priv->props.l3miss != props->l3miss)
-		_notify (self, PROP_L3MISS);
-
-	priv->props = *props;
+
+#define CHECK_PROPERTY_CHANGED(field, prop) \
+	G_STMT_START { \
+		if (priv->props.field != props->field) { \
+			priv->props.field = props->field; \
+			_notify (self, prop); \
+		} \
+	} G_STMT_END
+
+#define CHECK_PROPERTY_CHANGED_IN6ADDR(field, prop) \
+	G_STMT_START { \
+		if (memcmp (&priv->props.field, &props->field, sizeof (props->field)) != 0) { \
+			priv->props.field = props->field; \
+			_notify (self, prop); \
+		} \
+	} G_STMT_END
+
+	CHECK_PROPERTY_CHANGED (id, PROP_ID);
+	CHECK_PROPERTY_CHANGED (local, PROP_LOCAL);
+	CHECK_PROPERTY_CHANGED_IN6ADDR (local6, PROP_LOCAL);
+	CHECK_PROPERTY_CHANGED (group, PROP_GROUP);
+	CHECK_PROPERTY_CHANGED_IN6ADDR (group6, PROP_GROUP);
+	CHECK_PROPERTY_CHANGED (tos, PROP_TOS);
+	CHECK_PROPERTY_CHANGED (ttl, PROP_TTL);
+	CHECK_PROPERTY_CHANGED (learning, PROP_LEARNING);
+	CHECK_PROPERTY_CHANGED (ageing, PROP_AGEING);
+	CHECK_PROPERTY_CHANGED (limit, PROP_LIMIT);
+	CHECK_PROPERTY_CHANGED (src_port_min, PROP_SRC_PORT_MIN);
+	CHECK_PROPERTY_CHANGED (src_port_max, PROP_SRC_PORT_MAX);
+	CHECK_PROPERTY_CHANGED (dst_port, PROP_DST_PORT);
+	CHECK_PROPERTY_CHANGED (proxy, PROP_PROXY);
+	CHECK_PROPERTY_CHANGED (rsc, PROP_RSC);
+	CHECK_PROPERTY_CHANGED (l2miss, PROP_L2MISS);
+	CHECK_PROPERTY_CHANGED (l3miss, PROP_L3MISS);
 
 	g_object_thaw_notify (object);
 }
@@ -318,7 +314,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingVxlan *s_vxlan;
@@ -545,16 +541,48 @@ nm_device_vxlan_init (NMDeviceVxlan *self)
 {
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_vxlan = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_VXLAN,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Parent",     "o", NM_DEVICE_PARENT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",  "s", NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Id",         "u", NM_DEVICE_VXLAN_ID),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Group",      "s", NM_DEVICE_VXLAN_GROUP),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Local",      "s", NM_DEVICE_VXLAN_LOCAL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Tos",        "y", NM_DEVICE_VXLAN_TOS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Ttl",        "y", NM_DEVICE_VXLAN_TTL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Learning",   "b", NM_DEVICE_VXLAN_LEARNING),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Ageing",     "u", NM_DEVICE_VXLAN_AGEING),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Limit",      "u", NM_DEVICE_VXLAN_LIMIT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("DstPort",    "q", NM_DEVICE_VXLAN_DST_PORT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("SrcPortMin", "q", NM_DEVICE_VXLAN_SRC_PORT_MIN),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("SrcPortMax", "q", NM_DEVICE_VXLAN_SRC_PORT_MAX),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Proxy",      "b", NM_DEVICE_VXLAN_PROXY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Rsc",        "b", NM_DEVICE_VXLAN_RSC),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("L2miss",     "b", NM_DEVICE_VXLAN_L2MISS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("L3miss",     "b", NM_DEVICE_VXLAN_L3MISS),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_vxlan_class_init (NMDeviceVxlanClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_VXLAN)
 
 	object_class->get_property = get_property;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_vxlan);
+
 	device_class->link_changed = link_changed;
 	device_class->unrealize_notify = unrealize_notify;
 	device_class->connection_type = NM_SETTING_VXLAN_SETTING_NAME;
@@ -657,10 +685,6 @@ nm_device_vxlan_class_init (NMDeviceVxlanClass *klass)
 	                           G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_VXLAN_SKELETON,
-	                                        NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-vxlan.h b/src/devices/nm-device-vxlan.h
index 6f0102dc..511b7156 100644
--- a/src/devices/nm-device-vxlan.h
+++ b/src/devices/nm-device-vxlan.h
@@ -46,9 +46,6 @@
 #define NM_DEVICE_VXLAN_L2MISS       "l2miss"
 #define NM_DEVICE_VXLAN_L3MISS       "l3miss"
 
-/* defined in the parent class, but exposed on D-Bus by the subclass. */
-#define NM_DEVICE_VXLAN_PARENT       NM_DEVICE_PARENT
-
 typedef struct _NMDeviceVxlan NMDeviceVxlan;
 typedef struct _NMDeviceVxlanClass NMDeviceVxlanClass;
 
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index b8828d14..815de29a 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -34,6 +34,7 @@
 #include <arpa/inet.h>
 #include <fcntl.h>
 #include <linux/if_addr.h>
+#include <linux/if_arp.h>
 #include <linux/rtnetlink.h>
 #include <linux/pkt_sched.h>
 
@@ -48,6 +49,7 @@
 #include "ndisc/nm-ndisc.h"
 #include "ndisc/nm-lndp-ndisc.h"
 #include "dhcp/nm-dhcp-manager.h"
+#include "dhcp/nm-dhcp-utils.h"
 #include "nm-act-request.h"
 #include "nm-proxy-config.h"
 #include "nm-ip4-config.h"
@@ -64,13 +66,13 @@
 #include "nm-netns.h"
 #include "nm-dispatcher.h"
 #include "nm-config.h"
-#include "nm-utils/c-list.h"
+#include "c-list/src/c-list.h"
 #include "dns/nm-dns-manager.h"
+#include "nm-acd-manager.h"
 #include "nm-core-internal.h"
 #include "systemd/nm-sd.h"
 #include "nm-lldp-listener.h"
 #include "nm-audit-manager.h"
-#include "nm-arping-manager.h"
 #include "nm-connectivity.h"
 #include "nm-dbus-interface.h"
 #include "nm-device-vlan.h"
@@ -78,14 +80,10 @@
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF (NMDevice);
 
-#include "introspection/org.freedesktop.NetworkManager.Device.h"
-#include "introspection/org.freedesktop.NetworkManager.Device.Statistics.h"
-
 /*****************************************************************************/
 
-#define DHCP_RESTART_TIMEOUT   120
-#define DHCP_NUM_TRIES_MAX     3
 #define DEFAULT_AUTOCONNECT    TRUE
+#define DHCP_GRACE_PERIOD_SEC  480
 
 #define CARRIER_WAIT_TIME_MS 6000
 #define CARRIER_WAIT_TIME_AFTER_MTU_MS 10000
@@ -131,13 +129,13 @@ typedef struct {
 	int ifindex;
 } DeleteOnDeactivateData;
 
-typedef void (*ArpingCallback) (NMDevice *, NMIP4Config **, gboolean);
+typedef void (*AcdCallback) (NMDevice *, NMIP4Config **, gboolean);
 
 typedef struct {
-	ArpingCallback callback;
+	AcdCallback callback;
 	NMDevice *device;
 	NMIP4Config **configs;
-} ArpingData;
+} AcdData;
 
 typedef enum {
 	HW_ADDR_TYPE_UNSET = 0,
@@ -153,6 +151,24 @@ typedef enum {
 	FIREWALL_STATE_WAIT_IP_CONFIG,
 } FirewallState;
 
+typedef struct {
+	NMIPConfig *orig;      /* the original configuration applied to the device */
+	NMIPConfig *current;   /* configuration after external changes.  NULL means
+	                          that the original configuration didn't change. */
+} AppliedConfig;
+
+struct _NMDeviceConnectivityHandle {
+	CList concheck_lst;
+	NMDevice *self;
+	NMDeviceConnectivityCallback callback;
+	gpointer user_data;
+	NMConnectivityCheckHandle *c_handle;
+	guint64 seq;
+	bool is_periodic:1;
+	bool is_periodic_bump:1;
+	bool is_periodic_bump_on_complete:1;
+};
+
 /*****************************************************************************/
 
 enum {
@@ -166,6 +182,7 @@ enum {
 	REMOVED,
 	RECHECK_AUTO_ACTIVATE,
 	RECHECK_ASSUME,
+	CONNECTIVITY_CHANGED,
 	LAST_SIGNAL,
 };
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -230,12 +247,18 @@ typedef struct _NMDevicePrivate {
 		NMDeviceStateReason reason;
 	} queued_state;
 
-	guint queued_ip4_config_id;
-	guint queued_ip6_config_id;
+	union {
+		struct {
+			guint queued_ip_config_id_6;
+			guint queued_ip_config_id_4;
+		};
+		guint queued_ip_config_id_x[2];
+	};
+
 	GSList *pending_actions;
 	GSList *dad6_failed_addrs;
 
-	NMDevice *parent_device;
+	NMDBusTrackObjPath parent_device;
 
 	char *        udi;
 	char *        iface;   /* may change, could be renamed by user */
@@ -259,11 +282,13 @@ typedef struct _NMDevicePrivate {
 	bool queued_ip4_config_pending:1;
 	bool queued_ip6_config_pending:1;
 
+	bool update_ip_config_completed_v4:1;
+	bool update_ip_config_completed_v6:1;
+
 	char *        ip_iface;
 	int           ip_ifindex;
 	NMDeviceType  type;
 	char *        type_desc;
-	char *        type_description;
 	NMLinkType    link_type;
 	NMDeviceCapabilities capabilities;
 	char *        driver;
@@ -298,9 +323,7 @@ typedef struct _NMDevicePrivate {
 
 	NMActRequest *  queued_act_request;
 	bool            queued_act_request_is_waiting_for_carrier:1;
-	bool            act_request_public:1;
-	NMActRequest   *act_request;
-	gulong          act_request_id;
+	NMDBusTrackObjPath act_request;
 	ActivationHandleData act_handle4; /* for layer2 and IPv4. */
 	ActivationHandleData act_handle6;
 	guint           recheck_assume_id;
@@ -357,6 +380,12 @@ typedef struct _NMDevicePrivate {
 
 	NMDeviceAutoconnectBlockedFlags autoconnect_blocked_flags:4;
 
+	bool            is_enslaved:1;
+	bool            master_ready_handled:1;
+
+	bool            ipv6ll_handle:1; /* TRUE if NM handles the device's IPv6LL address */
+	bool            ipv6ll_has:1;
+
 	/* Generic DHCP stuff */
 	char *          dhcp_anycast_address;
 
@@ -367,17 +396,56 @@ typedef struct _NMDevicePrivate {
 	NMPacrunnerManager *pacrunner_manager;
 	NMPacrunnerCallId *pacrunner_call_id;
 
-	/* IP4 configuration info */
-	NMIP4Config *   ip4_config;     /* Combined config from VPN, settings, and device */
+	/* IP configuration info. Combined config from VPN, settings, and device */
+	union {
+		struct {
+			NMIP6Config *ip_config_6;
+			NMIP4Config *ip_config_4;
+		};
+		NMIPConfig *ip_config_x[2];
+	};
+
 	union {
 		const IpState   ip4_state;
 		IpState         ip4_state_;
 	};
-	NMIP4Config *   con_ip4_config; /* config from the setting */
-	NMIP4Config *   dev_ip4_config; /* Config from DHCP, PPP, LLv4, etc */
-	NMIP4Config *   ext_ip4_config; /* Stuff added outside NM */
-	NMIP4Config *   wwan_ip4_config; /* WWAN configuration */
-	GSList *        vpn4_configs;   /* VPNs which use this device */
+	AppliedConfig   dev_ip4_config; /* Config from DHCP, PPP, LLv4, etc */
+
+	/* config from the setting */
+	union {
+		struct {
+			NMIP6Config *con_ip_config_6;
+			NMIP4Config *con_ip_config_4;
+		};
+		NMIPConfig *con_ip_config_x[2];
+	};
+
+	/* Stuff added outside NM */
+	union {
+		struct {
+			NMIP6Config *ext_ip_config_6;
+			NMIP4Config *ext_ip_config_4;
+		};
+		NMIPConfig *ext_ip_config_x[2];
+	};
+
+	/* VPNs which use this device */
+	union {
+		struct {
+			GSList *vpn_configs_6;
+			GSList *vpn_configs_4;
+		};
+		GSList *vpn_configs_x[2];
+	};
+
+	/* WWAN configuration */
+	union {
+		struct {
+			AppliedConfig  wwan_ip_config_6;
+			AppliedConfig  wwan_ip_config_4;
+		};
+		AppliedConfig wwan_ip_config_x[2];
+	};
 
 	bool v4_has_shadowed_routes;
 	const char *ip4_rp_filter;
@@ -387,10 +455,9 @@ typedef struct _NMDevicePrivate {
 		NMDhcpClient *  client;
 		gulong          state_sigid;
 		NMDhcp4Config * config;
-		guint           restart_id;
-		guint           num_tries_left;
 		char *          pac_url;
 		bool            was_active;
+		guint           grace_id;
 	} dhcp4;
 
 	struct {
@@ -398,8 +465,8 @@ typedef struct _NMDevicePrivate {
 		guint timeout;
 		guint watch;
 		GPid pid;
-		const char *binary;
-		const char *address;
+		char *binary;
+		char *address;
 		guint deadline;
 	} gw_ping;
 
@@ -420,22 +487,17 @@ typedef struct _NMDevicePrivate {
 	/* IPv4 DAD stuff */
 	struct {
 		GSList *          dad_list;
-		NMArpingManager * announcing;
-	} arping;
+		NMAcdManager * announcing;
+	} acd;
 
-	/* IP6 configuration info */
-	NMIP6Config *  ip6_config;
 	union {
 		const IpState   ip6_state;
 		IpState         ip6_state_;
 	};
-	NMIP6Config *  con_ip6_config; /* config from the setting */
-	NMIP6Config *  wwan_ip6_config;
-	NMIP6Config *  ext_ip6_config; /* Stuff added outside NM */
+	AppliedConfig  ac_ip6_config;  /* config from IPv6 autoconfiguration */
 	NMIP6Config *  ext_ip6_config_captured; /* Configuration captured from platform. */
-	GSList *       vpn6_configs;   /* VPNs which use this device */
-	bool           nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */
 	NMIP6Config *  dad6_ip6_config;
+	struct in6_addr ipv6ll_addr;
 
 	GHashTable *   rt6_temporary_not_available;
 
@@ -443,8 +505,6 @@ typedef struct _NMDevicePrivate {
 	gulong         ndisc_changed_id;
 	gulong         ndisc_timeout_id;
 	NMSettingIP6ConfigPrivacy ndisc_use_tempaddr;
-	/* IP6 config from autoconf */
-	NMIP6Config *  ac_ip6_config;
 
 	guint          linklocal6_timeout_id;
 	guint8         linklocal6_dad_counter;
@@ -458,21 +518,18 @@ typedef struct _NMDevicePrivate {
 		gulong           prefix_sigid;
 		NMDhcp6Config *  config;
 		/* IP6 config from DHCP */
-		NMIP6Config *    ip6_config;
+		AppliedConfig    ip6_config;
 		/* Event ID of the current IP6 config from DHCP */
 		char *           event_id;
-		guint            restart_id;
-		guint            num_tries_left;
 		guint            needed_prefixes;
 		bool             was_active;
+		guint            grace_id;
 	} dhcp6;
 
 	gboolean needs_ip6_subnet;
 
 	/* master interface for bridge/bond/team slave */
 	NMDevice *      master;
-	bool            is_enslaved;
-	bool            master_ready_handled;
 	gulong          master_ready_id;
 
 	/* slave management */
@@ -485,9 +542,26 @@ typedef struct _NMDevicePrivate {
 	NMNetns *netns;
 
 	NMLldpListener *lldp_listener;
+
+	NMConnectivity *concheck_mgr;
+
+	/* if periodic checks are enabled, this is the source id for the next check. */
+	guint concheck_p_cur_id;
+
+	/* the currently configured max periodic interval. */
+	guint concheck_p_max_interval;
+
+	/* the current interval. If we are probing, the interval might be lower
+	 * then the configured max interval. */
+	guint concheck_p_cur_interval;
+
+	/* the timestamp, when we last scheduled the timer concheck_p_cur_id with current interval
+	 * concheck_p_cur_interval. */
+	gint64 concheck_p_cur_basetime_ns;
+
 	NMConnectivityState connectivity_state;
-	guint concheck_periodic_id;
-	guint64 concheck_seq;
+
+	CList concheck_lst_head;
 
 	guint check_delete_unrealized_id;
 
@@ -500,35 +574,35 @@ typedef struct _NMDevicePrivate {
 
 } NMDevicePrivate;
 
-G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
 
 #define NM_DEVICE_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMDevice, NM_IS_DEVICE)
 
 /*****************************************************************************/
 
+static const NMDBusInterfaceInfoExtended interface_info_device;
+static const GDBusSignalInfo signal_info_state_changed;
+
 static void nm_device_set_proxy_config (NMDevice *self, const char *pac_url);
 
-static gboolean update_ext_ip_config (NMDevice *self, int addr_family, gboolean initial, gboolean intersect_configs);
+static gboolean update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_configs);
 
-static gboolean nm_device_set_ip4_config (NMDevice *self,
-                                          NMIP4Config *config,
-                                          gboolean commit,
-                                          GPtrArray *ip4_dev_route_blacklist);
-static gboolean ip4_config_merge_and_apply (NMDevice *self,
-                                            gboolean commit);
+static gboolean nm_device_set_ip_config (NMDevice *self,
+                                         int addr_family,
+                                         NMIPConfig *config,
+                                         gboolean commit,
+                                         GPtrArray *ip4_dev_route_blacklist);
 
-static gboolean nm_device_set_ip6_config (NMDevice *self,
-                                          NMIP6Config *config,
-                                          gboolean commit);
-static gboolean ip6_config_merge_and_apply (NMDevice *self,
-                                            gboolean commit);
+static gboolean ip_config_merge_and_apply (NMDevice *self,
+                                           int addr_family,
+                                           gboolean commit);
 
 static gboolean nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure);
 static void nm_device_slave_notify_enslave (NMDevice *self, gboolean success);
 static void nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason);
 
-static gboolean addrconf6_start_with_link_ready (NMDevice *self);
-static NMActStageReturn linklocal6_start (NMDevice *self);
+static void addrconf6_start_with_link_ready (NMDevice *self);
+static gboolean linklocal6_start (NMDevice *self);
 
 static void _carrier_wait_check_queued_act_request (NMDevice *self);
 static gint64 _get_carrier_wait_ms (NMDevice *self);
@@ -556,9 +630,10 @@ static void realize_start_setup (NMDevice *self,
                                  NMUnmanFlagOp unmanaged_user_explicit);
 static void _set_mtu (NMDevice *self, guint32 mtu);
 static void _commit_mtu (NMDevice *self, const NMIP4Config *config);
-static void dhcp_schedule_restart (NMDevice *self, int addr_family, const char *reason);
 static void _cancel_activation (NMDevice *self);
 
+static void concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean is_periodic);
+
 /*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (queued_state_to_string, NMDeviceState,
@@ -683,6 +758,16 @@ nm_device_get_platform (NMDevice *self)
 	return nm_netns_get_platform (nm_device_get_netns (self));
 }
 
+static NMConnectivity *
+concheck_get_mgr (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (G_UNLIKELY (!priv->concheck_mgr))
+		priv->concheck_mgr = g_object_ref (nm_connectivity_get ());
+	return priv->concheck_mgr;
+}
+
 static NMIP4Config *
 _ip4_config_new (NMDevice *self)
 {
@@ -697,6 +782,105 @@ _ip6_config_new (NMDevice *self)
 	                          nm_device_get_ip_ifindex (self));
 }
 
+static NMIPConfig *
+_ip_config_new (NMDevice *self, int addr_family)
+{
+	nm_assert_addr_family (addr_family);
+
+	return addr_family == AF_INET
+	       ? (gpointer) _ip4_config_new (self)
+	       : (gpointer) _ip6_config_new (self);
+}
+
+static void
+applied_config_clear (AppliedConfig *config)
+{
+	g_clear_object (&config->current);
+	g_clear_object (&config->orig);
+}
+
+static void
+applied_config_init (AppliedConfig *config, gpointer ip_config)
+{
+	nm_g_object_ref (ip_config);
+	applied_config_clear (config);
+	config->orig = ip_config;
+}
+
+static void
+applied_config_init_new (AppliedConfig *config, NMDevice *self, int addr_family)
+{
+	gs_unref_object NMIPConfig *c = _ip_config_new (self, addr_family);
+
+	applied_config_init (config, c);
+}
+
+static NMIPConfig *
+applied_config_get_current (AppliedConfig *config)
+{
+	return config->current ?: config->orig;
+}
+
+static void
+applied_config_add_address (AppliedConfig *config, const NMPlatformIPAddress *address)
+{
+	if (config->orig)
+		nm_ip_config_add_address (config->orig, address);
+	else
+		nm_assert (!config->current);
+
+	if (config->current)
+		nm_ip_config_add_address (config->current, address);
+}
+
+static void
+applied_config_add_nameserver (AppliedConfig *config, const NMIPAddr *ns)
+{
+	if (config->orig)
+		nm_ip_config_add_nameserver (config->orig, ns);
+	else
+		nm_assert (!config->current);
+
+	if (config->current)
+		nm_ip_config_add_nameserver (config->current, ns);
+}
+
+static void
+applied_config_add_search (AppliedConfig *config, const char *new)
+{
+	if (config->orig)
+		nm_ip_config_add_search (config->orig, new);
+	else
+		nm_assert (!config->current);
+
+	if (config->current)
+		nm_ip_config_add_search (config->current, new);
+}
+
+static void
+applied_config_reset_searches (AppliedConfig *config)
+{
+	if (config->orig)
+		nm_ip_config_reset_searches (config->orig);
+	else
+		nm_assert (!config->current);
+
+	if (config->current)
+		nm_ip_config_reset_searches (config->current);
+}
+
+static void
+applied_config_reset_nameservers (AppliedConfig *config)
+{
+	if (config->orig)
+		nm_ip_config_reset_nameservers (config->orig);
+	else
+		nm_assert (!config->current);
+
+	if (config->current)
+		nm_ip_config_reset_nameservers (config->current);
+}
+
 /*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_sys_iface_state_to_str, NMDeviceSysIfaceState,
@@ -826,29 +1010,18 @@ nm_device_assume_state_reset (NMDevice *self)
 /*****************************************************************************/
 
 static void
-init_ip4_config_dns_priority (NMDevice *self, NMIP4Config *config)
+init_ip_config_dns_priority (NMDevice *self, NMIPConfig *config)
 {
 	gs_free char *value = NULL;
 	gint priority;
 
 	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               "ipv4.dns-priority",
+	                                               (nm_ip_config_get_addr_family (config) == AF_INET)
+	                                                 ? "ipv4.dns-priority"
+	                                                 : "ipv6.dns-priority",
 	                                               self);
 	priority = _nm_utils_ascii_str_to_int64 (value, 10, G_MININT, G_MAXINT, 0);
-	nm_ip4_config_set_dns_priority (config, priority ?: NM_DNS_PRIORITY_DEFAULT_NORMAL);
-}
-
-static void
-init_ip6_config_dns_priority (NMDevice *self, NMIP6Config *config)
-{
-	gs_free char *value = NULL;
-	gint priority;
-
-	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               "ipv6.dns-priority",
-	                                               self);
-	priority = _nm_utils_ascii_str_to_int64 (value, 10, G_MININT, G_MAXINT, 0);
-	nm_ip6_config_set_dns_priority (config, priority ?: NM_DNS_PRIORITY_DEFAULT_NORMAL);
+	nm_ip_config_set_dns_priority (config, priority ?: NM_DNS_PRIORITY_DEFAULT_NORMAL);
 }
 
 /*****************************************************************************/
@@ -879,19 +1052,41 @@ nm_device_ipv4_sysctl_set (NMDevice *self, const char *property, const char *val
 }
 
 static guint32
-nm_device_ipv4_sysctl_get_uint32 (NMDevice *self, const char *property, guint32 fallback)
+nm_device_ipv4_sysctl_get_effective_uint32 (NMDevice *self, const char *property, guint32 fallback)
 {
 	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+	gint64 v, v_all;
 
 	if (!nm_device_get_ip_ifindex (self))
 		return fallback;
 
-	return nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
-	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, nm_device_get_ip_iface (self), property)),
-	                                           10,
-	                                           0,
-	                                           G_MAXUINT32,
-	                                           fallback);
+	/* for this kind of sysctl (e.g. "rp_filter"), kernel effectively uses the
+	 * MAX of the per-device value and the "all" value.
+	 *
+	 * Also do that, by reading both sysctls and return the maximum. */
+
+	v = nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
+	                                        NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET,
+	                                                                                                  buf,
+	                                                                                                  nm_device_get_ip_iface (self),
+	                                                                                                  property)),
+	                                        10,
+	                                        0,
+	                                        G_MAXUINT32,
+	                                        -1);
+
+	v_all = nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
+	                                            NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET,
+	                                                                                                      buf,
+	                                                                                                      "all",
+	                                                                                                      property)),
+	                                            10,
+	                                            0,
+	                                            G_MAXUINT32,
+	                                            -1);
+
+	v = NM_MAX (v, v_all);
+	return v > -1 ? (guint32) v : fallback;
 }
 
 gboolean
@@ -1035,7 +1230,7 @@ _set_ip_state (NMDevice *self, int addr_family, IpState new_state)
 
 	p =   (addr_family == AF_INET)
 	    ? &priv->ip4_state_
-        : &priv->ip6_state_;
+	    : &priv->ip6_state_;
 
 	if (*p != new_state) {
 		_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)",
@@ -1076,28 +1271,26 @@ nm_device_get_iface (NMDevice *self)
 }
 
 gboolean
-nm_device_take_over_link (NMDevice *self, const char *ifname, gboolean *renamed)
+nm_device_take_over_link (NMDevice *self, int ifindex, char **old_name)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const NMPlatformLink *plink;
 	NMPlatform *platform;
 	gboolean up, success = TRUE;
-	int ifindex;
+	gs_free char *name = NULL;
 
 	g_return_val_if_fail (priv->ifindex <= 0, FALSE);
-	g_return_val_if_fail (ifname, FALSE);
 
-	NM_SET_OUT (renamed, FALSE);
+	NM_SET_OUT (old_name, NULL);
 
 	platform = nm_device_get_platform (self);
-	plink = nm_platform_link_get_by_ifname (platform, ifname);
+	plink = nm_platform_link_get (platform, ifindex);
 	if (!plink)
 		return FALSE;
 
-	ifindex = plink->ifindex;
-
-	if (!nm_streq (ifname, nm_device_get_iface (self))) {
+	if (!nm_streq (plink->name, nm_device_get_iface (self))) {
 		up = NM_FLAGS_HAS (plink->n_ifi_flags, IFF_UP);
+		name = g_strdup (plink->name);
 
 		/* Rename the link to the device ifname */
 		if (up)
@@ -1106,7 +1299,8 @@ nm_device_take_over_link (NMDevice *self, const char *ifname, gboolean *renamed)
 		if (up)
 			nm_platform_link_set_up (platform, ifindex, NULL);
 
-		NM_SET_OUT (renamed, success);
+		if (success)
+			NM_SET_OUT (old_name, g_steal_pointer (&name));
 	}
 
 	if (success) {
@@ -1178,71 +1372,102 @@ nm_device_get_ip_ifindex (const NMDevice *self)
 	return priv->ip_iface ? priv->ip_ifindex : priv->ifindex;
 }
 
-/**
- * nm_device_set_ip_iface:
- * @self: the #NMDevice
- * @iface: the new IP interface name
- *
- * Updates the IP interface name and possibly the ifindex.
- *
- * Returns: %TRUE if the anything (name or ifindex) changed, %FALSE if nothing
- * changed.
- */
-gboolean
-nm_device_set_ip_iface (NMDevice *self, const char *iface)
+static void
+_set_ip_ifindex (NMDevice *self,
+                 int ifindex,
+                 const char *ifname)
 {
-	NMDevicePrivate *priv;
-	int ifindex;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMPlatform *platform;
+	gboolean eq_name;
 
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	/* normalize arguments */
+	if (ifindex <= 0) {
+		ifindex = 0;
+		ifname = NULL;
+	}
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	if (nm_streq0 (iface, priv->ip_iface)) {
-		if (!iface)
-			return FALSE;
-		ifindex = nm_platform_if_nametoindex (nm_device_get_platform (self), iface);
-		if (   ifindex <= 0
-		    || priv->ip_ifindex == ifindex)
-			return FALSE;
+	eq_name = nm_streq0 (priv->ip_iface, ifname);
 
-		priv->ip_ifindex = ifindex;
-		_LOGD (LOGD_DEVICE, "ip-ifname: update ifindex for ifname '%s': %d", iface, priv->ip_ifindex);
-	} else {
+	if (   eq_name
+	    && priv->ip_ifindex == ifindex)
+		return;
+
+	_LOGD (LOGD_DEVICE, "ip-ifindex: update ip-interface to %s%s%s, ifindex %d",
+	       NM_PRINT_FMT_QUOTE_STRING (ifname),
+	       ifindex);
+
+	priv->ip_ifindex = ifindex;
+	if (!eq_name) {
 		g_free (priv->ip_iface);
-		priv->ip_iface = g_strdup (iface);
-
-		if (iface) {
-			/* The @iface name is not in sync with the platform cache.
-			 * So, there is no point asking the platform cache to resolve
-			 * the ifindex. Instead, we can only hope that the interface
-			 * with this name still exists and we resolve the ifindex
-			 * anew.
-			 */
-			priv->ip_ifindex = nm_platform_if_nametoindex (nm_device_get_platform (self), iface);
-			if (priv->ip_ifindex > 0)
-				_LOGD (LOGD_DEVICE, "ip-ifname: set ifname '%s', ifindex %d", iface, priv->ip_ifindex);
-			else
-				_LOGW (LOGD_DEVICE, "ip-ifname: set ifname '%s', unknown ifindex", iface);
-		} else {
-			priv->ip_ifindex = 0;
-			_LOGD (LOGD_DEVICE, "ip-ifname: clear ifname");
-		}
+		priv->ip_iface = g_strdup (ifname);
+		_notify (self, PROP_IP_IFACE);
 	}
 
 	if (priv->ip_ifindex > 0) {
-		if (nm_platform_check_kernel_support (nm_device_get_platform (self),
+		platform = nm_device_get_platform (self);
+
+		nm_platform_process_events_ensure_link (platform,
+		                                        priv->ip_ifindex,
+		                                        priv->ip_iface);
+
+		if (nm_platform_check_kernel_support (platform,
 		                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
-			nm_platform_link_set_user_ipv6ll_enabled (nm_device_get_platform (self), priv->ip_ifindex, TRUE);
+			nm_platform_link_set_user_ipv6ll_enabled (platform, priv->ip_ifindex, TRUE);
 
-		if (!nm_platform_link_is_up (nm_device_get_platform (self), priv->ip_ifindex))
-			nm_platform_link_set_up (nm_device_get_platform (self), priv->ip_ifindex, NULL);
+		if (!nm_platform_link_is_up (platform, priv->ip_ifindex))
+			nm_platform_link_set_up (platform, priv->ip_ifindex, NULL);
 	}
 
 	/* We don't care about any saved values from the old iface */
 	g_hash_table_remove_all (priv->ip6_saved_properties);
+}
 
-	_notify (self, PROP_IP_IFACE);
-	return TRUE;
+gboolean
+nm_device_set_ip_ifindex (NMDevice *self, int ifindex)
+{
+	char ifname_buf[IFNAMSIZ];
+	const char *ifname = NULL;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (nm_device_is_activating (self), FALSE);
+
+	if (ifindex > 0) {
+		ifname = nm_platform_if_indextoname (nm_device_get_platform (self), ifindex, ifname_buf);
+		if (!ifname)
+			_LOGW (LOGD_DEVICE, "ip-ifindex: ifindex %d not found", ifindex);
+	}
+
+	_set_ip_ifindex (self, ifindex, ifname);
+	return ifindex > 0;
+}
+
+/**
+ * nm_device_set_ip_iface:
+ * @self: the #NMDevice
+ * @ifname: the new IP interface name
+ *
+ * Updates the IP interface name and possibly the ifindex.
+ *
+ * Returns: %TRUE if an interface with name @ifname exists,
+ *   and %FALSE, if @ifname is %NULL or no such interface exists.
+ */
+gboolean
+nm_device_set_ip_iface (NMDevice *self, const char *ifname)
+{
+	int ifindex = 0;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (nm_device_is_activating (self), FALSE);
+
+	if (ifname) {
+		ifindex = nm_platform_if_nametoindex (nm_device_get_platform (self), ifname);
+		if (ifindex <= 0)
+			_LOGW (LOGD_DEVICE, "ip-ifindex: ifname %s not found", ifname);
+	}
+
+	_set_ip_ifindex (self, ifindex, ifname);
+	return ifindex > 0;
 }
 
 static gboolean
@@ -1289,12 +1514,9 @@ nm_device_parent_get_ifindex (NMDevice *self)
 NMDevice *
 nm_device_parent_get_device (NMDevice *self)
 {
-	NMDevicePrivate *priv;
-
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	return priv->parent_device;
+	return NM_DEVICE_GET_PRIVATE (self)->parent_device.obj;
 }
 
 static void
@@ -1316,7 +1538,7 @@ _parent_set_ifindex (NMDevice *self,
 	NMDevice *parent_device;
 	gboolean changed = FALSE;
 	int old_ifindex;
-	NMDevice *old_device;
+	gs_unref_object NMDevice *old_device = NULL;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
@@ -1326,16 +1548,15 @@ _parent_set_ifindex (NMDevice *self,
 		parent_ifindex = 0;
 
 	old_ifindex = priv->parent_ifindex;
-	old_device = priv->parent_device;
 
 	if (priv->parent_ifindex == parent_ifindex) {
 		if (parent_ifindex > 0) {
 			if (   !force_check
-			    && priv->parent_device
-			    && nm_device_get_ifindex (priv->parent_device) == parent_ifindex)
+			    && priv->parent_device.obj
+			    && nm_device_get_ifindex (priv->parent_device.obj) == parent_ifindex)
 				return FALSE;
 		} else {
-			if (!priv->parent_device)
+			if (!priv->parent_device.obj)
 				return FALSE;
 		}
 	} else {
@@ -1350,24 +1571,23 @@ _parent_set_ifindex (NMDevice *self,
 	} else
 		parent_device = NULL;
 
-	if (parent_device != priv->parent_device) {
-		priv->parent_device = parent_device;
+	if (parent_device != priv->parent_device.obj) {
+		old_device = nm_g_object_ref (priv->parent_device.obj);
+		nm_dbus_track_obj_path_set (&priv->parent_device, parent_device, TRUE);
 		changed = TRUE;
 	}
 
 	if (changed) {
 		if (priv->parent_ifindex <= 0)
 			_LOGD (LOGD_DEVICE, "parent: clear");
-		else if (!priv->parent_device)
+		else if (!priv->parent_device.obj)
 			_LOGD (LOGD_DEVICE, "parent: ifindex %d, no device", priv->parent_ifindex);
 		else {
 			_LOGD (LOGD_DEVICE, "parent: ifindex %d, device %p, %s", priv->parent_ifindex,
-			       priv->parent_device, nm_device_get_iface (priv->parent_device));
+			       priv->parent_device.obj, nm_device_get_iface (priv->parent_device.obj));
 		}
 
-		NM_DEVICE_GET_CLASS (self)->parent_changed_notify (self, old_ifindex, old_device, priv->parent_ifindex, priv->parent_device);
-
-		_notify (self, PROP_PARENT);
+		NM_DEVICE_GET_CLASS (self)->parent_changed_notify (self, old_ifindex, old_device, priv->parent_ifindex, priv->parent_device.obj);
 	}
 	return changed;
 }
@@ -1392,7 +1612,7 @@ nm_device_parent_notify_changed (NMDevice *self,
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->parent_ifindex > 0) {
-		if (   priv->parent_device == change_candidate
+		if (   priv->parent_device.obj == change_candidate
 		    || priv->parent_ifindex == nm_device_get_ifindex (change_candidate))
 			return _parent_set_ifindex (self, priv->parent_ifindex, device_removed);
 	}
@@ -1698,16 +1918,13 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 static gboolean
 default_route_metric_penalty_detect (NMDevice *self)
 {
-#if WITH_CONCHECK
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	/* currently we don't differentiate between IPv4 and IPv6 when detecting
 	 * connectivity. */
 	if (   priv->connectivity_state != NM_CONNECTIVITY_FULL
-		&& nm_connectivity_check_enabled (nm_connectivity_get ())) {
+	    && nm_connectivity_check_enabled (concheck_get_mgr (self)))
 		return TRUE;
-	}
-#endif
 
 	return FALSE;
 }
@@ -1775,6 +1992,34 @@ out:
 	return nm_utils_ip_route_metric_normalize (addr_family, route_metric);
 }
 
+static NMSettingConnectionMdns
+_get_mdns (NMDevice *self)
+{
+	NMConnection *connection;
+	NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_SETTING_CONNECTION_MDNS_DEFAULT);
+
+	connection = nm_device_get_applied_connection (self);
+	if (connection)
+		mdns = nm_setting_connection_get_mdns (nm_connection_get_setting_connection (connection));
+
+	if (mdns == NM_SETTING_CONNECTION_MDNS_DEFAULT) {
+		gs_free char *value = NULL;
+
+		value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+		                                               "connection.mdns",
+		                                               self);
+		mdns = _nm_utils_ascii_str_to_int64 (value,
+		                                     10,
+		                                     NM_SETTING_CONNECTION_MDNS_NO,
+		                                     NM_SETTING_CONNECTION_MDNS_YES,
+		                                     NM_SETTING_CONNECTION_MDNS_DEFAULT);
+	}
+
+	return mdns;
+}
+
 guint32
 nm_device_get_route_table (NMDevice *self,
                            int addr_family,
@@ -1851,12 +2096,12 @@ nm_device_get_best_default_route (NMDevice *self,
 
 	switch (addr_family) {
 	case AF_INET:
-		return priv->ip4_config ? nm_ip4_config_best_default_route_get (priv->ip4_config) : NULL;
+		return priv->ip_config_4 ? nm_ip4_config_best_default_route_get (priv->ip_config_4) : NULL;
 	case AF_INET6:
-		return priv->ip6_config ? nm_ip6_config_best_default_route_get (priv->ip6_config) : NULL;
+		return priv->ip_config_6 ? nm_ip6_config_best_default_route_get (priv->ip_config_6) : NULL;
 	case AF_UNSPEC:
-		return    (priv->ip4_config ? nm_ip4_config_best_default_route_get (priv->ip4_config) : NULL)
-		       ?: (priv->ip6_config ? nm_ip6_config_best_default_route_get (priv->ip6_config) : NULL);
+		return    (priv->ip_config_4 ? nm_ip4_config_best_default_route_get (priv->ip_config_4) : NULL)
+		       ?: (priv->ip_config_6 ? nm_ip6_config_best_default_route_get (priv->ip_config_6) : NULL);
 	default:
 		g_return_val_if_reached (NULL);
 	}
@@ -1884,18 +2129,23 @@ nm_device_get_type_description (NMDevice *self)
 static const char *
 get_type_description (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceClass *klass;
 
-	if (!priv->type_description) {
+	nm_assert (NM_IS_DEVICE (self));
+
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (G_UNLIKELY (!klass->default_type_description)) {
 		const char *typename;
+		gs_free char *s = NULL;
 
 		typename = G_OBJECT_TYPE_NAME (self);
 		if (g_str_has_prefix (typename, "NMDevice"))
 			typename += 8;
-		priv->type_description = g_ascii_strdown (typename, -1);
+		s = g_ascii_strdown (typename, -1);
+		klass->default_type_description = g_intern_string (s);
 	}
 
-	return priv->type_description;
+	return klass->default_type_description;
 }
 
 gboolean
@@ -1909,7 +2159,7 @@ nm_device_get_act_request (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	return NM_DEVICE_GET_PRIVATE (self)->act_request;
+	return NM_DEVICE_GET_PRIVATE (self)->act_request.obj;
 }
 
 NMSettingsConnection *
@@ -1917,7 +2167,7 @@ nm_device_get_settings_connection (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	return priv->act_request ? nm_act_request_get_settings_connection (priv->act_request) : NULL;
+	return priv->act_request.obj ? nm_act_request_get_settings_connection (priv->act_request.obj) : NULL;
 }
 
 NMConnection *
@@ -1929,7 +2179,7 @@ nm_device_get_applied_connection (NMDevice *self)
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	return priv->act_request ? nm_act_request_get_applied_connection (priv->act_request) : NULL;
+	return priv->act_request.obj ? nm_act_request_get_applied_connection (priv->act_request.obj) : NULL;
 }
 
 gboolean
@@ -1937,10 +2187,10 @@ nm_device_has_unmodified_applied_connection (NMDevice *self, NMSettingCompareFla
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (!priv->act_request)
+	if (!priv->act_request.obj)
 		return FALSE;
 
-	return nm_active_connection_has_unmodified_applied_connection ((NMActiveConnection *) priv->act_request, compare_flags);
+	return nm_active_connection_has_unmodified_applied_connection ((NMActiveConnection *) priv->act_request.obj, compare_flags);
 }
 
 NMSetting *
@@ -1968,173 +2218,557 @@ nm_device_get_physical_port_id (NMDevice *self)
 
 /*****************************************************************************/
 
+typedef enum {
+	CONCHECK_SCHEDULE_UPDATE_INTERVAL,
+	CONCHECK_SCHEDULE_CHECK_EXTERNAL,
+	CONCHECK_SCHEDULE_CHECK_PERIODIC,
+	CONCHECK_SCHEDULE_RETURNED_MIN,
+	CONCHECK_SCHEDULE_RETURNED_BUMP,
+	CONCHECK_SCHEDULE_RETURNED_MAX,
+} ConcheckScheduleMode;
+
+static NMDeviceConnectivityHandle *concheck_start (NMDevice *self,
+                                                   NMDeviceConnectivityCallback callback,
+                                                   gpointer user_data,
+                                                   gboolean is_periodic);
+
+static void concheck_periodic_schedule_set (NMDevice *self,
+                                            ConcheckScheduleMode mode);
+
+static gboolean
+concheck_periodic_timeout_cb (gpointer user_data)
+{
+	NMDevice *self = user_data;
+
+	_LOGt (LOGD_CONCHECK, "connectivity: periodic timeout");
+	concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_CHECK_PERIODIC);
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+concheck_is_possible (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (   !nm_device_is_real (self)
+	    || NM_FLAGS_HAS (priv->unmanaged_flags, NM_UNMANAGED_LOOPBACK))
+		return FALSE;
+
+	/* we enable periodic checks for every device state (except UNKNOWN). Especially with
+	 * unmanaged devices, it is interesting to know whether we have connectivity on that device. */
+	if (priv->state == NM_DEVICE_STATE_UNKNOWN)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+concheck_periodic_schedule_do (NMDevice *self, gint64 interval_ns)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gboolean periodic_check_disabled = FALSE;
+
+	/* we always cancel whatever was pending. */
+	if (nm_clear_g_source (&priv->concheck_p_cur_id))
+		periodic_check_disabled = TRUE;
+
+	if (priv->concheck_p_max_interval == 0) {
+		/* periodic checks are disabled */
+		goto out;
+	}
+
+	nm_assert (interval_ns >= 0);
+
+	if (!concheck_is_possible (self))
+		goto out;
+
+	_LOGT (LOGD_CONCHECK, "connectivity: periodic-check: %sscheduled in %u milliseconds (%u seconds interval)",
+	       periodic_check_disabled ? "re-" : "",
+	       (guint) (interval_ns / NM_UTILS_NS_PER_MSEC),
+	       priv->concheck_p_cur_interval);
+
+	nm_assert (priv->concheck_p_cur_interval > 0);
+	priv->concheck_p_cur_id = g_timeout_add (interval_ns / NM_UTILS_NS_PER_MSEC,
+	                                         concheck_periodic_timeout_cb,
+	                                         self);
+	return TRUE;
+out:
+	if (periodic_check_disabled)
+		_LOGT (LOGD_CONCHECK, "connectivity: periodic-check: unscheduled");
+	return FALSE;
+}
+
+#define CONCHECK_P_PROBE_INTERVAL 1
+
 static void
-update_connectivity_state (NMDevice *self, NMConnectivityState state)
+concheck_periodic_schedule_set (NMDevice *self,
+                                ConcheckScheduleMode mode)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gint64 new_expiry, exp_expiry, cur_expiry, tdiff;
+	gint64 now_ns = 0;
 
-	/* If the connectivity check is disabled, make an optimistic guess. */
-	if (state == NM_CONNECTIVITY_UNKNOWN) {
-		if (priv->state == NM_DEVICE_STATE_ACTIVATED) {
-			if (nm_device_get_best_default_route (self, AF_UNSPEC))
-				state = NM_CONNECTIVITY_FULL;
-			else
-				state = NM_CONNECTIVITY_LIMITED;
-		} else {
-			state = NM_CONNECTIVITY_NONE;
+	if (priv->concheck_p_max_interval == 0) {
+		/* periodic check is disabled. Nothing to do. */
+		return;
+	}
+
+	if (!priv->concheck_p_cur_id) {
+		/* we currently don't have a timeout scheduled. No need to reschedule
+		 * another one... */
+		if (mode == CONCHECK_SCHEDULE_UPDATE_INTERVAL) {
+			/* ... unless, we are initalizing. In this case, setup the current current
+			 * interval and schedule a perform a check right away.  */
+			priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_max_interval, CONCHECK_P_PROBE_INTERVAL);
+			priv->concheck_p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+			if (concheck_periodic_schedule_do (self, priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND))
+				concheck_start (self, NULL, NULL, TRUE);
 		}
+		return;
 	}
 
-	if (priv->connectivity_state != state) {
-#if WITH_CONCHECK
-		_LOGD (LOGD_CONCHECK, "state changed from %s to %s",
-		       nm_connectivity_state_to_string (priv->connectivity_state),
-		       nm_connectivity_state_to_string (state));
-#endif
-		priv->connectivity_state = state;
-		_notify (self, PROP_CONNECTIVITY);
+	switch (mode) {
+	case CONCHECK_SCHEDULE_UPDATE_INTERVAL:
+		/* called with "UPDATE_INTERVAL" and already have a concheck_p_cur_id scheduled. */
 
-		if (   priv->state == NM_DEVICE_STATE_ACTIVATED
-		    && !nm_device_sys_iface_state_is_external (self)) {
-			if (   nm_device_get_best_default_route (self, AF_INET)
-			    && !ip4_config_merge_and_apply (self, TRUE))
-				_LOGW (LOGD_IP4, "Failed to update IPv4 route metric");
-			if (   nm_device_get_best_default_route (self, AF_INET6)
-			    && !ip6_config_merge_and_apply (self, TRUE))
-				_LOGW (LOGD_IP6, "Failed to update IPv6 route metric");
+		nm_assert (priv->concheck_p_max_interval > 0);
+		nm_assert (priv->concheck_p_cur_interval > 0);
+
+		if (priv->concheck_p_cur_interval <= priv->concheck_p_max_interval) {
+			/* we currently have a shorter interval set, than what we now have. Either,
+			 * because we are probing, or because the previous max interval was shorter.
+			 *
+			 * Either way, the current timer is set just fine. Nothing to do, we will
+			 * probe our way up. */
+			return;
 		}
+
+		cur_expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_max_interval * NM_UTILS_NS_PER_SECOND);
+		nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+
+		priv->concheck_p_cur_interval = priv->concheck_p_max_interval;
+		if (cur_expiry <= now_ns) {
+			/* Since the last time we scheduled a periodic check, already more than the
+			 * new max_interval passed. We need to start a check right away (and
+			 * schedule a timeout in cur-interval in the future). */
+			priv->concheck_p_cur_basetime_ns = now_ns;
+			if (concheck_periodic_schedule_do (self, priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND))
+				concheck_start (self, NULL, NULL, TRUE);
+		} else {
+			/* we are reducing the max-interval to a shorter interval that we have currently
+			 * scheduled (with cur_interval).
+			 *
+			 * However, since the last time we scheduled the check, not even the new max-interval
+			 * expired. All we need to do, is reschedule the timer to expire sooner. The cur_basetime
+			 * is unchanged. */
+			concheck_periodic_schedule_do (self, cur_expiry - now_ns);
+		}
+		return;
+
+	case CONCHECK_SCHEDULE_CHECK_EXTERNAL:
+		/* a external connectivity check delays our periodic check. We reset the counter. */
+		priv->concheck_p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+		concheck_periodic_schedule_do (self, priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+		return;
+
+	case CONCHECK_SCHEDULE_CHECK_PERIODIC:
+	{
+		gboolean any_periodic_pending;
+		NMDeviceConnectivityHandle *handle;
+		guint old_interval = priv->concheck_p_cur_interval;
+
+		any_periodic_pending = FALSE;
+		c_list_for_each_entry (handle, &priv->concheck_lst_head, concheck_lst) {
+			if (handle->is_periodic_bump) {
+				handle->is_periodic_bump = FALSE;
+				handle->is_periodic_bump_on_complete = FALSE;
+				any_periodic_pending = TRUE;
+			}
+		}
+		if (any_periodic_pending) {
+			/* we reached a timeout to schedule a new periodic request, however we still
+			 * have period requests pending that didn't complete yet. We need to bump the
+			 * interval already. */
+			priv->concheck_p_cur_interval = NM_MIN (old_interval * 2, priv->concheck_p_max_interval);
+		}
+
+		/* we just reached a timeout. The expected expiry (exp_expiry) should be
+		 * pretty close to now_ns.
+		 *
+		 * We want to reschedule the timeout at exp_expiry (aka now) + cur_interval. */
+		nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+		exp_expiry = priv->concheck_p_cur_basetime_ns + (old_interval * NM_UTILS_NS_PER_SECOND);
+		new_expiry = exp_expiry + (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+		tdiff = NM_MAX (new_expiry - now_ns, 0);
+		priv->concheck_p_cur_basetime_ns = (now_ns + tdiff) - (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+		concheck_periodic_schedule_do (self, tdiff);
+		handle = concheck_start (self, NULL, NULL, TRUE);
+		if (old_interval != priv->concheck_p_cur_interval) {
+			/* we just bumped the interval already when scheduling this check.
+			 * When the handle returns, don't bump a second time.
+			 *
+			 * But if we reach the timeout again before the handle returns (this
+			 * code here) we will still bump the interval. */
+			handle->is_periodic_bump_on_complete = FALSE;
+		}
+		return;
 	}
-}
 
-typedef struct {
-	NMDevice *self;
-	NMDeviceConnectivityCallback callback;
-	gpointer user_data;
-	guint64 seq;
-} ConnectivityCheckData;
+	/* we just got an event that we lost connectivity (that is, concheck returned). We reset
+	 * the interval to min/max or increase the probe interval (bump). */
+	case CONCHECK_SCHEDULE_RETURNED_MIN:
+		priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_max_interval, CONCHECK_P_PROBE_INTERVAL);
+		break;
+	case CONCHECK_SCHEDULE_RETURNED_MAX:
+		priv->concheck_p_cur_interval = priv->concheck_p_max_interval;
+		break;
+	case CONCHECK_SCHEDULE_RETURNED_BUMP:
+		priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_cur_interval * 2, priv->concheck_p_max_interval);
+		break;
+	}
 
-static void
-concheck_done (ConnectivityCheckData *data)
+	/* we are here, because we returned from a connectivity check and adjust the current interval.
+	 *
+	 * But note that we calculate the new timeout based on the time when we scheduled the
+	 * last check, instead of counting from now. The reaons is, that we want that the times
+	 * when we schedule checks be at precise intervals, without including the time it took for
+	 * the connectivity check. */
+	new_expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	tdiff = NM_MAX (new_expiry - nm_utils_get_monotonic_timestamp_ns_cached (&now_ns), 0);
+	priv->concheck_p_cur_basetime_ns = now_ns + tdiff - (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	concheck_periodic_schedule_do (self, tdiff);
+}
+
+void
+nm_device_check_connectivity_update_interval (NMDevice *self)
 {
-	NMDevice *self = data->self;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	guint new_interval;
+
+	new_interval = nm_connectivity_get_interval (concheck_get_mgr (self));
+
+	new_interval = NM_MIN (new_interval, 7 *24 * 3600);
+
+	if (new_interval != priv->concheck_p_max_interval) {
+		_LOGT (LOGD_CONCHECK, "connectivity: periodic-check: set interval to %u seconds", new_interval);
+		priv->concheck_p_max_interval = new_interval;
+	}
+
+	if (!new_interval) {
+		/* this will cancel any potentially pending timeout. */
+		concheck_periodic_schedule_do (self, 0);
+
+		/* also update the fake connectivity state. */
+		concheck_update_state (self, NM_CONNECTIVITY_FAKE, TRUE);
+		return;
+	}
 
-	/* The unsolicited connectivity checks don't hook a callback. */
-	if (data->callback)
-		data->callback (data->self, priv->connectivity_state, data->user_data);
-	g_object_unref (data->self);
-	g_slice_free (ConnectivityCheckData, data);
+	concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_UPDATE_INTERVAL);
 }
 
-#if WITH_CONCHECK
 static void
-concheck_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
+concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow_periodic_bump)
 {
-	ConnectivityCheckData *data = user_data;
-	NMDevice *self = data->self;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnectivity *connectivity = NM_CONNECTIVITY (source_object);
-	NMConnectivityState state;
-	GError *error = NULL;
 
-	state = nm_connectivity_check_finish (connectivity, result, &error);
-	if (error) {
-		_LOGW (LOGD_DEVICE, "connectivity checking on '%s' failed: %s",
-		       nm_device_get_iface (self), error->message);
-		g_error_free (error);
+	/* @state is a result of the connectivity check. We only expect a precise
+	 * number of possible values. */
+	nm_assert (NM_IN_SET (state, NM_CONNECTIVITY_LIMITED,
+	                             NM_CONNECTIVITY_PORTAL,
+	                             NM_CONNECTIVITY_FULL,
+	                             NM_CONNECTIVITY_FAKE,
+	                             NM_CONNECTIVITY_ERROR));
+
+	if (state == NM_CONNECTIVITY_ERROR) {
+		/* on error, we don't change the current connectivity state,
+		 * except making UNKNOWN to NONE. */
+		state = priv->connectivity_state;
+		if (state == NM_CONNECTIVITY_UNKNOWN)
+			state = NM_CONNECTIVITY_NONE;
+	} else if (state == NM_CONNECTIVITY_FAKE) {
+		/* If the connectivity check is disabled and we obtain a fake
+		 * result, make an optimistic guess. */
+		if (priv->state == NM_DEVICE_STATE_ACTIVATED) {
+			/* FIXME: the fake connectivity state depends on the availablility of
+			 * a default route. However, we have no mechanism that rechecks the
+			 * value if a device route appears/disappears after the device
+			 * was activated. */
+			if (nm_device_get_best_default_route (self, AF_UNSPEC))
+				state = NM_CONNECTIVITY_FULL;
+			else
+				state = NM_CONNECTIVITY_LIMITED;
+		} else
+			state = NM_CONNECTIVITY_NONE;
+	}
+
+	if (priv->connectivity_state == state) {
+		/* we got a connectivty update, but the state didn't change. If we were probing,
+		 * we bump the probe frequency. */
+		if (allow_periodic_bump)
+			concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_BUMP);
+		return;
+	}
+	/* we need to update the probe interval before emitting signals. Emitting
+	 * a signal might call back into NMDevice and change the probe settings.
+	 * So, do that first. */
+	if (state == NM_CONNECTIVITY_FULL) {
+		/* we reached full connectivity state. Stop probing by setting the
+		 * interval to the max. */
+		concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_MAX);
+	} else if (priv->connectivity_state == NM_CONNECTIVITY_FULL) {
+		/* we are about to loose connectivity. (re)start probing by setting
+		 * the timeout interval to the min. */
+		concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_MIN);
+	} else {
+		if (allow_periodic_bump)
+			concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_BUMP);
 	}
 
-	if (data->seq == priv->concheck_seq)
-		update_connectivity_state (data->self, state);
-	concheck_done (data);
+	_LOGD (LOGD_CONCHECK, "connectivity state changed from %s to %s",
+	       nm_connectivity_state_to_string (priv->connectivity_state),
+	       nm_connectivity_state_to_string (state));
+	priv->connectivity_state = state;
+
+	_notify (self, PROP_CONNECTIVITY);
+	g_signal_emit (self, signals[CONNECTIVITY_CHANGED], 0);
+
+	if (   priv->state == NM_DEVICE_STATE_ACTIVATED
+	    && !nm_device_sys_iface_state_is_external (self)) {
+		if (   nm_device_get_best_default_route (self, AF_INET)
+		    && !ip_config_merge_and_apply (self, AF_INET, TRUE))
+			_LOGW (LOGD_IP4, "Failed to update IPv4 route metric");
+		if (   nm_device_get_best_default_route (self, AF_INET6)
+		    && !ip_config_merge_and_apply (self, AF_INET6, TRUE))
+			_LOGW (LOGD_IP6, "Failed to update IPv6 route metric");
+	}
 }
-#endif /* WITH_CONCHECK */
 
-static gboolean
-no_concheck (gpointer user_data)
+static void
+concheck_handle_complete (NMDeviceConnectivityHandle *handle,
+                          GError *error)
 {
-	ConnectivityCheckData *data = user_data;
+	/* The moment we invoke the callback, we unlink it. It signals
+	 * that @handle is handled -- as far as the callee of callback
+	 * is concerned. */
+	c_list_unlink (&handle->concheck_lst);
 
-	concheck_done (data);
-	return G_SOURCE_REMOVE;
+	if (handle->c_handle)
+		nm_connectivity_check_cancel (handle->c_handle);
+
+	if (handle->callback) {
+		handle->callback (handle->self,
+		                  handle,
+		                  NM_DEVICE_GET_PRIVATE (handle->self)->connectivity_state,
+		                  error,
+		                  handle->user_data);
+	}
+
+	g_slice_free (NMDeviceConnectivityHandle, handle);
 }
 
-void
-nm_device_check_connectivity (NMDevice *self,
-                              NMDeviceConnectivityCallback callback,
-                              gpointer user_data)
+static void
+concheck_cb (NMConnectivity *connectivity,
+             NMConnectivityCheckHandle *c_handle,
+             NMConnectivityState state,
+             GError *error,
+             gpointer user_data)
 {
-	ConnectivityCheckData *data;
-#if WITH_CONCHECK
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-#endif
+	_nm_unused gs_unref_object NMDevice *self_keep_alive = NULL;
+	NMDevice *self;
+	NMDevicePrivate *priv;
+	NMDeviceConnectivityHandle *handle;
+	NMDeviceConnectivityHandle *other_handle;
+	gboolean handle_is_alive;
+	gboolean allow_periodic_bump;
+	gboolean any_periodic_before;
+	gboolean any_periodic_after;
+	guint64 seq;
 
-	data = g_slice_new0 (ConnectivityCheckData);
-	data->self = g_object_ref (self);
-	data->callback = callback;
-	data->user_data = user_data;
+	handle = user_data;
+	nm_assert (handle->c_handle == c_handle);
+	nm_assert (NM_IS_DEVICE (handle->self));
 
-#if WITH_CONCHECK
-	if (priv->concheck_periodic_id) {
-		data->seq = ++priv->concheck_seq;
+	handle->c_handle = NULL;
+	self = handle->self;
 
-		/* Kick off a real connectivity check. */
-		nm_connectivity_check_async (nm_connectivity_get (),
-		                             nm_device_get_ip_iface (self),
-		                             concheck_cb,
-		                             data);
+	if (nm_utils_error_is_cancelled (error, FALSE)) {
+		/* the only place where we nm_connectivity_check_cancel(@c_handle), is
+		 * from inside concheck_handle_complete(). This is a recursive call,
+		 * nothing to do. */
+		_LOGT (LOGD_CONCHECK, "connectivity: complete check (seq:%llu, cancelled)",
+		       (long long unsigned) handle->seq);
 		return;
 	}
-#endif
 
-	/* Fake one. */
-	g_idle_add (no_concheck, data);
+	self_keep_alive = g_object_ref (self);
+
+	_LOGT (LOGD_CONCHECK, "connectivity: complete check (seq:%llu, state:%s%s%s%s)",
+	       (long long unsigned) handle->seq,
+	       nm_connectivity_state_to_string (state),
+	       NM_PRINT_FMT_QUOTED (error, ", error: ", error->message, "", ""));
+
+	/* we keep NMConnectivity instance alive. It cannot be disposing. */
+	nm_assert (!nm_utils_error_is_cancelled (error, TRUE));
+
+	/* keep @self alive, while we invoke callbacks. */
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	nm_assert (!handle || c_list_contains (&priv->concheck_lst_head, &handle->concheck_lst));
+
+	seq = handle->seq;
+
+	/* find out, if there are any periodic checks pending (either whether they
+	 * were scheduled before or after @handle. */
+	any_periodic_before = FALSE;
+	any_periodic_after = FALSE;
+	c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) {
+		if (other_handle->is_periodic_bump_on_complete) {
+			if (other_handle->seq < seq)
+				any_periodic_before = TRUE;
+			else if (other_handle->seq > seq)
+				any_periodic_after = TRUE;
+		}
+	}
+	if (NM_IN_SET (state, NM_CONNECTIVITY_ERROR)) {
+		/* the request failed. We consider this periodic check only as completed if
+		 * this was a periodic check, and there are not checks pending (either
+		 * before or after this one).
+		 *
+		 * We allow_periodic_bump, if the request failed and there are
+		 * still other requests periodic pending. */
+		allow_periodic_bump =    handle->is_periodic_bump_on_complete
+		                      && !any_periodic_before
+		                      && !any_periodic_after;
+	} else {
+		/* the request succeeded. This marks the completion of a periodic check,
+		 * if this handle was periodic, or any previously scheduled one (that
+		 * we are going to complete below). */
+		allow_periodic_bump =    handle->is_periodic_bump_on_complete
+		                      || any_periodic_before;
+	}
+
+	/* first update the new state, and emit signals. */
+	concheck_update_state (self, state, allow_periodic_bump);
+
+	handle_is_alive = FALSE;
+
+	/* we might have invoked callbacks during concheck_update_state(). The caller might have
+	 * cancelled and thus destroyed @handle. We have to check whether handle is still alive,
+	 * by searching it in the list of alive handles.
+	 *
+	 * Also, we might want to complete all pending callbacks that were started before
+	 * @handle, as they are automatically obsoleted. */
+check_handles:
+	c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) {
+		if (other_handle->seq >= seq) {
+			/* it's not guaranteed that @handle is still in the list. It might already
+			 * be canceled while invoking callbacks for a previous other_handle.
+			 * If it is already cancelled, @handle is a dangling pointer.
+			 *
+			 * Since @seq is assigned uniquely and increasing, either @other_handle is
+			 * @handle (and thus, handle is alive), or it isn't. */
+			if (other_handle == handle)
+				handle_is_alive = TRUE;
+			break;
+		}
+
+		nm_assert (other_handle != handle);
+
+		if (!NM_IN_SET (state, NM_CONNECTIVITY_ERROR)) {
+			/* we also want to complete handles that were started before the current
+			 * @handle. Their response is out-dated. */
+			concheck_handle_complete (other_handle, NULL);
+
+			/* we invoked callbacks, other handles might be cancelled and removed from the list.
+			 * Need to iterate the list from the start. */
+			goto check_handles;
+		}
+	}
+
+	if (!handle_is_alive) {
+		/* We didn't find @handle in the list of alive handles. Thus, the handles
+		 * was cancelled while we were invoking events. Nothing to do, and don't
+		 * touch the dangling pointer. */
+		return;
+	}
+
+	concheck_handle_complete (handle, NULL);
 }
 
-NMConnectivityState
-nm_device_get_connectivity_state (NMDevice *self)
+static NMDeviceConnectivityHandle *
+concheck_start (NMDevice *self,
+                NMDeviceConnectivityCallback callback,
+                gpointer user_data,
+                gboolean is_periodic)
 {
-	g_return_val_if_fail (NM_IS_DEVICE (self), NM_CONNECTIVITY_UNKNOWN);
+	static guint64 seq_counter = 0;
+	NMDevicePrivate *priv;
+	NMDeviceConnectivityHandle *handle;
 
-	return NM_DEVICE_GET_PRIVATE (self)->connectivity_state;
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	handle = g_slice_new0 (NMDeviceConnectivityHandle);
+	handle->seq = ++seq_counter;
+	handle->self = self;
+	handle->callback = callback;
+	handle->user_data = user_data;
+	handle->is_periodic = is_periodic;
+	handle->is_periodic_bump = is_periodic;
+	handle->is_periodic_bump_on_complete = is_periodic;
+
+	c_list_link_tail (&priv->concheck_lst_head, &handle->concheck_lst);
+
+	_LOGT (LOGD_CONCHECK, "connectivity: start check (seq:%llu%s)",
+	       (long long unsigned) handle->seq,
+	       is_periodic ? ", periodic-check" : "");
+
+	handle->c_handle = nm_connectivity_check_start (concheck_get_mgr (self),
+	                                                nm_device_get_ip_iface (self),
+	                                                concheck_cb,
+	                                                handle);
+	return handle;
 }
 
-#if WITH_CONCHECK
-static void
-concheck_periodic (NMConnectivity *connectivity, NMDevice *self)
+NMDeviceConnectivityHandle *
+nm_device_check_connectivity (NMDevice *self,
+                              NMDeviceConnectivityCallback callback,
+                              gpointer user_data)
 {
-	nm_device_check_connectivity (self, NULL, NULL);
+	NMDeviceConnectivityHandle *handle;
+
+	if (!concheck_is_possible (self))
+		return NULL;
+
+	concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_CHECK_EXTERNAL);
+	handle = concheck_start (self, callback, user_data, FALSE);
+	return handle;
 }
-#endif
 
-static void
-concheck_periodic_update (NMDevice *self)
+void
+nm_device_check_connectivity_cancel (NMDeviceConnectivityHandle *handle)
 {
-#if WITH_CONCHECK
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	gboolean check_enable;
+	gs_free_error GError *cancelled_error = NULL;
 
-	check_enable =    (priv->state == NM_DEVICE_STATE_ACTIVATED)
-	               && nm_device_get_best_default_route (self, AF_UNSPEC);
+	g_return_if_fail (handle);
+	g_return_if_fail (NM_IS_DEVICE (handle->self));
+	g_return_if_fail (!c_list_is_empty (&handle->concheck_lst));
 
-	if (check_enable && !priv->concheck_periodic_id) {
-		/* We just gained a default route. Enable periodic checking. */
-		priv->concheck_periodic_id = g_signal_connect (nm_connectivity_get (),
-		                                               NM_CONNECTIVITY_PERIODIC_CHECK,
-		                                               G_CALLBACK (concheck_periodic), self);
-		/* Also kick off a check right away. */
-		nm_device_check_connectivity (self, NULL, NULL);
-	} else if (!check_enable && priv->concheck_periodic_id) {
-		/* The default route has gone off, and so has connectivity. */
-		g_signal_handler_disconnect (nm_connectivity_get (), priv->concheck_periodic_id);
-		priv->concheck_periodic_id = 0;
-		update_connectivity_state (self, NM_CONNECTIVITY_NONE);
-	}
-#else
-	/* update_connectivity_state() figures out how to lie about
-	 * connectivity state if the actual state is not really known. */
-	update_connectivity_state (self, NM_CONNECTIVITY_UNKNOWN);
-#endif
+	/* nobody has access to periodic handles, and cannot cancel
+	 * them externally. */
+	nm_assert (!handle->is_periodic);
+
+	nm_utils_error_set_cancelled (&cancelled_error, FALSE, "NMDevice");
+	concheck_handle_complete (handle, cancelled_error);
+}
+
+NMConnectivityState
+nm_device_get_connectivity_state (NMDevice *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_CONNECTIVITY_UNKNOWN);
+
+	return NM_DEVICE_GET_PRIVATE (self)->connectivity_state;
 }
 
 /*****************************************************************************/
@@ -2214,7 +2848,7 @@ nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *c
 	/* Since slave devices don't have their own IP configuration,
 	 * set the MTU here.
 	 */
-	_commit_mtu (slave, NM_DEVICE_GET_PRIVATE (slave)->ip4_config);
+	_commit_mtu (slave, NM_DEVICE_GET_PRIVATE (slave)->ip_config_4);
 
 	return success;
 }
@@ -2352,8 +2986,6 @@ nm_device_update_dynamic_ip_setup (NMDevice *self)
 {
 	NMDevicePrivate *priv;
 	GError *error = NULL;
-	gconstpointer addr;
-	size_t addr_length;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
@@ -2386,8 +3018,6 @@ nm_device_update_dynamic_ip_setup (NMDevice *self)
 
 	if (priv->lldp_listener && nm_lldp_listener_is_running (priv->lldp_listener)) {
 		nm_lldp_listener_stop (priv->lldp_listener);
-		addr = nm_platform_link_get_address (nm_device_get_platform (self), priv->ifindex, &addr_length);
-
 		if (!nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self), &error)) {
 			_LOGD (LOGD_DEVICE, "LLDP listener %p could not be restarted: %s",
 			       priv->lldp_listener, error->message);
@@ -2624,12 +3254,14 @@ ndisc_set_router_config (NMNDisc *ndisc, NMDevice *self)
 
 	now = nm_utils_get_monotonic_timestamp_s ();
 
-	head_entry = nm_ip6_config_lookup_addresses (priv->ip6_config);
+	head_entry = nm_ip6_config_lookup_addresses (priv->ip_config_6);
 	addresses = g_array_sized_new (FALSE, TRUE, sizeof (NMNDiscAddress),
 	                               head_entry ? head_entry->len : 0);
 	nm_dedup_multi_iter_for_each (&ipconf_iter, head_entry) {
 		const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS (ipconf_iter.current->obj);
 		NMNDiscAddress *ndisc_addr;
+		guint32 lifetime, preferred;
+		gint32 base;
 
 		if (IN6_IS_ADDR_LINKLOCAL (&addr->address))
 			continue;
@@ -2641,19 +3273,35 @@ ndisc_set_router_config (NMNDisc *ndisc, NMDevice *self)
 		if (addr->plen != 64)
 			continue;
 
+		/* resolve the timestamps relative to a new base.
+		 *
+		 * Note that for convenience, platform @addr might have timestamp and/or
+		 * lifetime unset. We don't allow that flexibility for ndisc and require
+		 * well defined timestamps. */
+		if (addr->timestamp) {
+			nm_assert (addr->timestamp < G_MAXINT32);
+			base = addr->timestamp;
+		} else
+			base = now;
+
+		lifetime = nm_utils_lifetime_get (addr->timestamp, addr->lifetime, addr->preferred,
+		                                  base, &preferred);
+		if (!lifetime)
+			continue;
+
 		g_array_set_size (addresses, addresses->len+1);
 		ndisc_addr = &g_array_index (addresses, NMNDiscAddress, addresses->len-1);
 		ndisc_addr->address = addr->address;
-		ndisc_addr->timestamp = addr->timestamp;
-		ndisc_addr->lifetime = addr->lifetime;
-		ndisc_addr->preferred = addr->preferred;
+		ndisc_addr->timestamp = base;
+		ndisc_addr->lifetime = lifetime;
+		ndisc_addr->preferred = preferred;
 	}
 
-	len = nm_ip6_config_get_num_nameservers (priv->ip6_config);
+	len = nm_ip6_config_get_num_nameservers (priv->ip_config_6);
 	dns_servers = g_array_sized_new (FALSE, TRUE, sizeof (NMNDiscDNSServer), len);
 	g_array_set_size (dns_servers, len);
 	for (i = 0; i < len; i++) {
-		const struct in6_addr *nameserver = nm_ip6_config_get_nameserver (priv->ip6_config, i);
+		const struct in6_addr *nameserver = nm_ip6_config_get_nameserver (priv->ip_config_6, i);
 		NMNDiscDNSServer *ndisc_nameserver;
 
 		ndisc_nameserver = &g_array_index (dns_servers, NMNDiscDNSServer, i);
@@ -2662,11 +3310,11 @@ ndisc_set_router_config (NMNDisc *ndisc, NMDevice *self)
 		ndisc_nameserver->lifetime = NM_NDISC_ROUTER_LIFETIME;
 	}
 
-	len = nm_ip6_config_get_num_searches (priv->ip6_config);
+	len = nm_ip6_config_get_num_searches (priv->ip_config_6);
 	dns_domains = g_array_sized_new (FALSE, TRUE, sizeof (NMNDiscDNSDomain), len);
 	g_array_set_size (dns_domains, len);
 	for (i = 0; i < len; i++) {
-		const char *search = nm_ip6_config_get_search (priv->ip6_config, i);
+		const char *search = nm_ip6_config_get_search (priv->ip_config_6, i);
 		NMNDiscDNSDomain *ndisc_search;
 
 		ndisc_search = &g_array_index (dns_domains, NMNDiscDNSDomain, i);
@@ -2792,11 +3440,11 @@ device_link_changed (NMDevice *self)
 		/* the link was down and just came up. That happens for example, while changing MTU.
 		 * We must restore IP configuration. */
 		if (priv->ip4_state == IP_DONE) {
-			if (!ip4_config_merge_and_apply (self, TRUE))
+			if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 				_LOGW (LOGD_IP4, "failed applying IP4 config after link comes up again");
 		}
 		if (priv->ip6_state == IP_DONE) {
-			if (!ip6_config_merge_and_apply (self, TRUE))
+			if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 				_LOGW (LOGD_IP6, "failed applying IP6 config after link comes up again");
 		}
 	}
@@ -2981,7 +3629,7 @@ ip4_rp_filter_update (NMDevice *self)
 
 	if (   priv->v4_has_shadowed_routes
 	    || nm_device_get_best_default_route (self, AF_INET)) {
-		if (nm_device_ipv4_sysctl_get_uint32 (self, "rp_filter", 0) != 1) {
+		if (nm_device_ipv4_sysctl_get_effective_uint32 (self, "rp_filter", 0) != 1) {
 			/* Don't touch the rp_filter if it's not strict. */
 			return;
 		}
@@ -3300,8 +3948,8 @@ realize_start_setup (NMDevice *self,
 	g_return_if_fail (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT));
 	g_return_if_fail (priv->ip_ifindex <= 0);
 	g_return_if_fail (priv->ip_iface == NULL);
-	g_return_if_fail (!priv->queued_ip4_config_id);
-	g_return_if_fail (!priv->queued_ip6_config_id);
+	g_return_if_fail (!priv->queued_ip_config_id_4);
+	g_return_if_fail (!priv->queued_ip_config_id_6);
 
 	_LOGD (LOGD_DEVICE, "start setup of %s, kernel ifindex %d", G_OBJECT_TYPE_NAME (self), plink ? plink->ifindex : 0);
 
@@ -3348,7 +3996,7 @@ realize_start_setup (NMDevice *self,
 
 		if (nm_platform_check_kernel_support (nm_device_get_platform (self),
 		                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
-			priv->nm_ipv6ll = nm_platform_link_get_user_ipv6ll_enabled (nm_device_get_platform (self), priv->ifindex);
+			priv->ipv6ll_handle = nm_platform_link_get_user_ipv6ll_enabled (nm_device_get_platform (self), priv->ifindex);
 
 		if (nm_platform_link_supports_sriov (nm_device_get_platform (self), priv->ifindex))
 			capabilities |= NM_DEVICE_CAP_SRIOV;
@@ -3447,6 +4095,9 @@ nm_device_realize_finish (NMDevice *self, const NMPlatformLink *plink)
 	if (plink)
 		device_recheck_slave_status (self, plink);
 
+	priv->update_ip_config_completed_v4 = FALSE;
+	priv->update_ip_config_completed_v6 = FALSE;
+
 	priv->real = TRUE;
 	_notify (self, PROP_REAL);
 
@@ -3672,7 +4323,7 @@ nm_device_notify_component_added (NMDevice *self, GObject *component)
  * because that ethernet interface is controlled by the WWAN device and cannot
  * be used independently of the WWAN device.
  *
- * Returns: %TRUE if @self or it's components owns the interface name,
+ * Returns: %TRUE if @self or its components own the interface name,
  * %FALSE if not
  */
 gboolean
@@ -3962,7 +4613,7 @@ check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 
 	/* Don't progress into IP_CHECK or SECONDARIES if we're waiting for the
 	 * master to enslave us. */
-	if (   nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request))
+	if (   nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request.obj))
 	    && !priv->is_enslaved)
 		return;
 
@@ -4150,8 +4801,8 @@ nm_device_removed (NMDevice *self, gboolean unconfigure_ip_config)
 	if (!unconfigure_ip_config)
 		return;
 
-	nm_device_set_ip4_config (self, NULL, FALSE, NULL);
-	nm_device_set_ip6_config (self, NULL, FALSE);
+	nm_device_set_ip_config (self, AF_INET, NULL, FALSE, NULL);
+	nm_device_set_ip_config (self, AF_INET6, NULL, FALSE, NULL);
 }
 
 static gboolean
@@ -4403,9 +5054,9 @@ device_has_config (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	/* Check for IP configuration. */
-	if (priv->ip4_config && nm_ip4_config_get_num_addresses (priv->ip4_config))
+	if (priv->ip_config_4 && nm_ip4_config_get_num_addresses (priv->ip_config_4))
 		return TRUE;
-	if (priv->ip6_config && nm_ip6_config_get_num_addresses (priv->ip6_config))
+	if (priv->ip_config_6 && nm_ip6_config_get_num_addresses (priv->ip_config_6))
 		return TRUE;
 
 	/* The existence of a software device is good enough. */
@@ -4530,10 +5181,10 @@ nm_device_generate_connection (NMDevice *self,
 		}
 	} else {
 		/* Only regular and master devices get IP configuration; slaves do not */
-		s_ip4 = nm_ip4_config_create_setting (priv->ip4_config);
+		s_ip4 = nm_ip4_config_create_setting (priv->ip_config_4);
 		nm_connection_add_setting (connection, s_ip4);
 
-		s_ip6 = nm_ip6_config_create_setting (priv->ip6_config);
+		s_ip6 = nm_ip6_config_create_setting (priv->ip_config_6);
 		nm_connection_add_setting (connection, s_ip6);
 
 		nm_connection_add_setting (connection, nm_setting_proxy_new ());
@@ -4590,34 +5241,45 @@ nm_device_generate_connection (NMDevice *self,
 	return g_steal_pointer (&connection);
 }
 
+/**
+ * nm_device_complete_connection:
+ *
+ * Complete the connection. This is solely used for AddAndActivate where the user
+ * may pass in an incomplete connection and a device, and the device tries to
+ * make sense of it and complete it for activation. Otherwise, this is not
+ * used.
+ *
+ * Returns: success or failure.
+ */
 gboolean
 nm_device_complete_connection (NMDevice *self,
                                NMConnection *connection,
                                const char *specific_object,
-                               const GSList *existing_connections,
+                               NMConnection *const*existing_connections,
                                GError **error)
 {
-	gboolean success = FALSE;
+	NMDeviceClass *klass;
 
-	g_return_val_if_fail (self != NULL, FALSE);
-	g_return_val_if_fail (connection != NULL, FALSE);
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+
+	klass = NM_DEVICE_GET_CLASS (self);
 
-	if (!NM_DEVICE_GET_CLASS (self)->complete_connection) {
+	if (!klass->complete_connection) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
 		             "Device class %s had no complete_connection method",
 		             G_OBJECT_TYPE_NAME (self));
 		return FALSE;
 	}
 
-	success = NM_DEVICE_GET_CLASS (self)->complete_connection (self,
-	                                                           connection,
-	                                                           specific_object,
-	                                                           existing_connections,
-	                                                           error);
-	if (success)
-		success = nm_connection_verify (connection, error);
+	if (!klass->complete_connection (self,
+	                                 connection,
+	                                 specific_object,
+	                                 existing_connections,
+	                                 error))
+		return FALSE;
 
-	return success;
+	return nm_connection_verify (connection, error);
 }
 
 gboolean
@@ -4726,15 +5388,12 @@ nm_device_check_connection_compatible (NMDevice *self, NMConnection *connection)
 gboolean
 nm_device_check_slave_connection_compatible (NMDevice *self, NMConnection *slave)
 {
-	NMDevicePrivate *priv;
 	NMSettingConnection *s_con;
 	const char *connection_type, *slave_type;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 	g_return_val_if_fail (NM_IS_CONNECTION (slave), FALSE);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-
 	if (!nm_device_is_master (self))
 		return FALSE;
 
@@ -5147,8 +5806,9 @@ activate_stage1_device_prepare (NMDevice *self)
 	_set_ip_state (self, AF_INET6, IP_NONE);
 
 	/* Notify the new ActiveConnection along with the state change */
-	priv->act_request_public = TRUE;
-	_notify (self, PROP_ACTIVE_CONNECTION);
+	nm_dbus_track_obj_path_set (&priv->act_request,
+	                            priv->act_request.obj,
+	                            TRUE);
 
 	nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
 
@@ -5184,7 +5844,7 @@ nm_device_activate_schedule_stage1_device_prepare (NMDevice *self)
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
 	activation_source_schedule (self, activate_stage1_device_prepare, AF_INET);
 }
@@ -5202,8 +5862,6 @@ lldp_init (NMDevice *self, gboolean restart)
 
 	if (priv->ifindex > 0 && lldp_rx_enabled (self)) {
 		gs_free_error GError *error = NULL;
-		gconstpointer addr;
-		size_t addr_length;
 
 		if (priv->lldp_listener) {
 			if (restart && nm_lldp_listener_is_running (priv->lldp_listener))
@@ -5217,8 +5875,6 @@ lldp_init (NMDevice *self, gboolean restart)
 		}
 
 		if (!nm_lldp_listener_is_running (priv->lldp_listener)) {
-			addr = nm_platform_link_get_address (nm_device_get_platform (self), priv->ifindex, &addr_length);
-
 			if (nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self), &error))
 				_LOGD (LOGD_DEVICE, "LLDP listener %p started", priv->lldp_listener);
 			else {
@@ -5365,7 +6021,7 @@ activate_stage2_device_config (NMDevice *self)
 
 		if (slave_state == NM_DEVICE_STATE_IP_CONFIG)
 			nm_device_master_enslave_slave (self, info->slave, nm_device_get_applied_connection (info->slave));
-		else if (   priv->act_request
+		else if (   priv->act_request.obj
 		         && nm_device_sys_iface_state_is_external (self)
 		         && slave_state <= NM_DEVICE_STATE_DISCONNECTED)
 			nm_device_queue_recheck_assume (info->slave);
@@ -5390,10 +6046,10 @@ nm_device_activate_schedule_stage2_device_config (NMDevice *self)
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
 	if (!priv->master_ready_handled) {
-		NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request);
+		NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request.obj);
 		NMActiveConnection *master;
 
 		master = nm_active_connection_get_master (active);
@@ -5431,13 +6087,9 @@ nm_device_ip_method_failed (NMDevice *self,
                             int addr_family,
                             NMDeviceStateReason reason)
 {
-	NMDevicePrivate *priv;
-
 	g_return_if_fail (NM_IS_DEVICE (self));
 	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-
 	_set_ip_state (self, addr_family, IP_FAIL);
 
 	if (get_ip_config_may_fail (self, addr_family))
@@ -5478,16 +6130,16 @@ get_ipv4_dad_timeout (NMDevice *self)
 }
 
 static void
-arping_data_destroy (gpointer ptr, GClosure *closure)
+acd_data_destroy (gpointer ptr, GClosure *closure)
 {
-	ArpingData *data = ptr;
+	AcdData *data = ptr;
 	int i;
 
 	if (data) {
 		for (i = 0; data->configs && data->configs[i]; i++)
 			g_object_unref (data->configs[i]);
 		g_free (data->configs);
-		g_slice_free (ArpingData, data);
+		g_slice_free (AcdData, data);
 	}
 }
 
@@ -5507,7 +6159,7 @@ ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean succes
 }
 
 static void
-arping_manager_probe_terminated (NMArpingManager *arping_manager, ArpingData *data)
+acd_manager_probe_terminated (NMAcdManager *acd_manager, AcdData *data)
 {
 	NMDevice *self;
 	NMDevicePrivate *priv;
@@ -5522,7 +6174,7 @@ arping_manager_probe_terminated (NMArpingManager *arping_manager, ArpingData *da
 
 	for (i = 0; data->configs && data->configs[i]; i++) {
 		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, data->configs[i], &address) {
-			result = nm_arping_manager_check_address (arping_manager, address->address);
+			result = nm_acd_manager_check_address (acd_manager, address->address);
 			success &= result;
 
 			_NMLOG (result ? LOGL_DEBUG : LOGL_WARN,
@@ -5535,8 +6187,8 @@ arping_manager_probe_terminated (NMArpingManager *arping_manager, ArpingData *da
 
 	data->callback (self, data->configs, success);
 
-	priv->arping.dad_list = g_slist_remove (priv->arping.dad_list, arping_manager);
-	nm_arping_manager_destroy (arping_manager);
+	priv->acd.dad_list = g_slist_remove (priv->acd.dad_list, acd_manager);
+	nm_acd_manager_destroy (acd_manager);
 }
 
 /**
@@ -5550,18 +6202,17 @@ arping_manager_probe_terminated (NMArpingManager *arping_manager, ArpingData *da
  * be started. @configs will be unreferenced after @cb has been called.
  */
 static void
-ipv4_dad_start (NMDevice *self, NMIP4Config **configs, ArpingCallback cb)
+ipv4_dad_start (NMDevice *self, NMIP4Config **configs, AcdCallback cb)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMArpingManager *arping_manager;
+	NMAcdManager *acd_manager;
 	const NMPlatformIP4Address *address;
 	NMDedupMultiIter ipconf_iter;
-	ArpingData *data;
+	AcdData *data;
 	guint timeout;
 	gboolean ret, addr_found;
-	const guint8 *hw_addr;
-	size_t hw_addr_len = 0;
-	GError *error = NULL;
+	const guint8 *hwaddr_arr;
+	size_t length;
 	guint i;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
@@ -5576,14 +6227,14 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, ArpingCallback cb)
 	}
 
 	timeout = get_ipv4_dad_timeout (self);
-	hw_addr = nm_platform_link_get_address (nm_device_get_platform (self),
-	                                        nm_device_get_ip_ifindex (self),
-	                                        &hw_addr_len);
+	hwaddr_arr = nm_platform_link_get_address (nm_device_get_platform (self),
+	                                           nm_device_get_ip_ifindex (self),
+	                                           &length);
 
 	if (   !timeout
-	    || !hw_addr
-	    || !hw_addr_len
+	    || !hwaddr_arr
 	    || !addr_found
+	    || length != ETH_ALEN
 	    || nm_device_sys_iface_state_is_external_or_assume (self)) {
 
 		/* DAD not needed, signal success */
@@ -5596,36 +6247,36 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, ArpingCallback cb)
 		return;
 	}
 
-	/* don't take additional references of @arping_manager that outlive @self.
+	/* don't take additional references of @acd_manager that outlive @self.
 	 * Otherwise, the callback can be invoked on a dangling pointer as we don't
 	 * disconnect the handler. */
-	arping_manager = nm_arping_manager_new (nm_device_get_ip_ifindex (self));
-	priv->arping.dad_list = g_slist_append (priv->arping.dad_list, arping_manager);
+	acd_manager = nm_acd_manager_new (nm_device_get_ip_ifindex (self), hwaddr_arr, length);
+	priv->acd.dad_list = g_slist_append (priv->acd.dad_list, acd_manager);
 
-	data = g_slice_new0 (ArpingData);
+	data = g_slice_new0 (AcdData);
 	data->configs = configs;
 	data->callback = cb;
 	data->device = self;
 
 	for (i = 0; configs[i]; i++) {
 		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, configs[i], &address)
-			nm_arping_manager_add_address (arping_manager, address->address);
+			nm_acd_manager_add_address (acd_manager, address->address);
 	}
 
-	g_signal_connect_data (arping_manager, NM_ARPING_MANAGER_PROBE_TERMINATED,
-	                       G_CALLBACK (arping_manager_probe_terminated), data,
-	                       arping_data_destroy, 0);
+	g_signal_connect_data (acd_manager, NM_ACD_MANAGER_PROBE_TERMINATED,
+	                       G_CALLBACK (acd_manager_probe_terminated), data,
+	                       acd_data_destroy, 0);
 
-	ret = nm_arping_manager_start_probe (arping_manager, timeout, &error);
+	ret = nm_acd_manager_start_probe (acd_manager, timeout);
 
 	if (!ret) {
-		_LOGW (LOGD_DEVICE, "arping probe failed: %s", error->message);
+		_LOGW (LOGD_DEVICE, "acd probe failed");
 
 		/* DAD could not be started, signal success */
 		cb (self, configs, TRUE);
 
-		priv->arping.dad_list = g_slist_remove (priv->arping.dad_list, arping_manager);
-		nm_arping_manager_destroy (arping_manager);
+		priv->acd.dad_list = g_slist_remove (priv->acd.dad_list, acd_manager);
+		nm_acd_manager_destroy (acd_manager);
 	}
 }
 
@@ -5687,10 +6338,10 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 	NMIP4Config *config;
 	int r;
 
-	if (priv->act_request == NULL)
+	if (priv->act_request.obj == NULL)
 		return;
 
-	connection = nm_act_request_get_applied_connection (priv->act_request);
+	connection = nm_act_request_get_applied_connection (priv->act_request.obj);
 	g_assert (connection);
 
 	/* Ignore if the connection isn't an AutoIP connection */
@@ -5724,9 +6375,8 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 			nm_clear_g_source (&priv->ipv4ll_timeout);
 			nm_device_activate_schedule_ip4_config_result (self, config);
 		} else if (priv->ip4_state == IP_DONE) {
-			g_clear_object (&priv->dev_ip4_config);
-			priv->dev_ip4_config = g_object_ref (config);
-			if (!ip4_config_merge_and_apply (self, TRUE)) {
+			applied_config_init (&priv->dev_ip4_config, config);
+			if (!ip_config_merge_and_apply (self, AF_INET, TRUE)) {
 				_LOGE (LOGD_AUTOIP4, "failed to update IP4 config for autoip change.");
 				nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
 			}
@@ -5822,55 +6472,42 @@ ipv4ll_start (NMDevice *self)
 /*****************************************************************************/
 
 static void
-ensure_con_ip4_config (NMDevice *self)
+ensure_con_ip_config (NMDevice *self, int addr_family)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+	NMIPConfig *con_ip_config;
 
-	if (priv->con_ip4_config)
+	if (priv->con_ip_config_x[IS_IPv4])
 		return;
 
 	connection = nm_device_get_applied_connection (self);
 	if (!connection)
 		return;
 
-	priv->con_ip4_config = _ip4_config_new (self);
-	nm_ip4_config_merge_setting (priv->con_ip4_config,
-	                             nm_connection_get_setting_ip4_config (connection),
-	                             nm_device_get_route_table (self, AF_INET, TRUE),
-	                             nm_device_get_route_metric (self, AF_INET));
+	con_ip_config = _ip_config_new (self, addr_family);
 
-	if (nm_device_sys_iface_state_is_external_or_assume (self)) {
-		/* For assumed connections ignore all addresses and routes. */
-		nm_ip4_config_reset_addresses (priv->con_ip4_config);
-		nm_ip4_config_reset_routes (priv->con_ip4_config);
+	if (IS_IPv4) {
+		nm_ip4_config_merge_setting (NM_IP4_CONFIG (con_ip_config),
+		                             nm_connection_get_setting_ip4_config (connection),
+		                             _get_mdns (self),
+		                             nm_device_get_route_table (self, addr_family, TRUE),
+		                             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_metric (self, addr_family));
 	}
-}
-
-static void
-ensure_con_ip6_config (NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
-
-	if (priv->con_ip6_config)
-		return;
-
-	connection = nm_device_get_applied_connection (self);
-	if (!connection)
-		return;
-
-	priv->con_ip6_config = _ip6_config_new (self);
-	nm_ip6_config_merge_setting (priv->con_ip6_config,
-	                             nm_connection_get_setting_ip6_config (connection),
-	                             nm_device_get_route_table (self, AF_INET6, TRUE),
-	                             nm_device_get_route_metric (self, AF_INET6));
 
 	if (nm_device_sys_iface_state_is_external_or_assume (self)) {
 		/* For assumed connections ignore all addresses and routes. */
-		nm_ip6_config_reset_addresses (priv->con_ip6_config);
-		nm_ip6_config_reset_routes (priv->con_ip6_config);
+		nm_ip_config_reset_addresses (con_ip_config);
+		nm_ip_config_reset_routes (con_ip_config);
 	}
+
+	priv->con_ip_config_x[IS_IPv4] = con_ip_config;
 }
 
 /*****************************************************************************/
@@ -5881,7 +6518,7 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->dhcp4.restart_id);
+	nm_clear_g_source (&priv->dhcp4.grace_id);
 	g_clear_pointer (&priv->dhcp4.pac_url, g_free);
 
 	if (priv->dhcp4.client) {
@@ -5898,105 +6535,222 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	}
 
 	if (priv->dhcp4.config) {
-		nm_exported_object_clear_and_unexport (&priv->dhcp4.config);
+		nm_dbus_object_clear_and_unexport (&priv->dhcp4.config);
 		_notify (self, PROP_DHCP4_CONFIG);
 	}
 }
 
 static gboolean
-ip4_config_merge_and_apply (NMDevice *self,
-                            gboolean commit)
+ip_config_merge_and_apply (NMDevice *self,
+                           int addr_family,
+                           gboolean commit)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
 	gboolean success;
-	NMIP4Config *composite;
+	gs_unref_object NMIPConfig *composite = NULL;
+	NMIPConfig *config;
+	gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
+	NMConnection *connection;
 	gboolean ignore_auto_routes = FALSE;
 	gboolean ignore_auto_dns = FALSE;
 	gboolean ignore_default_routes = FALSE;
 	GSList *iter;
-	gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
+	const char *ip6_addr_gen_token = NULL;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	if (nm_device_sys_iface_state_is_external (self))
-		commit = 0;
+		commit = FALSE;
 
-	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	connection = nm_device_get_applied_connection (self);
+
+	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	if (connection) {
-		NMSettingIPConfig *s_ip4 = nm_connection_get_setting_ip4_config (connection);
+		NMSettingIPConfig *s_ip =   IS_IPv4
+		                          ? nm_connection_get_setting_ip4_config (connection)
+		                          : nm_connection_get_setting_ip6_config (connection);
 
-		if (s_ip4) {
-			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip4);
-			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip4);
+		if (s_ip) {
+			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip);
+			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip);
 
 			/* if the connection has an explicit gateway, we also ignore
 			 * the default routes from other sources. */
-			ignore_default_routes =    nm_setting_ip_config_get_never_default (s_ip4)
-			                        || nm_setting_ip_config_get_gateway (s_ip4);
+			ignore_default_routes =    nm_setting_ip_config_get_never_default (s_ip)
+			                        || nm_setting_ip_config_get_gateway (s_ip);
+
+			if (!IS_IPv4) {
+				NMSettingIP6Config *s_ip6 = NM_SETTING_IP6_CONFIG (s_ip);
+
+				if (nm_setting_ip6_config_get_addr_gen_mode (s_ip6) == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64)
+					ip6_addr_gen_token = nm_setting_ip6_config_get_token (s_ip6);
+			}
 		}
 	}
 
-	composite = _ip4_config_new (self);
-	init_ip4_config_dns_priority (self, composite);
+	composite = _ip_config_new (self, addr_family);
+
+	if (!IS_IPv4) {
+		nm_ip6_config_set_privacy (NM_IP6_CONFIG (composite),
+		                           priv->ndisc
+		                             ? priv->ndisc_use_tempaddr
+		                             : NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+	}
+
+	init_ip_config_dns_priority (self, composite);
 
 	if (commit) {
-		if (priv->queued_ip4_config_id)
-			update_ext_ip_config (self, AF_INET, FALSE, FALSE);
-		ensure_con_ip4_config (self);
+		if (priv->queued_ip_config_id_x[IS_IPv4])
+			update_ext_ip_config (self, addr_family, FALSE);
+		ensure_con_ip_config (self, addr_family);
 	}
 
-	if (commit)
-		priv->default_route_metric_penalty_ip4_has = default_route_metric_penalty_detect (self);
+	if (!IS_IPv4) {
+		if (   commit
+		    && priv->ipv6ll_has) {
+			const NMPlatformIP6Address ll_a = {
+				.address = priv->ipv6ll_addr,
+				.plen = 64,
+				.addr_source = NM_IP_CONFIG_SOURCE_IP6LL,
+			};
+			const NMPlatformIP6Route ll_r = {
+				.network.s6_addr16[0] = htons (0xfe80u),
+				.plen = 64,
+				.metric = nm_device_get_route_metric (self, addr_family),
+				.rt_source = NM_IP_CONFIG_SOURCE_IP6LL,
+			};
 
-	if (priv->dev_ip4_config) {
-		nm_ip4_config_merge (composite, priv->dev_ip4_config,
-		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-		                     default_route_metric_penalty_get (self, AF_INET));
+			nm_assert (IN6_IS_ADDR_LINKLOCAL (&priv->ipv6ll_addr));
+
+			nm_ip6_config_add_address (NM_IP6_CONFIG (composite), &ll_a);
+			nm_ip6_config_add_route (NM_IP6_CONFIG (composite), &ll_r, NULL);
+		}
 	}
 
-	for (iter = priv->vpn4_configs; iter; iter = iter->next)
-		nm_ip4_config_merge (composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
+	if (commit) {
+		gboolean v;
+
+		v = default_route_metric_penalty_detect (self);
+		if (IS_IPv4)
+			priv->default_route_metric_penalty_ip4_has = v;
+		else
+			priv->default_route_metric_penalty_ip6_has = v;
+	}
 
-	if (priv->ext_ip4_config)
-		nm_ip4_config_merge (composite, priv->ext_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT, 0);
+	/* Merge all the IP configs into the composite config */
+
+	if (IS_IPv4) {
+		config = applied_config_get_current (&priv->dev_ip4_config);
+		if (config) {
+			nm_ip4_config_merge (NM_IP4_CONFIG (composite), NM_IP4_CONFIG (config),
+			                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+			                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+			                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+			                     default_route_metric_penalty_get (self, addr_family));
+		}
+	}
+
+	if (!IS_IPv4) {
+		config = applied_config_get_current (&priv->ac_ip6_config);
+		if (config) {
+			nm_ip6_config_merge (NM_IP6_CONFIG (composite), NM_IP6_CONFIG (config),
+			                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+			                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+			                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+			                     default_route_metric_penalty_get (self, addr_family));
+		}
+	}
+
+	if (!IS_IPv4) {
+		config = applied_config_get_current (&priv->dhcp6.ip6_config);
+		if (config) {
+			nm_ip6_config_merge (NM_IP6_CONFIG (composite), NM_IP6_CONFIG (config),
+			                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+			                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+			                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+			                     default_route_metric_penalty_get (self, addr_family));
+		}
+	}
+
+	for (iter = priv->vpn_configs_x[IS_IPv4]; iter; iter = iter->next)
+		nm_ip_config_merge (composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
+
+	if (priv->ext_ip_config_x[IS_IPv4])
+		nm_ip_config_merge (composite, priv->ext_ip_config_x[IS_IPv4], NM_IP_CONFIG_MERGE_DEFAULT, 0);
 
 	/* Merge WWAN config *last* to ensure modem-given settings overwrite
 	 * any external stuff set by pppd or other scripts.
 	 */
-	if (priv->wwan_ip4_config) {
-		nm_ip4_config_merge (composite, priv->wwan_ip4_config,
-		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-		                     default_route_metric_penalty_get (self, AF_INET));
+	config = applied_config_get_current (&priv->wwan_ip_config_x[IS_IPv4]);
+	if (config) {
+		nm_ip_config_merge (composite, config,
+		                      (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+		                    | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+		                    | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+		                    default_route_metric_penalty_get (self, addr_family));
+	}
+
+	if (!IS_IPv4) {
+		if (priv->rt6_temporary_not_available) {
+			const NMPObject *o;
+			GHashTableIter hiter;
+
+			g_hash_table_iter_init (&hiter, priv->rt6_temporary_not_available);
+			while (g_hash_table_iter_next (&hiter, (gpointer *) &o, NULL)) {
+				nm_ip6_config_add_route (NM_IP6_CONFIG (composite),
+				                         NMP_OBJECT_CAST_IP6_ROUTE (o),
+				                         NULL);
+			}
+		}
 	}
 
 	/* Merge user overrides into the composite config. For assumed connections,
-	 * con_ip4_config is empty. */
-	if (priv->con_ip4_config) {
-		nm_ip4_config_merge (composite, priv->con_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT,
-		                     default_route_metric_penalty_get (self, AF_INET));
+	 * con_ip_config_x is empty. */
+	if (priv->con_ip_config_x[IS_IPv4]) {
+		nm_ip_config_merge (composite, priv->con_ip_config_x[IS_IPv4], NM_IP_CONFIG_MERGE_DEFAULT,
+		                    default_route_metric_penalty_get (self, addr_family));
 	}
 
 	if (commit) {
-		nm_ip4_config_add_dependent_routes (composite,
-		                                    nm_device_get_route_table (self, AF_INET, TRUE),
-		                                    nm_device_get_route_metric (self, AF_INET),
-		                                    &ip4_dev_route_blacklist);
+		if (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_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_metric (self, addr_family));
+		}
 	}
 
-	if (commit) {
-		if (NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit)
-			NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit (self, composite);
+	if (IS_IPv4) {
+		if (commit) {
+			if (NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit)
+				NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit (self, NM_IP4_CONFIG (composite));
+		}
 	}
 
-	success = nm_device_set_ip4_config (self, composite, commit, ip4_dev_route_blacklist);
-	g_object_unref (composite);
+	if (!IS_IPv4) {
+		if (commit) {
+			NMUtilsIPv6IfaceId iid;
+
+			if (   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);
+			}
+		}
+	}
+
+	success = nm_device_set_ip_config (self, addr_family, composite, commit, ip4_dev_route_blacklist);
+	if (commit) {
+		if (IS_IPv4)
+			priv->v4_commit_first_time = FALSE;
+		else
+			priv->v6_commit_first_time = FALSE;
+	}
 
-	if (commit)
-		priv->v4_commit_first_time = FALSE;
 	return success;
 }
 
@@ -6007,10 +6761,9 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 
 	g_return_val_if_fail (config, FALSE);
 
-	g_clear_object (&priv->dev_ip4_config);
-	priv->dev_ip4_config = g_object_ref (config);
+	applied_config_init (&priv->dev_ip4_config, config);
 
-	if (!ip4_config_merge_and_apply (self, TRUE)) {
+	if (!ip_config_merge_and_apply (self, AF_INET, TRUE)) {
 		_LOGW (LOGD_DHCP4, "failed to update IPv4 config for DHCP change.");
 		return FALSE;
 	}
@@ -6026,20 +6779,17 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 }
 
 static gboolean
-dhcp4_restart_cb (gpointer user_data)
+dhcp4_grace_period_expired (gpointer user_data)
 {
 	NMDevice *self = user_data;
-	NMDevicePrivate *priv;
-
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	priv->dhcp4.restart_id = 0;
+	_LOGI (LOGD_DHCP4, "DHCPv4: grace period expired");
 
-	if (dhcp4_start (self) == NM_ACT_STAGE_RETURN_FAILURE)
-		dhcp_schedule_restart (self, AF_INET, NULL);
+	nm_device_ip_method_failed (self, AF_INET,
+	                            NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+	/* If the device didn't fail, the DHCP client will continue */
 
-	return FALSE;
+	return G_SOURCE_REMOVE;
 }
 
 static void
@@ -6047,44 +6797,48 @@ dhcp4_fail (NMDevice *self, gboolean timeout)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	_LOGD (LOGD_DHCP4, "DHCPv4 failed: timeout %d, num tries left %u",
-	       timeout, priv->dhcp4.num_tries_left);
+	_LOGD (LOGD_DHCP4, "DHCPv4 failed%s", timeout ? " (timeout)" : "");
 
-	dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-
-	/* Don't fail if there are static addresses configured on
-	 * the device, instead retry after some time.
+	/* Keep client running if there are static addresses configured
+	 * on the interface.
 	 */
 	if (   priv->ip4_state == IP_DONE
-	    && priv->con_ip4_config
-	    && nm_ip4_config_get_num_addresses (priv->con_ip4_config) > 0) {
-		dhcp_schedule_restart (self, AF_INET, "device has IP addresses");
+	    && priv->con_ip_config_4
+	    && nm_ip4_config_get_num_addresses (priv->con_ip_config_4) > 0)
+		goto clear_config;
+
+	/* Fail the method in case of timeout or failure during initial
+	 * configuration.
+	 */
+	if (   !priv->dhcp4.was_active
+	    && (timeout || priv->ip4_state == IP_CONF)) {
+		dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
+		nm_device_activate_schedule_ip4_config_timeout (self);
 		return;
 	}
 
-	if (   priv->dhcp4.num_tries_left == DHCP_NUM_TRIES_MAX
-	    && (timeout || (priv->ip4_state == IP_CONF))
-	    && !priv->dhcp4.was_active)
-		nm_device_activate_schedule_ip4_config_timeout (self);
-	else if (   priv->dhcp4.num_tries_left < DHCP_NUM_TRIES_MAX
-	         || priv->ip4_state == IP_DONE
-	         || priv->dhcp4.was_active) {
-		/* Don't fail immediately when the lease expires but try to
-		 * restart DHCP for a predefined number of times.
-		 */
-		if (priv->dhcp4.num_tries_left) {
-			priv->dhcp4.num_tries_left--;
-			dhcp_schedule_restart (self, AF_INET, "lease expired");
-		} else {
-			nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
-			/* We failed the ipv4 method but schedule again the retries if the ipv6 method is
-			 * configured, keeping the connection up.
-			 */
-			if (nm_device_get_state (self) != NM_DEVICE_STATE_FAILED)
-				dhcp_schedule_restart (self, AF_INET, "renewal failed");
-		}
-	} else
-		g_warn_if_reached ();
+	/* In any other case (expired lease, assumed connection, etc.),
+	 * start a grace period in which we keep the client running,
+	 * hoping that it will regain a lease.
+	 */
+	if (!priv->dhcp4.grace_id) {
+		priv->dhcp4.grace_id = g_timeout_add_seconds (DHCP_GRACE_PERIOD_SEC,
+		                                              dhcp4_grace_period_expired,
+		                                              self);
+		_LOGI (LOGD_DHCP4,
+		       "DHCPv4: %u seconds grace period started",
+		       DHCP_GRACE_PERIOD_SEC);
+		goto clear_config;
+	}
+	return;
+
+clear_config:
+	/* The previous configuration is no longer valid */
+	if (priv->dhcp4.config) {
+		nm_dbus_object_clear_and_unexport (&priv->dhcp4.config);
+		priv->dhcp4.config = nm_dhcp4_config_new ();
+		_notify (self, PROP_DHCP4_CONFIG);
+	}
 }
 
 static void
@@ -6124,6 +6878,8 @@ dhcp4_state_changed (NMDhcpClient *client,
 			break;
 		}
 
+		nm_clear_g_source (&priv->dhcp4.grace_id);
+
 		/* After some failures, we have been able to renew the lease:
 		 * update the ip state
 		 */
@@ -6136,7 +6892,6 @@ dhcp4_state_changed (NMDhcpClient *client,
 
 		nm_dhcp4_config_set_options (priv->dhcp4.config, options);
 		_notify (self, PROP_DHCP4_CONFIG);
-		priv->dhcp4.num_tries_left = DHCP_NUM_TRIES_MAX;
 
 		if (priv->ip4_state == IP_CONF) {
 			connection = nm_device_get_applied_connection (self);
@@ -6145,6 +6900,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 			manual = _ip4_config_new (self);
 			nm_ip4_config_merge_setting (manual,
 			                             nm_connection_get_setting_ip4_config (connection),
+			                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
 			                             nm_device_get_route_table (self, AF_INET, TRUE),
 			                             nm_device_get_route_metric (self, AF_INET));
 
@@ -6220,14 +6976,100 @@ get_dhcp_timeout (NMDevice *self, int addr_family)
 	return timeout ?: NM_DHCP_TIMEOUT_DEFAULT;
 }
 
+static GBytes *
+dhcp4_get_client_id (NMDevice *self, NMConnection *connection)
+{
+	NMSettingIPConfig *s_ip4;
+	const char *client_id;
+	gs_free char *client_id_default = NULL;
+	guint8 *client_id_buf;
+	gboolean is_mac;
+
+	s_ip4 = nm_connection_get_setting_ip4_config (connection);
+	client_id = nm_setting_ip4_config_get_dhcp_client_id (NM_SETTING_IP4_CONFIG (s_ip4));
+
+	if (!client_id) {
+		client_id_default = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+		                                                           "ipv4.dhcp-client-id", self);
+		if (client_id_default && client_id_default[0])
+			client_id = client_id_default;
+	}
+
+	if (!client_id)
+		return NULL;
+
+	if (   (is_mac = nm_streq (client_id, "mac"))
+	    || nm_streq (client_id, "perm-mac")) {
+		const char *hwaddr;
+		char addr_buf[NM_UTILS_HWADDR_LEN_MAX];
+		gsize addr_len;
+		guint8 addr_type;
+
+		hwaddr = is_mac
+		         ? nm_device_get_hw_address (self)
+		         : nm_device_get_permanent_hw_address (self);
+		if (!hwaddr)
+			return NULL;
+
+		if (!_nm_utils_hwaddr_aton (hwaddr, addr_buf, sizeof (addr_buf), &addr_len))
+			g_return_val_if_reached (NULL);
+
+		switch (addr_len) {
+		case ETH_ALEN:
+			addr_type = ARPHRD_ETHER;
+			break;
+		default:
+			/* unsupported type. */
+			return NULL;
+		}
+
+		client_id_buf = g_malloc (addr_len + 1);
+		client_id_buf[0] = addr_type;
+		memcpy (&client_id_buf[1], addr_buf, addr_len);
+		return g_bytes_new_take (client_id_buf, addr_len + 1);
+	}
+
+	if (nm_streq (client_id, "stable")) {
+		NMUtilsStableType stable_type;
+		const char *stable_id;
+		GChecksum *sum;
+		guint8 buf[20];
+		gsize buf_size;
+		guint32 salted_header;
+
+		stable_id = _get_stable_id (self, connection, &stable_type);
+		if (!stable_id)
+			g_return_val_if_reached (NULL);
+
+		salted_header = htonl (2011610591 + stable_type);
+
+		sum = g_checksum_new (G_CHECKSUM_SHA1);
+
+		g_checksum_update (sum, (const guchar *) &salted_header, sizeof (salted_header));
+		g_checksum_update (sum, (const guchar *) stable_id, strlen (stable_id));
+
+		buf_size = sizeof (buf);
+		g_checksum_get_digest (sum, buf, &buf_size);
+		nm_assert (buf_size == sizeof (buf));
+
+		g_checksum_free (sum);
+
+		client_id_buf = g_malloc (1 + 15);
+		client_id_buf[0] = 0;
+		memcpy (&client_id_buf[1], buf, 15);
+		return g_bytes_new_take (client_id_buf, 1 + 15);
+	}
+
+	return nm_dhcp_utils_client_id_string_to_bytes (client_id);
+}
+
 static NMActStageReturn
 dhcp4_start (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingIPConfig *s_ip4;
-	const guint8 *hw_addr;
-	size_t hw_addr_len = 0;
-	GByteArray *tmp = NULL;
+	gs_unref_bytes GBytes *hwaddr = NULL;
+	gs_unref_bytes GBytes *client_id = NULL;
 	NMConnection *connection;
 
 	connection = nm_device_get_applied_connection (self);
@@ -6236,36 +7078,31 @@ dhcp4_start (NMDevice *self)
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 
 	/* Clear old exported DHCP options */
-	nm_exported_object_clear_and_unexport (&priv->dhcp4.config);
+	nm_dbus_object_clear_and_unexport (&priv->dhcp4.config);
 	priv->dhcp4.config = nm_dhcp4_config_new ();
 
-	hw_addr = nm_platform_link_get_address (nm_device_get_platform (self), nm_device_get_ip_ifindex (self), &hw_addr_len);
-	if (hw_addr_len) {
-		tmp = g_byte_array_sized_new (hw_addr_len);
-		g_byte_array_append (tmp, hw_addr, hw_addr_len);
-	}
+	hwaddr = nm_platform_link_get_address_as_bytes (nm_device_get_platform (self),
+	                                                nm_device_get_ip_ifindex (self));
+
+	client_id = dhcp4_get_client_id (self, connection);
 
-	/* Begin DHCP on the interface */
 	g_warn_if_fail (priv->dhcp4.client == NULL);
 	priv->dhcp4.client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (),
 	                                                nm_netns_get_multi_idx (nm_device_get_netns (self)),
 	                                                nm_device_get_ip_iface (self),
 	                                                nm_device_get_ip_ifindex (self),
-	                                                tmp,
+	                                                hwaddr,
 	                                                nm_connection_get_uuid (connection),
 	                                                nm_device_get_route_table (self, AF_INET, TRUE),
 	                                                nm_device_get_route_metric (self, AF_INET),
 	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip4),
 	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip4),
 	                                                nm_setting_ip4_config_get_dhcp_fqdn (NM_SETTING_IP4_CONFIG (s_ip4)),
-	                                                nm_setting_ip4_config_get_dhcp_client_id (NM_SETTING_IP4_CONFIG (s_ip4)),
+	                                                client_id,
 	                                                get_dhcp_timeout (self, AF_INET),
 	                                                priv->dhcp_anycast_address,
 	                                                NULL);
 
-	if (tmp)
-		g_byte_array_free (tmp, TRUE);
-
 	if (!priv->dhcp4.client)
 		return NM_ACT_STAGE_RETURN_FAILURE;
 
@@ -6338,7 +7175,7 @@ shared4_new_config (NMDevice *self, NMConnection *connection)
 		guint32 count = 0;
 
 		if (G_UNLIKELY (!shared_ips))
-			shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal);
+			shared_ips = g_hash_table_new (nm_direct_hash, NULL);
 		else {
 			while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) {
 				count += ntohl (0x100);
@@ -6507,7 +7344,6 @@ act_stage3_ip4_config_start (NMDevice *self,
 	}
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	priv->dhcp4.num_tries_left = DHCP_NUM_TRIES_MAX;
 
 	/* Start IPv4 addressing based on the method requested */
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
@@ -6524,6 +7360,7 @@ act_stage3_ip4_config_start (NMDevice *self,
 		config = _ip4_config_new (self);
 		nm_ip4_config_merge_setting (config,
 		                             nm_connection_get_setting_ip4_config (connection),
+		                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
 		                             nm_device_get_route_table (self, AF_INET, TRUE),
 		                             nm_device_get_route_metric (self, AF_INET));
 
@@ -6560,9 +7397,9 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
-	g_clear_object (&priv->dhcp6.ip6_config);
+	applied_config_clear (&priv->dhcp6.ip6_config);
 	g_clear_pointer (&priv->dhcp6.event_id, g_free);
-	nm_clear_g_source (&priv->dhcp6.restart_id);
+	nm_clear_g_source (&priv->dhcp6.grace_id);
 
 	if (priv->dhcp6.client) {
 		nm_clear_g_signal_handler (priv->dhcp6.client, &priv->dhcp6.state_sigid);
@@ -6578,148 +7415,18 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	nm_device_remove_pending_action (self, NM_PENDING_ACTION_DHCP6, FALSE);
 
 	if (priv->dhcp6.config) {
-		nm_exported_object_clear_and_unexport (&priv->dhcp6.config);
+		nm_dbus_object_clear_and_unexport (&priv->dhcp6.config);
 		_notify (self, PROP_DHCP6_CONFIG);
 	}
 }
 
 static gboolean
-ip6_config_merge_and_apply (NMDevice *self,
-                            gboolean commit)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
-	gboolean success;
-	NMIP6Config *composite;
-	gboolean ignore_auto_routes = FALSE;
-	gboolean ignore_auto_dns = FALSE;
-	gboolean ignore_default_routes = FALSE;
-	const char *token = NULL;
-	GSList *iter;
-
-	if (nm_device_sys_iface_state_is_external (self))
-		commit = 0;
-
-	/* Apply ignore-auto-routes and ignore-auto-dns settings */
-	connection = nm_device_get_applied_connection (self);
-	if (connection) {
-		NMSettingIPConfig *s_ip6 = nm_connection_get_setting_ip6_config (connection);
-
-		if (s_ip6) {
-			NMSettingIP6Config *ip6 = NM_SETTING_IP6_CONFIG (s_ip6);
-
-			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip6);
-			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip6);
-
-			/* if the connection has an explicit gateway, we also ignore
-			 * the default routes from other sources. */
-			ignore_default_routes =    nm_setting_ip_config_get_never_default (s_ip6)
-			                        || nm_setting_ip_config_get_gateway (s_ip6);
-
-			if (nm_setting_ip6_config_get_addr_gen_mode (ip6) == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64)
-				token = nm_setting_ip6_config_get_token (ip6);
-		}
-	}
-
-	composite = _ip6_config_new (self);
-	nm_ip6_config_set_privacy (composite,
-	                           priv->ndisc ?
-	                           priv->ndisc_use_tempaddr :
-	                           NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
-	init_ip6_config_dns_priority (self, composite);
-
-	if (commit) {
-		if (priv->queued_ip6_config_id)
-			update_ext_ip_config (self, AF_INET6, FALSE, FALSE);
-		ensure_con_ip6_config (self);
-	}
-
-	if (commit)
-		priv->default_route_metric_penalty_ip6_has = default_route_metric_penalty_detect (self);
-
-	/* Merge all the IP configs into the composite config */
-	if (priv->ac_ip6_config) {
-		nm_ip6_config_merge (composite, priv->ac_ip6_config,
-		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-		                     default_route_metric_penalty_get (self, AF_INET6));
-	}
-	if (priv->dhcp6.ip6_config) {
-		nm_ip6_config_merge (composite, priv->dhcp6.ip6_config,
-		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-		                     default_route_metric_penalty_get (self, AF_INET6));
-	}
-
-	for (iter = priv->vpn6_configs; iter; iter = iter->next)
-		nm_ip6_config_merge (composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
-
-	if (priv->ext_ip6_config)
-		nm_ip6_config_merge (composite, priv->ext_ip6_config, NM_IP_CONFIG_MERGE_DEFAULT, 0);
-
-	/* Merge WWAN config *last* to ensure modem-given settings overwrite
-	 * any external stuff set by pppd or other scripts.
-	 */
-	if (priv->wwan_ip6_config) {
-		nm_ip6_config_merge (composite, priv->wwan_ip6_config,
-		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-		                     default_route_metric_penalty_get (self, AF_INET6));
-	}
-
-	if (priv->rt6_temporary_not_available) {
-		const NMPObject *o;
-		GHashTableIter hiter;
-
-		g_hash_table_iter_init (&hiter, priv->rt6_temporary_not_available);
-		while (g_hash_table_iter_next (&hiter, (gpointer *) &o, NULL)) {
-			nm_ip6_config_add_route (composite,
-			                         NMP_OBJECT_CAST_IP6_ROUTE (o),
-			                         NULL);
-		}
-	}
-
-	/* Merge user overrides into the composite config. For assumed connections,
-	 * con_ip6_config is empty. */
-	if (priv->con_ip6_config) {
-		nm_ip6_config_merge (composite, priv->con_ip6_config, NM_IP_CONFIG_MERGE_DEFAULT,
-		                     default_route_metric_penalty_get (self, AF_INET6));
-	}
-
-	if (commit) {
-		nm_ip6_config_add_dependent_routes (composite,
-		                                    nm_device_get_route_table (self, AF_INET6, TRUE),
-		                                    nm_device_get_route_metric (self, AF_INET6));
-	}
-
-	/* Allow setting MTU etc */
-	if (commit) {
-		NMUtilsIPv6IfaceId iid;
-
-		if (token && nm_utils_ipv6_interface_identifier_get_from_token (&iid, token)) {
-			nm_platform_link_set_ipv6_token (nm_device_get_platform (self),
-			                                 nm_device_get_ip_ifindex (self),
-			                                 iid);
-		}
-	}
-
-	success = nm_device_set_ip6_config (self, composite, commit);
-	g_object_unref (composite);
-	if (commit)
-		priv->v6_commit_first_time = FALSE;
-	return success;
-}
-
-static gboolean
 dhcp6_lease_change (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingsConnection *settings_connection;
 
-	if (priv->dhcp6.ip6_config == NULL) {
+	if (!applied_config_get_current (&priv->dhcp6.ip6_config)) {
 		_LOGW (LOGD_DHCP6, "failed to get DHCPv6 config for rebind");
 		return FALSE;
 	}
@@ -6730,7 +7437,7 @@ dhcp6_lease_change (NMDevice *self)
 	g_assert (settings_connection);
 
 	/* Apply the updated config */
-	if (!ip6_config_merge_and_apply (self, TRUE)) {
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE)) {
 		_LOGW (LOGD_DHCP6, "failed to update IPv6 config in response to DHCP event");
 		return FALSE;
 	}
@@ -6746,53 +7453,17 @@ dhcp6_lease_change (NMDevice *self)
 }
 
 static gboolean
-dhcp6_restart_cb (gpointer user_data)
+dhcp6_grace_period_expired (gpointer user_data)
 {
 	NMDevice *self = user_data;
-	NMDevicePrivate *priv;
 
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	priv->dhcp6.restart_id = 0;
-
-	if (!dhcp6_start (self, FALSE))
-		dhcp_schedule_restart (self, AF_INET6, NULL);
-
-	return FALSE;
-}
-
-static void
-dhcp_schedule_restart (NMDevice *self,
-                       int addr_family,
-                       const char *reason)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	guint tries_left;
-	char tries_str[255];
-
-	nm_assert_addr_family (addr_family);
-
-	tries_left =   (addr_family == AF_INET)
-	             ? priv->dhcp4.num_tries_left
-	             : priv->dhcp6.num_tries_left;
+	_LOGI (LOGD_DHCP6, "DHCPv6: grace period expired");
 
-	_LOGI ((addr_family == AF_INET) ? LOGD_DHCP4 : LOGD_DHCP6,
-	       "scheduling DHCPv%c restart in %u seconds%s%s%s%s",
-	       nm_utils_addr_family_to_char (addr_family),
-	       DHCP_RESTART_TIMEOUT,
-	       (tries_left != DHCP_NUM_TRIES_MAX)
-	         ? nm_sprintf_buf (tries_str, ", %u tries left", tries_left + 1)
-	         : "",
-	       NM_PRINT_FMT_QUOTED (reason, " (reason: ", reason, ")", ""));
+	nm_device_ip_method_failed (self, AF_INET6,
+	                            NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+	/* If the device didn't fail, the DHCP client will continue */
 
-	if (addr_family == AF_INET) {
-		priv->dhcp4.restart_id = g_timeout_add_seconds (DHCP_RESTART_TIMEOUT,
-		                                                dhcp4_restart_cb, self);
-	} else {
-		priv->dhcp6.restart_id = g_timeout_add_seconds (DHCP_RESTART_TIMEOUT,
-		                                                dhcp6_restart_cb, self);
-	}
+	return G_SOURCE_REMOVE;
 }
 
 static void
@@ -6801,51 +7472,57 @@ dhcp6_fail (NMDevice *self, gboolean timeout)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean is_dhcp_managed;
 
-	_LOGD (LOGD_DHCP6, "DHCPv6 failed: timeout %d, num tries left %u",
-           timeout, priv->dhcp6.num_tries_left);
+	_LOGD (LOGD_DHCP6, "DHCPv6 failed%s", timeout ? " (timeout)" : "");
 
 	is_dhcp_managed = (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED);
-	dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
 
-	if (is_dhcp_managed || priv->dhcp6.num_tries_left < DHCP_NUM_TRIES_MAX) {
-		/* Don't fail if there are static addresses configured on
-		 * the device, instead retry after some time.
+	if (is_dhcp_managed) {
+		/* Keep client running if there are static addresses configured
+		 * on the interface.
 		 */
 		if (   priv->ip6_state == IP_DONE
-		    && priv->con_ip6_config
-		    && nm_ip6_config_get_num_addresses (priv->con_ip6_config)) {
-			dhcp_schedule_restart (self, AF_INET6, "device has IP addresses");
+		    && priv->con_ip_config_6
+		    && nm_ip6_config_get_num_addresses (priv->con_ip_config_6))
+			goto clear_config;
+
+		/* Fail the method in case of timeout or failure during initial
+		 * configuration.
+		 */
+		if (   !priv->dhcp6.was_active
+		    && (timeout || priv->ip6_state == IP_CONF)) {
+			dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
+			nm_device_activate_schedule_ip6_config_timeout (self);
 			return;
 		}
 
-		if (   priv->dhcp6.num_tries_left == DHCP_NUM_TRIES_MAX
-		    && (timeout || (priv->ip6_state == IP_CONF))
-		    && !priv->dhcp6.was_active)
-			nm_device_activate_schedule_ip6_config_timeout (self);
-		else if (   priv->dhcp6.num_tries_left < DHCP_NUM_TRIES_MAX
-		         || priv->ip6_state == IP_DONE
-		         || priv->dhcp6.was_active) {
-			/* Don't fail immediately when the lease expires but try to
-			 * restart DHCP for a predefined number of times.
-			 */
-			if (priv->dhcp6.num_tries_left) {
-				priv->dhcp6.num_tries_left--;
-				dhcp_schedule_restart (self, AF_INET6, "lease expired");
-			} else {
-				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
-				/* We failed the ipv6 method but schedule again the retries if the ipv4 method is
-				 * configured, keeping the connection up.
-				 */
-				if (nm_device_get_state (self) != NM_DEVICE_STATE_FAILED)
-					dhcp_schedule_restart (self, AF_INET6, "renewal failed");
-			}
-		} else
-			g_warn_if_reached ();
+		/* In any other case (expired lease, assumed connection, etc.),
+		 * start a grace period in which we keep the client running,
+		 * hoping that it will regain a lease.
+		 */
+		if (!priv->dhcp6.grace_id) {
+			priv->dhcp6.grace_id = g_timeout_add_seconds (DHCP_GRACE_PERIOD_SEC,
+			                                              dhcp6_grace_period_expired,
+			                                              self);
+			_LOGI (LOGD_DHCP6,
+			       "DHCPv6: %u seconds grace period started",
+			       DHCP_GRACE_PERIOD_SEC);
+			goto clear_config;
+		}
 	} else {
 		/* not a hard failure; just live with the RA info */
+		dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
 		if (priv->ip6_state == IP_CONF)
 			nm_device_activate_schedule_ip6_config_result (self);
 	}
+	return;
+
+clear_config:
+	/* The previous configuration is no longer valid */
+	if (priv->dhcp6.config) {
+		nm_dbus_object_clear_and_unexport (&priv->dhcp6.config);
+		priv->dhcp6.config = nm_dhcp6_config_new ();
+		_notify (self, PROP_DHCP6_CONFIG);
+	}
 }
 
 static void
@@ -6881,6 +7558,7 @@ dhcp6_state_changed (NMDhcpClient *client,
 
 	switch (state) {
 	case NM_DHCP_STATE_BOUND:
+		nm_clear_g_source (&priv->dhcp6.grace_id);
 		/* If the server sends multiple IPv6 addresses, we receive a state
 		 * changed event for each of them. Use the event ID to merge IPv6
 		 * addresses from the same transaction into a single configuration.
@@ -6893,16 +7571,16 @@ dhcp6_state_changed (NMDhcpClient *client,
 			const NMPlatformIP6Address *a;
 
 			nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6_config, &a)
-				nm_ip6_config_add_address (priv->dhcp6.ip6_config, a);
+				applied_config_add_address (&priv->dhcp6.ip6_config, NM_PLATFORM_IP_ADDRESS_CAST (a));
 		} else {
-			g_clear_object (&priv->dhcp6.ip6_config);
 			g_clear_pointer (&priv->dhcp6.event_id, g_free);
 			if (ip6_config) {
-				priv->dhcp6.ip6_config = g_object_ref (ip6_config);
+				applied_config_init (&priv->dhcp6.ip6_config, ip6_config);
 				priv->dhcp6.event_id = g_strdup (event_id);
 				nm_dhcp6_config_set_options (priv->dhcp6.config, options);
 				_notify (self, PROP_DHCP6_CONFIG);
-			}
+			} else
+				applied_config_clear (&priv->dhcp6.ip6_config);
 		}
 
 		/* After long time we have been able to renew the lease:
@@ -6911,10 +7589,8 @@ dhcp6_state_changed (NMDhcpClient *client,
 		if (priv->ip6_state == IP_FAIL)
 			_set_ip_state (self, AF_INET6, IP_CONF);
 
-		priv->dhcp6.num_tries_left = DHCP_NUM_TRIES_MAX;
-
 		if (priv->ip6_state == IP_CONF) {
-			if (priv->dhcp6.ip6_config == NULL) {
+			if (!applied_config_get_current (&priv->dhcp6.ip6_config)) {
 				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_DHCP_FAILED);
 				break;
 			}
@@ -6965,34 +7641,32 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingIPConfig *s_ip6;
-	GByteArray *tmp = NULL;
-	const guint8 *hw_addr;
-	size_t hw_addr_len = 0;
+	gs_unref_bytes GBytes *hwaddr = NULL;
 	const NMPlatformIP6Address *ll_addr = NULL;
 
 	g_assert (connection);
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
 
-	if (priv->ext_ip6_config_captured)
-		ll_addr = nm_ip6_config_get_address_first_nontentative (priv->ext_ip6_config_captured, TRUE);
+	if (priv->ext_ip6_config_captured) {
+		ll_addr = nm_ip6_config_find_first_address (priv->ext_ip6_config_captured,
+		                                              NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+		                                            | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL);
+	}
 
 	if (!ll_addr) {
 		_LOGW (LOGD_DHCP6, "can't start DHCPv6: no link-local address");
 		return FALSE;
 	}
 
-	hw_addr = nm_platform_link_get_address (nm_device_get_platform (self), nm_device_get_ip_ifindex (self), &hw_addr_len);
-	if (hw_addr_len) {
-		tmp = g_byte_array_sized_new (hw_addr_len);
-		g_byte_array_append (tmp, hw_addr, hw_addr_len);
-	}
+	hwaddr = nm_platform_link_get_address_as_bytes (nm_device_get_platform (self),
+	                                                nm_device_get_ip_ifindex (self));
 
 	priv->dhcp6.client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (),
 	                                                nm_device_get_multi_index (self),
 	                                                nm_device_get_ip_iface (self),
 	                                                nm_device_get_ip_ifindex (self),
-	                                                tmp,
+	                                                hwaddr,
 	                                                &ll_addr->address,
 	                                                nm_connection_get_uuid (connection),
 	                                                nm_device_get_route_table (self, AF_INET6, TRUE),
@@ -7004,8 +7678,6 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	                                                (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
 	                                                nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (s_ip6)),
 	                                                priv->dhcp6.needed_prefixes);
-	if (tmp)
-		g_byte_array_free (tmp, TRUE);
 
 	if (priv->dhcp6.client) {
 		priv->dhcp6.state_sigid = g_signal_connect (priv->dhcp6.client,
@@ -7031,11 +7703,11 @@ dhcp6_start (NMDevice *self, gboolean wait_for_ll)
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip6;
 
-	nm_exported_object_clear_and_unexport (&priv->dhcp6.config);
+	nm_dbus_object_clear_and_unexport (&priv->dhcp6.config);
 	priv->dhcp6.config = nm_dhcp6_config_new ();
 
-	g_warn_if_fail (priv->dhcp6.ip6_config == NULL);
-	g_clear_object (&priv->dhcp6.ip6_config);
+	nm_assert (!applied_config_get_current (&priv->dhcp6.ip6_config));
+	applied_config_clear (&priv->dhcp6.ip6_config);
 	g_clear_pointer (&priv->dhcp6.event_id, g_free);
 
 	connection = nm_device_get_applied_connection (self);
@@ -7046,17 +7718,12 @@ dhcp6_start (NMDevice *self, gboolean wait_for_ll)
 		nm_device_add_pending_action (self, NM_PENDING_ACTION_DHCP6, TRUE);
 
 	if (wait_for_ll) {
-		NMActStageReturn ret;
-
 		/* ensure link local is ready... */
-		ret = linklocal6_start (self);
-		if (ret == NM_ACT_STAGE_RETURN_POSTPONE) {
-			/* success; wait for the LL address to show up */
+		if (!linklocal6_start (self)) {
+			/* wait for the LL address to show up */
 			return TRUE;
 		}
-
-		/* success; already have the LL address; kick off DHCP */
-		g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
+		/* already have the LL address; kick off DHCP */
 	}
 
 	if (!dhcp6_start_with_link_ready (self, connection))
@@ -7118,19 +7785,19 @@ nm_device_use_ip6_subnet (NMDevice *self, const NMPlatformIP6Address *subnet)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMPlatformIP6Address address = *subnet;
 
-	if (!priv->ac_ip6_config)
-		priv->ac_ip6_config = _ip6_config_new (self);
+	if (!applied_config_get_current (&priv->ac_ip6_config))
+		applied_config_init_new (&priv->ac_ip6_config, self, AF_INET6);
 
 	/* Assign a ::1 address in the subnet for us. */
 	address.address.s6_addr32[3] |= htonl (1);
-	nm_ip6_config_add_address (priv->ac_ip6_config, &address);
+	applied_config_add_address (&priv->ac_ip6_config, NM_PLATFORM_IP_ADDRESS_CAST (&address));
 
 	_LOGD (LOGD_IP6, "ipv6-pd: using %s address (preferred for %u seconds)",
 	       nm_utils_inet6_ntop (&address.address, NULL),
 	       subnet->preferred);
 
 	/* This also updates the ndisc if there are actual changes. */
-	if (!ip6_config_merge_and_apply (self, TRUE))
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 		_LOGW (LOGD_IP6, "ipv6-pd: failed applying IP6 config for connection sharing");
 }
 
@@ -7145,11 +7812,11 @@ nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device)
 	NMIP6Config *from_config = NULL;
 	guint i, len;
 
-	if (priv->ac_ip6_config) {
-		nm_ip6_config_reset_nameservers (priv->ac_ip6_config);
-		nm_ip6_config_reset_searches (priv->ac_ip6_config);
+	if (applied_config_get_current (&priv->ac_ip6_config)) {
+		applied_config_reset_nameservers (&priv->ac_ip6_config);
+		applied_config_reset_searches (&priv->ac_ip6_config);
 	} else
-		priv->ac_ip6_config = _ip6_config_new (self);
+		applied_config_init_new (&priv->ac_ip6_config, self, AF_INET6);
 
 	if (from_device)
 		from_config = nm_device_get_ip6_config (from_device);
@@ -7158,34 +7825,28 @@ nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device)
 
 	len = nm_ip6_config_get_num_nameservers (from_config);
 	for (i = 0; i < len; i++) {
-		nm_ip6_config_add_nameserver (priv->ac_ip6_config,
-		                              nm_ip6_config_get_nameserver (from_config, i));
+		applied_config_add_nameserver (&priv->ac_ip6_config,
+		                               (const NMIPAddr *) nm_ip6_config_get_nameserver (from_config, i));
 	}
 
 	len = nm_ip6_config_get_num_searches (from_config);
 	for (i = 0; i < len; i++) {
-		nm_ip6_config_add_search (priv->ac_ip6_config,
-		                          nm_ip6_config_get_search (from_config, i));
+		applied_config_add_search (&priv->ac_ip6_config,
+		                           nm_ip6_config_get_search (from_config, i));
 	}
 
-	if (!ip6_config_merge_and_apply (self, TRUE))
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 		_LOGW (LOGD_IP6, "ipv6-pd: failed applying DNS config for connection sharing");
 }
 
 /*****************************************************************************/
 
 static void
-linklocal6_cleanup (NMDevice *self)
+linklocal6_failed (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	nm_clear_g_source (&priv->linklocal6_timeout_id);
-}
-
-static void
-linklocal6_failed (NMDevice *self)
-{
-	linklocal6_cleanup (self);
 	nm_device_activate_schedule_ip6_config_timeout (self);
 }
 
@@ -7200,17 +7861,26 @@ linklocal6_timeout_cb (gpointer user_data)
 }
 
 static void
-linklocal6_complete (NMDevice *self)
+linklocal6_check_complete (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
 	const char *method;
 
-	g_assert (priv->linklocal6_timeout_id);
-	g_assert (priv->ext_ip6_config_captured);
-	g_assert (nm_ip6_config_get_address_first_nontentative (priv->ext_ip6_config_captured, TRUE));
+	if (!priv->linklocal6_timeout_id) {
+		/* we are not waiting for linklocal to complete. Nothing to do. */
+		return;
+	}
+
+	if (   !priv->ext_ip6_config_captured
+	    || !nm_ip6_config_find_first_address (priv->ext_ip6_config_captured,
+	                                            NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+	                                          | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL)) {
+		/* we don't have a non-tentative link local address yet. Wait longer. */
+		return;
+	}
 
-	linklocal6_cleanup (self);
+	nm_clear_g_source (&priv->linklocal6_timeout_id);
 
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
@@ -7220,12 +7890,9 @@ linklocal6_complete (NMDevice *self)
 	_LOGD (LOGD_DEVICE, "linklocal6: waiting for link-local addresses successful, continue with method %s", method);
 
 	if (   strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
-	    || strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0) {
-		if (!addrconf6_start_with_link_ready (self)) {
-			/* Time out IPv6 instead of failing the entire activation */
-			nm_device_activate_schedule_ip6_config_timeout (self);
-		}
-	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
+	    || strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0)
+		addrconf6_start_with_link_ready (self);
+	else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
 		if (!dhcp6_start_with_link_ready (self, connection)) {
 			/* Time out IPv6 instead of failing the entire activation */
 			nm_device_activate_schedule_ip6_config_timeout (self);
@@ -7240,28 +7907,27 @@ static void
 check_and_add_ipv6ll_addr (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ip_ifindex = nm_device_get_ip_ifindex (self);
 	struct in6_addr lladdr;
 	NMConnection *connection;
 	NMSettingIP6Config *s_ip6 = NULL;
 	GError *error = NULL;
+	const char *addr_type;
 
-	if (priv->nm_ipv6ll == FALSE)
+	if (!priv->ipv6ll_handle)
 		return;
 
-	if (priv->ext_ip6_config_captured) {
-		NMDedupMultiIter ipconf_iter;
-		const NMPlatformIP6Address *addr;
-
-		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, priv->ext_ip6_config_captured, &addr) {
-			if (   IN6_IS_ADDR_LINKLOCAL (&addr->address)
-			    && !(addr->n_ifa_flags & IFA_F_DADFAILED)) {
-				/* Already have an LL address, nothing to do */
-				return;
-			}
-		}
+	if (   priv->ext_ip6_config_captured
+	    && nm_ip6_config_find_first_address (priv->ext_ip6_config_captured,
+	                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+	                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL
+	                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE)) {
+		/* Already have an LL address, nothing to do */
+		return;
 	}
 
+	priv->ipv6ll_has = FALSE;
+	memset (&priv->ipv6ll_addr, 0, sizeof (priv->ipv6ll_addr));
+
 	memset (&lladdr, 0, sizeof (lladdr));
 	lladdr.s6_addr16[0] = htons (0xfe80);
 
@@ -7286,7 +7952,7 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 			linklocal6_failed (self);
 			return;
 		}
-		_LOGD (LOGD_IP6, "linklocal6: using IPv6 stable-privacy addressing");
+		addr_type = "stable-privacy";
 	} else {
 		NMUtilsIPv6IfaceId iid;
 
@@ -7302,37 +7968,30 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 			_LOGW (LOGD_IP6, "linklocal6: failed to get interface identifier; IPv6 cannot continue");
 			return;
 		}
-		_LOGD (LOGD_IP6, "linklocal6: using EUI-64 identifier to generate IPv6LL address");
-
 		nm_utils_ipv6_addr_set_interface_identifier (&lladdr, iid);
+		addr_type = "EUI-64";
 	}
 
-	_LOGD (LOGD_IP6, "linklocal6: adding IPv6LL address %s", nm_utils_inet6_ntop (&lladdr, NULL));
-	if (!nm_platform_ip6_address_add (nm_device_get_platform (self),
-	                                  ip_ifindex,
-	                                  lladdr,
-	                                  64,
-	                                  in6addr_any,
-	                                  NM_PLATFORM_LIFETIME_PERMANENT,
-	                                  NM_PLATFORM_LIFETIME_PERMANENT,
-	                                  0)) {
-		_LOGW (LOGD_IP6, "failed to add IPv6 link-local address %s",
-		       nm_utils_inet6_ntop (&lladdr, NULL));
-	}
+	_LOGD (LOGD_IP6, "linklocal6: generated %s IPv6LL address %s", addr_type, nm_utils_inet6_ntop (&lladdr, NULL));
+	priv->ipv6ll_has = TRUE;
+	priv->ipv6ll_addr = lladdr;
+	ip_config_merge_and_apply (self, AF_INET6, TRUE);
 }
 
-static NMActStageReturn
+static gboolean
 linklocal6_start (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
 	const char *method;
 
-	linklocal6_cleanup (self);
+	nm_clear_g_source (&priv->linklocal6_timeout_id);
 
 	if (   priv->ext_ip6_config_captured
-	    && nm_ip6_config_get_address_first_nontentative (priv->ext_ip6_config_captured, TRUE))
-		return NM_ACT_STAGE_RETURN_SUCCESS;
+	    && nm_ip6_config_find_first_address (priv->ext_ip6_config_captured,
+	                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+	                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL))
+		return TRUE;
 
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
@@ -7348,8 +8007,7 @@ linklocal6_start (NMDevice *self)
 	 * (rh #1101809)
 	 */
 	priv->linklocal6_timeout_id = g_timeout_add_seconds (15, linklocal6_timeout_cb, self);
-
-	return NM_ACT_STAGE_RETURN_POSTPONE;
+	return FALSE;
 }
 
 /*****************************************************************************/
@@ -7414,7 +8072,7 @@ _set_mtu (NMDevice *self, guint32 mtu)
 
 	if (priv->master) {
 		/* changing the MTU of a slave, might require the master to reset
-		 * it's MTU. Note that the master usually cannot set a MTU larger
+		 * its MTU. Note that the master usually cannot set a MTU larger
 		 * then the slave's. Hence, when the slave increases the MTU,
 		 * master might want to retry setting the MTU. */
 		nm_device_commit_mtu (priv->master);
@@ -7591,7 +8249,7 @@ nm_device_commit_mtu (NMDevice *self)
 	if (   state >= NM_DEVICE_STATE_CONFIG
 	    && state < NM_DEVICE_STATE_DEACTIVATING) {
 		_LOGT (LOGD_DEVICE, "mtu: commit-mtu...");
-		_commit_mtu (self, NM_DEVICE_GET_PRIVATE (self)->ip4_config);
+		_commit_mtu (self, NM_DEVICE_GET_PRIVATE (self)->ip_config_4);
 	} else
 		_LOGT (LOGD_DEVICE, "mtu: commit-mtu... skip due to state %s", nm_device_state_to_str (state));
 }
@@ -7603,10 +8261,10 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	guint i;
 
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
-	if (!priv->ac_ip6_config)
-		priv->ac_ip6_config = _ip6_config_new (self);
+	if (!applied_config_get_current (&priv->ac_ip6_config))
+		applied_config_init_new (&priv->ac_ip6_config, self, AF_INET6);
 
 	if (changed & NM_NDISC_CONFIG_ADDRESSES) {
 		guint8 plen;
@@ -7627,16 +8285,23 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		} else
 			plen = 128;
 
-		nm_ip6_config_reset_addresses_ndisc (priv->ac_ip6_config,
+		nm_ip6_config_reset_addresses_ndisc ((NMIP6Config *) priv->ac_ip6_config.orig,
 		                                     rdata->addresses,
 		                                     rdata->addresses_n,
 		                                     plen,
 		                                     ifa_flags);
+		if (priv->ac_ip6_config.current) {
+			nm_ip6_config_reset_addresses_ndisc ((NMIP6Config *) priv->ac_ip6_config.current,
+			                                     rdata->addresses,
+			                                     rdata->addresses_n,
+			                                     plen,
+			                                     ifa_flags);
+		}
 	}
 
 	if (NM_FLAGS_ANY (changed,   NM_NDISC_CONFIG_ROUTES
 	                           | NM_NDISC_CONFIG_GATEWAYS)) {
-		nm_ip6_config_reset_routes_ndisc (priv->ac_ip6_config,
+		nm_ip6_config_reset_routes_ndisc ((NMIP6Config *) priv->ac_ip6_config.orig,
 		                                  rdata->gateways,
 		                                  rdata->gateways_n,
 		                                  rdata->routes,
@@ -7645,22 +8310,34 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		                                  nm_device_get_route_metric (self, AF_INET6),
 		                                  nm_platform_check_kernel_support (nm_device_get_platform (self),
 		                                                                    NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF));
+		if (priv->ac_ip6_config.current) {
+			nm_ip6_config_reset_routes_ndisc ((NMIP6Config *) priv->ac_ip6_config.current,
+			                                  rdata->gateways,
+			                                  rdata->gateways_n,
+			                                  rdata->routes,
+			                                  rdata->routes_n,
+			                                  nm_device_get_route_table (self, AF_INET6, TRUE),
+			                                  nm_device_get_route_metric (self, AF_INET6),
+			                                  nm_platform_check_kernel_support (nm_device_get_platform (self),
+			                                                                    NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF));
+		}
+
 	}
 
 	if (changed & NM_NDISC_CONFIG_DNS_SERVERS) {
 		/* Rebuild DNS server list from neighbor discovery cache. */
-		nm_ip6_config_reset_nameservers (priv->ac_ip6_config);
+		applied_config_reset_nameservers (&priv->ac_ip6_config);
 
 		for (i = 0; i < rdata->dns_servers_n; i++)
-			nm_ip6_config_add_nameserver (priv->ac_ip6_config, &rdata->dns_servers[i].address);
+			applied_config_add_nameserver (&priv->ac_ip6_config, (const NMIPAddr *) &rdata->dns_servers[i].address);
 	}
 
 	if (changed & NM_NDISC_CONFIG_DNS_DOMAINS) {
 		/* Rebuild domain list from neighbor discovery cache. */
-		nm_ip6_config_reset_searches (priv->ac_ip6_config);
+		applied_config_reset_searches (&priv->ac_ip6_config);
 
 		for (i = 0; i < rdata->dns_domains_n; i++)
-			nm_ip6_config_add_search (priv->ac_ip6_config, rdata->dns_domains[i].domain);
+			applied_config_add_search (&priv->ac_ip6_config, rdata->dns_domains[i].domain);
 	}
 
 	if (changed & NM_NDISC_CONFIG_DHCP_LEVEL) {
@@ -7710,16 +8387,21 @@ ndisc_ra_timeout (NMNDisc *ndisc, NMDevice *self)
 		 * ever receive one, then time out IPv6.  But if there is other
 		 * IPv6 configuration, like manual IPv6 addresses or external IPv6
 		 * config, consider that sufficient for IPv6 success.
+		 *
+		 * FIXME: it doesn't seem correct to determine this based on which
+		 *        addresses we find inside priv->ip_config_6.
 		 */
-		if (   priv->ip6_config
-		    && nm_ip6_config_get_address_first_nontentative (priv->ip6_config, FALSE))
+		if (   priv->ip_config_6
+		    && nm_ip6_config_find_first_address (priv->ip_config_6,
+		                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
+		                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))
 			nm_device_activate_schedule_ip6_config_result (self);
 		else
 			nm_device_activate_schedule_ip6_config_timeout (self);
 	}
 }
 
-static gboolean
+static void
 addrconf6_start_with_link_ready (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -7737,12 +8419,10 @@ addrconf6_start_with_link_ready (NMDevice *self)
 	}
 
 	/* Apply any manual configuration before starting RA */
-	if (!ip6_config_merge_and_apply (self, TRUE)) {
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 		_LOGW (LOGD_IP6, "failed to apply manual IPv6 configuration");
-		g_clear_object (&priv->con_ip6_config);
-	}
 
-	/* XXX: These sysctls would probably be better set by the lndp ndisc itself. */
+	/* FIXME: These sysctls would probably be better set by the lndp ndisc itself. */
 	switch (nm_ndisc_get_node_type (priv->ndisc)) {
 	case NM_NDISC_NODE_TYPE_HOST:
 		/* Accepting prefixes from discovered routers. */
@@ -7773,7 +8453,7 @@ addrconf6_start_with_link_ready (NMDevice *self)
 
 	ndisc_set_router_config (priv->ndisc, self);
 	nm_ndisc_start (priv->ndisc);
-	return TRUE;
+	return;
 }
 
 static NMNDiscNodeType
@@ -7796,7 +8476,6 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
-	NMActStageReturn ret;
 	NMSettingIP6Config *s_ip6 = NULL;
 	GError *error = NULL;
 	NMUtilsStableType stable_type;
@@ -7805,11 +8484,8 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
-	g_warn_if_fail (priv->ac_ip6_config == NULL);
-	if (priv->ac_ip6_config) {
-		g_object_unref (priv->ac_ip6_config);
-		priv->ac_ip6_config = NULL;
-	}
+	nm_assert (!applied_config_get_current (&priv->ac_ip6_config));
+	applied_config_clear (&priv->ac_ip6_config);
 
 	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
 	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
@@ -7846,15 +8522,14 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 		nm_device_add_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, TRUE);
 
 	/* ensure link local is ready... */
-	ret = linklocal6_start (self);
-	if (ret == NM_ACT_STAGE_RETURN_POSTPONE) {
-		/* success; wait for the LL address to show up */
+	if (!linklocal6_start (self)) {
+		/* wait for the LL address to show up */
 		return TRUE;
 	}
 
-	/* success; already have the LL address; kick off neighbor discovery */
-	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
-	return addrconf6_start_with_link_ready (self);
+	/* already have the LL address; kick off neighbor discovery */
+	addrconf6_start_with_link_ready (self);
+	return TRUE;
 }
 
 static void
@@ -7867,7 +8542,7 @@ addrconf6_cleanup (NMDevice *self)
 
 	nm_device_remove_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, FALSE);
 
-	g_clear_object (&priv->ac_ip6_config);
+	applied_config_clear (&priv->ac_ip6_config);
 	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
 	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
 	g_clear_object (&priv->ndisc);
@@ -7921,7 +8596,8 @@ restore_ip6_properties (NMDevice *self)
 	g_hash_table_iter_init (&iter, priv->ip6_saved_properties);
 	while (g_hash_table_iter_next (&iter, &key, &value)) {
 		/* Don't touch "disable_ipv6" if we're doing userland IPv6LL */
-		if (priv->nm_ipv6ll && strcmp (key, "disable_ipv6") == 0)
+		if (   priv->ipv6ll_handle
+		    && nm_streq (key, "disable_ipv6"))
 			continue;
 		nm_device_ipv6_sysctl_set (self, key, value);
 	}
@@ -7931,7 +8607,7 @@ static inline void
 set_disable_ipv6 (NMDevice *self, const char *value)
 {
 	/* We only touch disable_ipv6 when NM is not managing the IPv6LL address */
-	if (NM_DEVICE_GET_PRIVATE (self)->nm_ipv6ll == FALSE)
+	if (!NM_DEVICE_GET_PRIVATE (self)->ipv6ll_handle)
 		nm_device_ipv6_sysctl_set (self, "disable_ipv6", value);
 }
 
@@ -7946,7 +8622,7 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 	                                       NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
 		return;
 
-	priv->nm_ipv6ll = enable;
+	priv->ipv6ll_handle = enable;
 	if (ifindex > 0) {
 		NMPlatformError plerr;
 		const char *detail = enable ? "enable" : "disable";
@@ -8090,21 +8766,19 @@ act_stage3_ip6_config_start (NMDevice *self,
 	}
 
 	priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
-	priv->dhcp6.num_tries_left = DHCP_NUM_TRIES_MAX;
-
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
 
 	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) {
 		if (   !priv->master
 		    && !nm_device_sys_iface_state_is_external (self)) {
-			gboolean old_nm_ipv6ll = priv->nm_ipv6ll;
+			gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
 
 			/* When activating an IPv6 'ignore' connection we need to revert back
 			 * to kernel IPv6LL, but the kernel won't actually assign an address
 			 * to the interface until disable_ipv6 is bounced.
 			 */
 			set_nm_ipv6ll (self, FALSE);
-			if (old_nm_ipv6ll == TRUE)
+			if (ipv6ll_handle_old)
 				nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
 			restore_ip6_properties (self);
 		}
@@ -8115,7 +8789,7 @@ act_stage3_ip6_config_start (NMDevice *self,
 	 * expose any ipv6 sysctls or allow presence of any addresses on the interface,
 	 * including LL, which * would make it impossible to autoconfigure MTU to a
 	 * correct value. */
-	_commit_mtu (self, priv->ip4_config);
+	_commit_mtu (self, priv->ip_config_4);
 
 	/* Any method past this point requires an IPv6LL address. Use NM-controlled
 	 * IPv6LL if this is not an assumed connection, since assumed connections
@@ -8136,7 +8810,6 @@ act_stage3_ip6_config_start (NMDevice *self,
 	priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
 	                                                       nm_device_get_platform (self),
 	                                                       nm_device_get_ip_ifindex (self),
-	                                                       FALSE,
 	                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 
 	ip6_privacy = _ip6_privacy_get (self);
@@ -8149,7 +8822,9 @@ act_stage3_ip6_config_start (NMDevice *self,
 		} else
 			ret = NM_ACT_STAGE_RETURN_POSTPONE;
 	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) {
-		ret = linklocal6_start (self);
+		ret = linklocal6_start (self)
+		      ? NM_ACT_STAGE_RETURN_SUCCESS
+		      : NM_ACT_STAGE_RETURN_POSTPONE;
 	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
 		priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
 		if (!dhcp6_start (self, TRUE)) {
@@ -8259,8 +8934,8 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 		/* Here we get a static IPv6 config, like for Shared where it's
 		 * autogenerated or from modems where it comes from ModemManager.
 		 */
-		g_warn_if_fail (priv->ac_ip6_config == NULL);
-		priv->ac_ip6_config = ip6_config;
+		nm_assert (!applied_config_get_current (&priv->ac_ip6_config));
+		applied_config_init (&priv->ac_ip6_config, ip6_config);
 		nm_device_activate_schedule_ip6_config_result (self);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
 		_set_ip_state (self, AF_INET6, IP_DONE);
@@ -8398,7 +9073,7 @@ nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self)
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
 	/* Add the interface to the specified firewall zone */
 	if (priv->fw_state == FIREWALL_STATE_UNMANAGED) {
@@ -8470,7 +9145,7 @@ nm_device_activate_schedule_ip4_config_timeout (NMDevice *self)
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
 	activation_source_schedule (self, activate_stage4_ip4_config_timeout, AF_INET);
 }
@@ -8526,7 +9201,7 @@ nm_device_activate_schedule_ip6_config_timeout (NMDevice *self)
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request);
+	g_return_if_fail (priv->act_request.obj);
 
 	activation_source_schedule (self, activate_stage4_ip6_config_timeout, AF_INET6);
 }
@@ -8644,9 +9319,9 @@ arp_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->arping.announcing) {
-		nm_arping_manager_destroy (priv->arping.announcing);
-		priv->arping.announcing = NULL;
+	if (priv->acd.announcing) {
+		nm_acd_manager_destroy (priv->acd.announcing);
+		priv->acd.announcing = NULL;
 	}
 }
 
@@ -8666,7 +9341,7 @@ arp_announce (NMDevice *self)
 	                                        nm_device_get_ip_ifindex (self),
 	                                        &hw_addr_len);
 
-	if (!hw_addr_len || !hw_addr)
+	if (!hw_addr || hw_addr_len != ETH_ALEN)
 		return;
 
 	/* We only care about manually-configured addresses; DHCP- and autoip-configured
@@ -8682,19 +9357,19 @@ arp_announce (NMDevice *self)
 	if (num == 0)
 		return;
 
-	priv->arping.announcing = nm_arping_manager_new (nm_device_get_ip_ifindex (self));
+	priv->acd.announcing = nm_acd_manager_new (nm_device_get_ip_ifindex (self), hw_addr, hw_addr_len);
 
 	for (i = 0; i < num; i++) {
 		NMIPAddress *ip = nm_setting_ip_config_get_address (s_ip4, i);
 		in_addr_t addr;
 
 		if (inet_pton (AF_INET, nm_ip_address_get_address (ip), &addr) == 1)
-			nm_arping_manager_add_address (priv->arping.announcing, addr);
+			nm_acd_manager_add_address (priv->acd.announcing, addr);
 		else
 			g_warn_if_reached ();
 	}
 
-	nm_arping_manager_announce_addresses (priv->arping.announcing);
+	nm_acd_manager_announce_addresses (priv->acd.announcing);
 }
 
 static void
@@ -8719,8 +9394,7 @@ activate_stage5_ip4_config_result (NMDevice *self)
 			_LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (self));
 	}
 
-	/* NULL to use the existing priv->dev_ip4_config */
-	if (!ip4_config_merge_and_apply (self, TRUE)) {
+	if (!ip_config_merge_and_apply (self, AF_INET, TRUE)) {
 		_LOGD (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 5 of 5 (IPv4 Commit) failed");
 		nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
 		return;
@@ -8732,7 +9406,7 @@ activate_stage5_ip4_config_result (NMDevice *self)
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) {
 		gs_free_error GError *error = NULL;
 
-		if (!start_sharing (self, priv->ip4_config, &error)) {
+		if (!start_sharing (self, priv->ip_config_4, &error)) {
 			_LOGW (LOGD_SHARING, "Activation: Stage 5 of 5 (IPv4 Commit) start sharing failed: %s", error->message);
 			nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
 			return;
@@ -8768,10 +9442,7 @@ nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *conf
 	g_return_if_fail (NM_IS_DEVICE (self));
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	g_clear_object (&priv->dev_ip4_config);
-	if (config)
-		priv->dev_ip4_config = g_object_ref (config);
-
+	applied_config_init (&priv->dev_ip4_config, config);
 	activation_source_schedule (self, activate_stage5_ip4_config_result, AF_INET);
 }
 
@@ -8796,6 +9467,32 @@ nm_device_activate_ip4_state_done (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_DONE;
 }
 
+static void
+dad6_add_pending_address (NMDevice *self,
+                          NMPlatform *platform,
+                          int ifindex,
+                          const struct in6_addr *address,
+                          NMIP6Config **dad6_config)
+{
+	const NMPlatformIP6Address *pl_addr;
+
+	pl_addr = nm_platform_ip6_address_get (platform,
+	                                       ifindex,
+	                                       *address);
+	if (   pl_addr
+	    && NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_TENTATIVE)
+	    && !NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_DADFAILED)
+	    && !NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_OPTIMISTIC)) {
+		_LOGt (LOGD_DEVICE, "IPv6 DAD: pending address %s",
+		       nm_platform_ip6_address_to_string (pl_addr, NULL, 0));
+
+		if (!*dad6_config)
+			*dad6_config = _ip6_config_new (self);
+
+		nm_ip6_config_add_address (*dad6_config, pl_addr);
+	}
+}
+
 /*
  * Returns a NMIP6Config containing NM-configured addresses which
  * have the tentative flag, or NULL if none is present.
@@ -8804,42 +9501,43 @@ static NMIP6Config *
 dad6_get_pending_addresses (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMIP6Config *confs[] = { priv->ac_ip6_config,
-	                         priv->dhcp6.ip6_config,
-	                         priv->con_ip6_config,
-	                         priv->wwan_ip6_config };
-	const NMPlatformIP6Address *addr, *pl_addr;
+	NMIP6Config *confs[] = { (NMIP6Config *) applied_config_get_current (&priv->ac_ip6_config),
+	                         (NMIP6Config *) applied_config_get_current (&priv->dhcp6.ip6_config),
+	                         priv->con_ip_config_6,
+	                         (NMIP6Config *) applied_config_get_current (&priv->wwan_ip_config_6) };
+	const NMPlatformIP6Address *addr;
 	NMIP6Config *dad6_config = NULL;
 	NMDedupMultiIter ipconf_iter;
 	guint i;
 	int ifindex;
+	NMPlatform *platform;
 
 	ifindex = nm_device_get_ip_ifindex (self);
 	g_return_val_if_fail (ifindex > 0, NULL);
 
+	platform = nm_device_get_platform (self);
+
+	if (priv->ipv6ll_has) {
+		dad6_add_pending_address (self,
+		                          platform,
+		                          ifindex,
+		                          &priv->ipv6ll_addr,
+		                          &dad6_config);
+	}
+
 	/* We are interested only in addresses that we have explicitly configured,
 	 * not in externally added ones.
 	 */
 	for (i = 0; i < G_N_ELEMENTS (confs); i++) {
-		if (confs[i]) {
-
-			nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, confs[i], &addr) {
-				pl_addr = nm_platform_ip6_address_get (nm_device_get_platform (self),
-				                                       ifindex,
-				                                       addr->address);
-				if (   pl_addr
-				    && NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_TENTATIVE)
-				    && !NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_DADFAILED)
-				    && !NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_OPTIMISTIC)) {
-					_LOGt (LOGD_DEVICE, "IPv6 DAD: pending address %s",
-					       nm_platform_ip6_address_to_string (pl_addr, NULL, 0));
-
-					if (!dad6_config)
-						dad6_config = _ip6_config_new (self);
-
-					nm_ip6_config_add_address (dad6_config, pl_addr);
-				}
-			}
+		if (!confs[i])
+			continue;
+
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, confs[i], &addr) {
+			dad6_add_pending_address (self,
+			                          platform,
+			                          ifindex,
+			                          &addr->address,
+			                          &dad6_config);
 		}
 	}
 
@@ -8871,10 +9569,10 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 			_LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (self));
 	}
 
-	if (ip6_config_merge_and_apply (self, TRUE)) {
+	if (ip_config_merge_and_apply (self, AF_INET6, TRUE)) {
 		if (   priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE
 		    && priv->ip6_state == IP_CONF) {
-			if (priv->dhcp6.ip6_config) {
+			if (applied_config_get_current (&priv->dhcp6.ip6_config)) {
 				/* If IPv6 wasn't the first IP to complete, and DHCP was used,
 				 * then ensure dispatcher scripts get the DHCP lease information.
 				 */
@@ -8961,43 +9659,26 @@ nm_device_activate_ip6_state_done (NMDevice *self)
 /*****************************************************************************/
 
 static void
-act_request_set_cb (NMActRequest *act_request,
-                    GParamSpec *pspec,
-                    NMDevice *self)
-{
-	_notify (self, PROP_ACTIVE_CONNECTION);
-}
-
-static void
 act_request_set (NMDevice *self, NMActRequest *act_request)
 {
 	NMDevicePrivate *priv;
-	gs_unref_object NMActRequest *old_act_requst = NULL;
 
 	nm_assert (NM_IS_DEVICE (self));
 	nm_assert (!act_request || NM_IS_ACT_REQUEST (act_request));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (   !priv->act_request_public
-	    && priv->act_request == act_request)
+	if (   !priv->act_request.visible
+	    && priv->act_request.obj == act_request)
 		return;
 
 	/* always clear the public flag. The few callers that set a new @act_request
 	 * don't want that the property is public yet.  */
-	priv->act_request_public = FALSE;
-
-	nm_clear_g_signal_handler (priv->act_request, &priv->act_request_id);
-
-	old_act_requst = priv->act_request;
-	priv->act_request = nm_g_object_ref (act_request);
+	nm_dbus_track_obj_path_set (&priv->act_request,
+	                            act_request,
+	                            FALSE);
 
 	if (act_request) {
-		priv->act_request_id = g_signal_connect (act_request,
-		                                         "notify::"NM_EXPORTED_OBJECT_PATH,
-		                                         G_CALLBACK (act_request_set_cb),
-		                                         self);
-
 		switch (nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (act_request))) {
 		case NM_ACTIVATION_TYPE_EXTERNAL:
 			break;
@@ -9014,8 +9695,6 @@ act_request_set (NMDevice *self, NMActRequest *act_request)
 			break;
 		}
 	}
-
-	_notify (self, PROP_ACTIVE_CONNECTION);
 }
 
 static void
@@ -9041,9 +9720,9 @@ _update_ip4_address (NMDevice *self)
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
-	if (   priv->ip4_config
+	if (   priv->ip_config_4
 	    && ip_config_valid (priv->state)
-	    && (address = nm_ip4_config_get_first_address (priv->ip4_config))) {
+	    && (address = nm_ip4_config_get_first_address (priv->ip_config_4))) {
 		if (address->address != priv->ip4_address) {
 			priv->ip4_address = address->address;
 			_notify (self, PROP_IP4_ADDRESS);
@@ -9137,37 +9816,31 @@ delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex)
 }
 
 static void
-_cleanup_ip4_pre (NMDevice *self, CleanupType cleanup_type)
+_cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-	_set_ip_state (self, AF_INET, IP_NONE);
-
-	if (nm_clear_g_source (&priv->queued_ip4_config_id))
-		_LOGD (LOGD_DEVICE, "clearing queued IP4 config change");
-	priv->queued_ip4_config_pending = FALSE;
-
-	dhcp4_cleanup (self, cleanup_type, FALSE);
-	arp_cleanup (self);
-	dnsmasq_cleanup (self);
-	ipv4ll_cleanup (self);
-}
+	_set_ip_state (self, addr_family, IP_NONE);
 
-static void
-_cleanup_ip6_pre (NMDevice *self, CleanupType cleanup_type)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	_set_ip_state (self, AF_INET6, IP_NONE);
-
-	if (nm_clear_g_source (&priv->queued_ip6_config_id))
-		_LOGD (LOGD_DEVICE, "clearing queued IP6 config change");
-	priv->queued_ip6_config_pending = FALSE;
+	if (nm_clear_g_source (&priv->queued_ip_config_id_x[IS_IPv4])) {
+		_LOGD (LOGD_DEVICE, "clearing queued IP%c config change",
+		       nm_utils_addr_family_to_char (addr_family));
+	}
 
-	g_clear_object (&priv->dad6_ip6_config);
-	dhcp6_cleanup (self, cleanup_type, FALSE);
-	linklocal6_cleanup (self);
-	addrconf6_cleanup (self);
+	if (IS_IPv4) {
+		priv->queued_ip4_config_pending = FALSE;
+		dhcp4_cleanup (self, cleanup_type, FALSE);
+		arp_cleanup (self);
+		dnsmasq_cleanup (self);
+		ipv4ll_cleanup (self);
+	} else {
+		priv->queued_ip6_config_pending = FALSE;
+		g_clear_object (&priv->dad6_ip6_config);
+		dhcp6_cleanup (self, cleanup_type, FALSE);
+		nm_clear_g_source (&priv->linklocal6_timeout_id);
+		addrconf6_cleanup (self);
+	}
 }
 
 gboolean
@@ -9186,7 +9859,7 @@ _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
 		gs_unref_hashtable GHashTable *check_dups = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL);
 
 		for (i = 0; argv[i]; i++) {
-			if (!nm_g_hash_table_add (check_dups, (char *) argv[i]))
+			if (!g_hash_table_add (check_dups, (char *) argv[i]))
 				nm_assert (FALSE);
 		}
 		nm_assert (g_hash_table_size (check_dups) > 0);
@@ -9237,8 +9910,7 @@ _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
 void
 nm_device_reactivate_ip4_config (NMDevice *self,
                                  NMSettingIPConfig *s_ip4_old,
-                                 NMSettingIPConfig *s_ip4_new,
-                                 gboolean force_restart)
+                                 NMSettingIPConfig *s_ip4_new)
 {
 	NMDevicePrivate *priv;
 	const char *method_old, *method_new;
@@ -9247,31 +9919,31 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->ip4_state != IP_NONE) {
-		g_clear_object (&priv->con_ip4_config);
-		g_clear_object (&priv->ext_ip4_config);
-		priv->con_ip4_config = _ip4_config_new (self);
-		nm_ip4_config_merge_setting (priv->con_ip4_config,
+		g_clear_object (&priv->con_ip_config_4);
+		g_clear_object (&priv->ext_ip_config_4);
+		g_clear_object (&priv->dev_ip4_config.current);
+		g_clear_object (&priv->wwan_ip_config_4.current);
+		priv->con_ip_config_4 = _ip4_config_new (self);
+		nm_ip4_config_merge_setting (priv->con_ip_config_4,
 		                             s_ip4_new,
+		                             _get_mdns (self),
 		                             nm_device_get_route_table (self, AF_INET, TRUE),
 		                             nm_device_get_route_metric (self, AF_INET));
 
-		if (!force_restart) {
-			method_old = s_ip4_old
-			             ? nm_setting_ip_config_get_method (s_ip4_old)
-			             : NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
-			method_new = s_ip4_new
-			             ? nm_setting_ip_config_get_method (s_ip4_new)
-			             : NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
-			force_restart = !nm_streq0 (method_old, method_new);
-		}
+		method_old = s_ip4_old
+		             ? nm_setting_ip_config_get_method (s_ip4_old)
+		             : NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+		method_new = s_ip4_new
+		             ? nm_setting_ip_config_get_method (s_ip4_new)
+		             : NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
 
-		if (force_restart) {
-			_cleanup_ip4_pre (self, CLEANUP_TYPE_DECONFIGURE);
+		if (!nm_streq0 (method_old, method_new)) {
+			_cleanup_ip_pre (self, AF_INET, CLEANUP_TYPE_DECONFIGURE);
 			_set_ip_state (self, AF_INET, IP_WAIT);
 			if (!nm_device_activate_stage3_ip4_start (self))
 				_LOGW (LOGD_IP4, "Failed to apply IPv4 configuration");
 		} else {
-			if (!ip4_config_merge_and_apply (self, TRUE))
+			if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 				_LOGW (LOGD_IP4, "Failed to reapply IPv4 configuration");
 		}
 	}
@@ -9280,8 +9952,7 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 void
 nm_device_reactivate_ip6_config (NMDevice *self,
                                  NMSettingIPConfig *s_ip6_old,
-                                 NMSettingIPConfig *s_ip6_new,
-                                 gboolean force_restart)
+                                 NMSettingIPConfig *s_ip6_new)
 {
 	NMDevicePrivate *priv;
 	const char *method_old, *method_new;
@@ -9290,31 +9961,34 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (priv->ip6_state != IP_NONE) {
-		g_clear_object (&priv->con_ip6_config);
-		g_clear_object (&priv->ext_ip6_config);
-		priv->con_ip6_config = _ip6_config_new (self);
-		nm_ip6_config_merge_setting (priv->con_ip6_config,
+		g_clear_object (&priv->con_ip_config_6);
+		g_clear_object (&priv->ext_ip_config_6);
+		g_clear_object (&priv->ac_ip6_config.current);
+		g_clear_object (&priv->dhcp6.ip6_config.current);
+		g_clear_object (&priv->wwan_ip_config_6.current);
+		if (   priv->ipv6ll_handle
+		    && !IN6_IS_ADDR_UNSPECIFIED (&priv->ipv6ll_addr))
+			priv->ipv6ll_has = TRUE;
+		priv->con_ip_config_6 = _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_metric (self, AF_INET6));
 
-		if (!force_restart) {
-			method_old = s_ip6_old
-			             ? nm_setting_ip_config_get_method (s_ip6_old)
-			             : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
-			method_new = s_ip6_new
-			             ? nm_setting_ip_config_get_method (s_ip6_new)
-			             : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
-			force_restart = !nm_streq0 (method_old, method_new);
-		}
+		method_old = s_ip6_old
+		             ? nm_setting_ip_config_get_method (s_ip6_old)
+		             : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
+		method_new = s_ip6_new
+		             ? nm_setting_ip_config_get_method (s_ip6_new)
+		             : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
 
-		if (force_restart) {
-			_cleanup_ip6_pre (self, CLEANUP_TYPE_DECONFIGURE);
+		if (!nm_streq0 (method_old, method_new)) {
+			_cleanup_ip_pre (self, AF_INET6, CLEANUP_TYPE_DECONFIGURE);
 			_set_ip_state (self, AF_INET6, IP_WAIT);
 			if (!nm_device_activate_stage3_ip6_start (self))
 				_LOGW (LOGD_IP6, "Failed to apply IPv6 configuration");
 		} else {
-			if (!ip6_config_merge_and_apply (self, TRUE))
+			if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 				_LOGW (LOGD_IP4, "Failed to reapply IPv6 configuration");
 		}
 	}
@@ -9359,7 +10033,7 @@ can_reapply_change (NMDevice *self, const char *setting_name,
 		 * allowed to differ.
 		 *
 		 * This includes UUID, there is no principal problem with reapplying a
-		 * connection and changing it's UUID. In fact, disallowing it makes it
+		 * connection and changing its UUID. In fact, disallowing it makes it
 		 * cumbersome for the user to reapply any connection but the original
 		 * settings-connection. */
 		return nm_device_hash_check_invalid_keys (diffs,
@@ -9485,7 +10159,7 @@ check_and_reapply_connection (NMDevice *self,
 	}
 
 	if (   version_id != 0
-	    && version_id != nm_active_connection_version_id_get ((NMActiveConnection *) priv->act_request)) {
+	    && version_id != nm_active_connection_version_id_get ((NMActiveConnection *) priv->act_request.obj)) {
 		g_set_error_literal (error,
 		                     NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_VERSION_ID_MISMATCH,
@@ -9498,10 +10172,10 @@ check_and_reapply_connection (NMDevice *self,
 	 *************************************************************************/
 
 	if (diffs)
-		nm_active_connection_version_id_bump ((NMActiveConnection *) priv->act_request);
+		nm_active_connection_version_id_bump ((NMActiveConnection *) priv->act_request.obj);
 
 	_LOGD (LOGD_DEVICE, "reapply (version-id %llu%s)",
-	       (unsigned long long) nm_active_connection_version_id_get (((NMActiveConnection *) priv->act_request)),
+	       (unsigned long long) nm_active_connection_version_id_get (((NMActiveConnection *) priv->act_request.obj)),
 	       diffs ? "" : " (unmodified)");
 
 	if (diffs) {
@@ -9557,8 +10231,8 @@ 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);
 
-	nm_device_reactivate_ip4_config (self, s_ip4_old, s_ip4_new, TRUE);
-	nm_device_reactivate_ip6_config (self, s_ip6_old, s_ip6_new, TRUE);
+	nm_device_reactivate_ip4_config (self, s_ip4_old, s_ip4_new);
+	nm_device_reactivate_ip6_config (self, s_ip6_old, s_ip6_new);
 
 	reactivate_proxy_config (self);
 
@@ -9627,25 +10301,33 @@ reapply_cb (NMDevice *self,
 }
 
 static void
-impl_device_reapply (NMDevice *self,
-                     GDBusMethodInvocation *context,
-                     GVariant *settings,
-                     guint64 version_id,
-                     guint32 flags)
+impl_device_reapply (NMDBusObject *obj,
+                     const NMDBusInterfaceInfoExtended *interface_info,
+                     const NMDBusMethodInfoExtended *method_info,
+                     GDBusConnection *dbus_connection,
+                     const char *sender,
+                     GDBusMethodInvocation *invocation,
+                     GVariant *parameters)
 {
+	NMDevice *self = NM_DEVICE (obj);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingsConnection *settings_connection;
 	NMConnection *connection = NULL;
 	GError *error = NULL;
 	ReapplyData *reapply_data;
+	gs_unref_variant GVariant *settings = NULL;
+	guint64 version_id;
+	guint32 flags;
+
+	g_variant_get (parameters, "(@a{sa{sv}}tu)", &settings, &version_id, &flags);
 
 	/* No flags supported as of now. */
 	if (flags != 0) {
 		error = g_error_new_literal (NM_DEVICE_ERROR,
 		                             NM_DEVICE_ERROR_FAILED,
 		                             "Invalid flags specified");
-		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, NULL, context, error->message);
-		g_dbus_method_invocation_take_error (context, error);
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, NULL, invocation, error->message);
+		g_dbus_method_invocation_take_error (invocation, error);
 		return;
 	}
 
@@ -9653,8 +10335,8 @@ impl_device_reapply (NMDevice *self,
 		error = g_error_new_literal (NM_DEVICE_ERROR,
 		                             NM_DEVICE_ERROR_NOT_ACTIVE,
 		                             "Device is not activated");
-		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, NULL, context, error->message);
-		g_dbus_method_invocation_take_error (context, error);
+		nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, NULL, invocation, error->message);
+		g_dbus_method_invocation_take_error (invocation, error);
 		return;
 	}
 
@@ -9669,8 +10351,8 @@ impl_device_reapply (NMDevice *self,
 		                                                  &error);
 		if (!connection) {
 			g_prefix_error (&error, "The settings specified are invalid: ");
-			nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, NULL, context, error->message);
-			g_dbus_method_invocation_take_error (context, error);
+			nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, NULL, invocation, error->message);
+			g_dbus_method_invocation_take_error (invocation, error);
 			return;
 		}
 		nm_connection_clear_secrets (connection);
@@ -9683,9 +10365,8 @@ impl_device_reapply (NMDevice *self,
 	} else
 		reapply_data = NULL;
 
-	/* Ask the manager to authenticate this request for us */
 	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               context,
+	               invocation,
 	               nm_device_get_applied_connection (self),
 	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	               TRUE,
@@ -9744,40 +10425,44 @@ get_applied_connection_cb (NMDevice *self,
 	g_dbus_method_invocation_return_value (context,
 	                                       g_variant_new ("(@a{sa{sv}}t)",
 	                                                      settings,
-	                                                      nm_active_connection_version_id_get ((NMActiveConnection *) priv->act_request)));
+	                                                      nm_active_connection_version_id_get ((NMActiveConnection *) priv->act_request.obj)));
 }
 
 static void
-impl_device_get_applied_connection (NMDevice *self,
-                                    GDBusMethodInvocation *context,
-                                    guint32 flags)
+impl_device_get_applied_connection (NMDBusObject *obj,
+                                    const NMDBusInterfaceInfoExtended *interface_info,
+                                    const NMDBusMethodInfoExtended *method_info,
+                                    GDBusConnection *connection,
+                                    const char *sender,
+                                    GDBusMethodInvocation *invocation,
+                                    GVariant *parameters)
 {
+	NMDevice *self = NM_DEVICE (obj);
 	NMConnection *applied_connection;
-	GError *error = NULL;
+	guint32 flags;
 
-	g_return_if_fail (NM_IS_DEVICE (self));
+	g_variant_get (parameters, "(u)", &flags);
 
 	/* No flags supported as of now. */
 	if (flags != 0) {
-		error = g_error_new_literal (NM_DEVICE_ERROR,
-		                             NM_DEVICE_ERROR_FAILED,
-		                             "Invalid flags specified");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_FAILED,
+		                                               "Invalid flags specified");
 		return;
 	}
 
 	applied_connection = nm_device_get_applied_connection (self);
 	if (!applied_connection) {
-		error = g_error_new_literal (NM_DEVICE_ERROR,
-		                             NM_DEVICE_ERROR_NOT_ACTIVE,
-		                             "Device is not activated");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "Device is not activated");
 		return;
 	}
 
-	/* Ask the manager to authenticate this request for us */
 	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               context,
+	               invocation,
 	               applied_connection,
 	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	               TRUE,
@@ -9926,25 +10611,31 @@ _clear_queued_act_request (NMDevicePrivate *priv)
 }
 
 static void
-impl_device_disconnect (NMDevice *self, GDBusMethodInvocation *context)
+impl_device_disconnect (NMDBusObject *obj,
+                        const NMDBusInterfaceInfoExtended *interface_info,
+                        const NMDBusMethodInfoExtended *method_info,
+                        GDBusConnection *dbus_connection,
+                        const char *sender,
+                        GDBusMethodInvocation *invocation,
+                        GVariant *parameters)
 {
+	NMDevice *self = NM_DEVICE (obj);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
-	GError *error = NULL;
 
-	if (NM_DEVICE_GET_PRIVATE (self)->act_request == NULL) {
-		error = g_error_new_literal (NM_DEVICE_ERROR,
-		                             NM_DEVICE_ERROR_NOT_ACTIVE,
-		                             "This device is not active");
-		g_dbus_method_invocation_take_error (context, error);
+	if (!priv->act_request.obj) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "This device is not active");
 		return;
 	}
 
 	connection = nm_device_get_applied_connection (self);
-	g_assert (connection);
+	nm_assert (connection);
 
-	/* Ask the manager to authenticate this request for us */
 	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               context,
+	               invocation,
 	               connection,
 	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	               TRUE,
@@ -9976,21 +10667,27 @@ delete_cb (NMDevice *self,
 }
 
 static void
-impl_device_delete (NMDevice *self, GDBusMethodInvocation *context)
+impl_device_delete (NMDBusObject *obj,
+                    const NMDBusInterfaceInfoExtended *interface_info,
+                    const NMDBusMethodInfoExtended *method_info,
+                    GDBusConnection *connection,
+                    const char *sender,
+                    GDBusMethodInvocation *invocation,
+                    GVariant *parameters)
 {
-	GError *error = NULL;
+	NMDevice *self = NM_DEVICE (obj);
 
-	if (!nm_device_is_software (self) || !nm_device_is_real (self)) {
-		error = g_error_new_literal (NM_DEVICE_ERROR,
-		                             NM_DEVICE_ERROR_NOT_SOFTWARE,
-		                             "This device is not a software device or is not realized");
-		g_dbus_method_invocation_take_error (context, error);
+	if (   !nm_device_is_software (self)
+	    || !nm_device_is_real (self)) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_SOFTWARE,
+		                                               "This device is not a software device or is not realized");
 		return;
 	}
 
-	/* Ask the manager to authenticate this request for us */
 	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               context,
+	               invocation,
 	               NULL,
 	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	               TRUE,
@@ -10114,8 +10811,8 @@ nm_device_steal_connection (NMDevice *self, NMSettingsConnection *connection)
 	    && connection == nm_active_connection_get_settings_connection (NM_ACTIVE_CONNECTION (priv->queued_act_request)))
 		_clear_queued_act_request (priv);
 
-	if (   priv->act_request
-	    && connection == nm_active_connection_get_settings_connection (NM_ACTIVE_CONNECTION (priv->act_request))
+	if (   priv->act_request.obj
+	    && connection == nm_active_connection_get_settings_connection (NM_ACTIVE_CONNECTION (priv->act_request.obj))
 	    && priv->state < NM_DEVICE_STATE_DEACTIVATING) {
 		nm_device_state_changed (self,
 		                         NM_DEVICE_STATE_DEACTIVATING,
@@ -10131,7 +10828,7 @@ nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 
 	must_queue = _carrier_wait_check_act_request_must_queue (self, req);
 
-	if (   !priv->act_request
+	if (   !priv->act_request.obj
 	    && !must_queue
 	    && nm_device_is_real (self)) {
 		_device_activate (self, req);
@@ -10146,7 +10843,7 @@ nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 	_LOGD (LOGD_DEVICE, "queue activation request waiting for %s", must_queue ? "carrier" : "currently active connection to disconnect");
 
 	/* Deactivate existing activation request first */
-	if (priv->act_request) {
+	if (priv->act_request.obj) {
 		_LOGI (LOGD_DEVICE, "disconnecting for new activation request.");
 		nm_device_state_changed (self,
 		                         NM_DEVICE_STATE_DEACTIVATING,
@@ -10231,116 +10928,155 @@ nm_device_get_ip4_config (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	return NM_DEVICE_GET_PRIVATE (self)->ip4_config;
+	return NM_DEVICE_GET_PRIVATE (self)->ip_config_4;
 }
 
 
 static gboolean
-nm_device_set_ip4_config (NMDevice *self,
-                          NMIP4Config *new_config,
-                          gboolean commit,
-                          GPtrArray *ip4_dev_route_blacklist)
+nm_device_set_ip_config (NMDevice *self,
+                         int addr_family,
+                         NMIPConfig *new_config,
+                         gboolean commit,
+                         GPtrArray *ip4_dev_route_blacklist)
 {
-	NMDevicePrivate *priv;
-	NMIP4Config *old_config = NULL;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+	NMIPConfig *old_config;
 	gboolean has_changes = FALSE;
 	gboolean success = TRUE;
+	NMSettingsConnection *settings_connection;
 
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
-
-	_LOGD (LOGD_IP4, "ip4-config: update (commit=%d, new-config=%p)",
-	       commit, new_config);
-
+	nm_assert_addr_family (addr_family);
+	nm_assert (!new_config || nm_ip_config_get_addr_family (new_config) == addr_family);
 	nm_assert (   !new_config
 	           || (   new_config
 	               && ({
 	                    int ip_ifindex = nm_device_get_ip_ifindex (self);
 
 	                    (   ip_ifindex > 0
-	                     && ip_ifindex == nm_ip4_config_get_ifindex (new_config));
+	                     && ip_ifindex == nm_ip_config_get_ifindex (new_config));
 	                  })));
+	nm_assert (IS_IPv4 || !ip4_dev_route_blacklist);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-
-	old_config = priv->ip4_config;
+	_LOGD (LOGD_IP_from_af (addr_family),
+	       "ip%c-config: update (commit=%d, new-config=%p)",
+	       nm_utils_addr_family_to_char (addr_family),
+	       commit,
+	       new_config);
 
 	/* Always commit to nm-platform to update lifetimes */
 	if (commit && new_config) {
-		_commit_mtu (self, new_config);
-		success = nm_ip4_config_commit (new_config,
-		                                nm_device_get_platform (self),
-		                                nm_device_get_route_table (self, AF_INET, FALSE)
-		                                  ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
-		                                  : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN);
-		nm_platform_ip4_dev_route_blacklist_set (nm_device_get_platform (self),
-		                                         nm_ip4_config_get_ifindex (new_config),
-		                                         ip4_dev_route_blacklist);
-	}
-
-	if (new_config) {
-		if (old_config) {
-			/* has_changes is set only on relevant changes, because when the configuration changes,
-			 * this causes a re-read and reset. This should only happen for relevant changes */
-			nm_ip4_config_replace (old_config, new_config, &has_changes);
-			if (has_changes) {
-				_LOGD (LOGD_IP4, "ip4-config: update IP4Config instance (%s)",
-				       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
-			}
+
+		_commit_mtu (self,
+		             IS_IPv4
+		               ? NM_IP4_CONFIG (new_config)
+		               : priv->ip_config_4);
+
+		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);
+			nm_platform_ip4_dev_route_blacklist_set (nm_device_get_platform (self),
+			                                         nm_ip_config_get_ifindex (new_config),
+			                                         ip4_dev_route_blacklist);
 		} else {
-			has_changes = TRUE;
-			priv->ip4_config = g_object_ref (new_config);
+			gs_unref_ptrarray GPtrArray *temporary_not_available = NULL;
 
-			if (success && !nm_exported_object_is_exported (NM_EXPORTED_OBJECT (new_config)))
-				nm_exported_object_export (NM_EXPORTED_OBJECT (new_config));
+			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,
+			                                &temporary_not_available);
 
-			_LOGD (LOGD_IP4, "ip4-config: set IP4Config instance (%s)",
-			       nm_exported_object_get_path (NM_EXPORTED_OBJECT (new_config)));
+			if (!_rt6_temporary_not_available_set (self, temporary_not_available))
+				success = FALSE;
 		}
-	} else if (old_config) {
-		has_changes = TRUE;
-		priv->ip4_config = NULL;
-		_LOGD (LOGD_IP4, "ip4-config: clear IP4Config instance (%s)",
-		       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
-		/* Device config is invalid if combined config is invalid */
-		g_clear_object (&priv->dev_ip4_config);
 	}
 
-	concheck_periodic_update (self);
+	old_config = priv->ip_config_x[IS_IPv4];
 
-	if (!nm_device_sys_iface_state_is_external_or_assume (self))
-		ip4_rp_filter_update (self);
+	if (new_config && old_config) {
+		/* has_changes is set only on relevant changes, because when the configuration changes,
+		 * this causes a re-read and reset. This should only happen for relevant changes */
+		nm_ip_config_replace (old_config, new_config, &has_changes);
+		if (has_changes) {
+			_LOGD (LOGD_IP_from_af (addr_family),
+			       "ip%c-config: update IP Config instance (%s)",
+			       nm_utils_addr_family_to_char (addr_family),
+			       nm_dbus_object_get_path (NM_DBUS_OBJECT (old_config)));
+		}
+	} else if (new_config /*&& !old_config*/) {
+		has_changes = TRUE;
+		priv->ip_config_x[IS_IPv4] = g_object_ref (new_config);
+		if (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (new_config)))
+			nm_dbus_object_export (NM_DBUS_OBJECT (new_config));
+
+		_LOGD (LOGD_IP_from_af (addr_family),
+		       "ip%c-config: set IP Config instance (%s)",
+		       nm_utils_addr_family_to_char (addr_family),
+		       nm_dbus_object_get_path (NM_DBUS_OBJECT (new_config)));
+	} else if (old_config /*&& !new_config*/) {
+		has_changes = TRUE;
+		priv->ip_config_x[IS_IPv4] = NULL;
+		_LOGD (LOGD_IP_from_af (addr_family),
+		       "ip%c-config: clear IP Config instance (%s)",
+		       nm_utils_addr_family_to_char (addr_family),
+		       nm_dbus_object_get_path (NM_DBUS_OBJECT (old_config)));
+		if (IS_IPv4) {
+			/* Device config is invalid if combined config is invalid */
+			applied_config_clear (&priv->dev_ip4_config);
+		} else
+			priv->needs_ip6_subnet = FALSE;
+	}
+
+	if (IS_IPv4) {
+		if (!nm_device_sys_iface_state_is_external_or_assume (self))
+			ip4_rp_filter_update (self);
+	}
 
 	if (has_changes) {
-		NMSettingsConnection *settings_connection;
 
-		_update_ip4_address (self);
+		if (IS_IPv4)
+			_update_ip4_address (self);
 
-		if (old_config != priv->ip4_config)
-			_notify (self, PROP_IP4_CONFIG);
-		g_signal_emit (self, signals[IP4_CONFIG_CHANGED], 0, priv->ip4_config, old_config);
+		if (old_config != priv->ip_config_x[IS_IPv4])
+			_notify (self, IS_IPv4 ? PROP_IP4_CONFIG : PROP_IP6_CONFIG);
 
-		if (old_config != priv->ip4_config)
-			nm_exported_object_clear_and_unexport (&old_config);
+		g_signal_emit (self,
+		               signals[IS_IPv4 ? IP4_CONFIG_CHANGED : IP6_CONFIG_CHANGED],
+		               0,
+		               priv->ip_config_x[IS_IPv4],
+		               old_config);
+
+		if (old_config != priv->ip_config_x[IS_IPv4])
+			nm_dbus_object_clear_and_unexport (&old_config);
 
 		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_FLAGS_NM_GENERATED)
-		    && nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (priv->act_request)) == NM_ACTIVATION_TYPE_EXTERNAL) {
-			NMSetting *s_ip4;
-
+		                     NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)
+		    && nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (priv->act_request.obj)) == NM_ACTIVATION_TYPE_EXTERNAL) {
 			g_object_freeze_notify (G_OBJECT (settings_connection));
-
-			nm_connection_remove_setting (NM_CONNECTION (settings_connection), NM_TYPE_SETTING_IP4_CONFIG);
-			s_ip4 = nm_ip4_config_create_setting (priv->ip4_config);
-			nm_connection_add_setting (NM_CONNECTION (settings_connection), s_ip4);
-
+			nm_connection_add_setting (NM_CONNECTION (settings_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_device_queue_recheck_assume (self);
+
+		if (!IS_IPv4) {
+			if (priv->ndisc)
+				ndisc_set_router_config (priv->ndisc, self);
+		}
 	}
 
+	nm_assert (!old_config || old_config == priv->ip_config_x[IS_IPv4]);
+
 	return success;
 }
 
@@ -10387,11 +11123,11 @@ nm_device_replace_vpn4_config (NMDevice *self, NMIP4Config *old, NMIP4Config *co
 	nm_assert (!old || nm_ip4_config_get_ifindex (old) == nm_device_get_ip_ifindex (self));
 	nm_assert (!config || nm_ip4_config_get_ifindex (config) == nm_device_get_ip_ifindex (self));
 
-	if (!_replace_vpn_config_in_list (&priv->vpn4_configs, (GObject *) old, (GObject *) config))
+	if (!_replace_vpn_config_in_list (&priv->vpn_configs_4, (GObject *) old, (GObject *) config))
 		return;
 
 	/* NULL to use existing configs */
-	if (!ip4_config_merge_and_apply (self, TRUE))
+	if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 		_LOGW (LOGD_IP4, "failed to set VPN routes for device");
 }
 
@@ -10400,125 +11136,11 @@ nm_device_set_wwan_ip4_config (NMDevice *self, NMIP4Config *config)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->wwan_ip4_config == config)
-		return;
-
-	g_clear_object (&priv->wwan_ip4_config);
-	if (config)
-		priv->wwan_ip4_config = g_object_ref (config);
-
-	/* NULL to use existing configs */
-	if (!ip4_config_merge_and_apply (self, TRUE))
+	applied_config_init (&priv->wwan_ip_config_4, config);
+	if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 		_LOGW (LOGD_IP4, "failed to set WWAN IPv4 configuration");
 }
 
-static gboolean
-nm_device_set_ip6_config (NMDevice *self,
-                          NMIP6Config *new_config,
-                          gboolean commit)
-{
-	NMDevicePrivate *priv;
-	NMIP6Config *old_config = NULL;
-	gboolean has_changes = FALSE;
-	gboolean success = TRUE;
-
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
-
-	_LOGD (LOGD_IP6, "ip6-config: update (commit=%d, new-config=%p)",
-	       commit, new_config);
-
-	nm_assert (   !new_config
-	           || (   new_config
-	               && ({
-	                    int ip_ifindex = nm_device_get_ip_ifindex (self);
-
-	                    (   ip_ifindex > 0
-	                     && ip_ifindex == nm_ip6_config_get_ifindex (new_config));
-	                  })));
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-
-	old_config = priv->ip6_config;
-
-	/* Always commit to nm-platform to update lifetimes */
-	if (commit && new_config) {
-		gs_unref_ptrarray GPtrArray *temporary_not_available = NULL;
-
-		_commit_mtu (self, priv->ip4_config);
-
-		success = nm_ip6_config_commit (new_config,
-		                                nm_device_get_platform (self),
-		                                nm_device_get_route_table (self, AF_INET6, FALSE)
-		                                  ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
-		                                  : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN,
-		                                &temporary_not_available);
-
-		if (!_rt6_temporary_not_available_set (self, temporary_not_available))
-			success = FALSE;
-	}
-
-	if (new_config) {
-		if (old_config) {
-			/* has_changes is set only on relevant changes, because when the configuration changes,
-			 * this causes a re-read and reset. This should only happen for relevant changes */
-			nm_ip6_config_replace (old_config, new_config, &has_changes);
-			if (has_changes) {
-				_LOGD (LOGD_IP6, "ip6-config: update IP6Config instance (%s)",
-				       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
-			}
-		} else {
-			has_changes = TRUE;
-			priv->ip6_config = g_object_ref (new_config);
-
-			if (success && !nm_exported_object_is_exported (NM_EXPORTED_OBJECT (new_config)))
-				nm_exported_object_export (NM_EXPORTED_OBJECT (new_config));
-
-			_LOGD (LOGD_IP6, "ip6-config: set IP6Config instance (%s)",
-			       nm_exported_object_get_path (NM_EXPORTED_OBJECT (new_config)));
-		}
-	} else if (old_config) {
-		has_changes = TRUE;
-		priv->ip6_config = NULL;
-		priv->needs_ip6_subnet = FALSE;
-		_LOGD (LOGD_IP6, "ip6-config: clear IP6Config instance (%s)",
-		       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
-	}
-
-	if (has_changes) {
-		NMSettingsConnection *settings_connection;
-
-		if (old_config != priv->ip6_config)
-			_notify (self, PROP_IP6_CONFIG);
-		g_signal_emit (self, signals[IP6_CONFIG_CHANGED], 0, priv->ip6_config, old_config);
-
-		if (old_config != priv->ip6_config)
-			nm_exported_object_clear_and_unexport (&old_config);
-
-		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_FLAGS_NM_GENERATED)
-		    && nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (priv->act_request)) == NM_ACTIVATION_TYPE_EXTERNAL) {
-			NMSetting *s_ip6;
-
-			g_object_freeze_notify (G_OBJECT (settings_connection));
-
-			nm_connection_remove_setting (NM_CONNECTION (settings_connection), NM_TYPE_SETTING_IP6_CONFIG);
-			s_ip6 = nm_ip6_config_create_setting (priv->ip6_config);
-			nm_connection_add_setting (NM_CONNECTION (settings_connection), s_ip6);
-
-			g_object_thaw_notify (G_OBJECT (settings_connection));
-		}
-
-		nm_device_queue_recheck_assume (self);
-
-		if (priv->ndisc)
-			ndisc_set_router_config (priv->ndisc, self);
-	}
-
-	return success;
-}
-
 void
 nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *config)
 {
@@ -10529,11 +11151,11 @@ nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *co
 	nm_assert (!old || nm_ip6_config_get_ifindex (old) == nm_device_get_ip_ifindex (self));
 	nm_assert (!config || nm_ip6_config_get_ifindex (config) == nm_device_get_ip_ifindex (self));
 
-	if (!_replace_vpn_config_in_list (&priv->vpn6_configs, (GObject *) old, (GObject *) config))
+	if (!_replace_vpn_config_in_list (&priv->vpn_configs_6, (GObject *) old, (GObject *) config))
 		return;
 
 	/* NULL to use existing configs */
-	if (!ip6_config_merge_and_apply (self, TRUE))
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 		_LOGW (LOGD_IP6, "failed to set VPN routes for device");
 }
 
@@ -10542,15 +11164,8 @@ nm_device_set_wwan_ip6_config (NMDevice *self, NMIP6Config *config)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->wwan_ip6_config == config)
-		return;
-
-	g_clear_object (&priv->wwan_ip6_config);
-	if (config)
-		priv->wwan_ip6_config = g_object_ref (config);
-
-	/* NULL to use existing configs */
-	if (!ip6_config_merge_and_apply (self, TRUE))
+	applied_config_init (&priv->wwan_ip_config_6, config);
+	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 		_LOGW (LOGD_IP6, "failed to set WWAN IPv6 configuration");
 }
 
@@ -10567,7 +11182,7 @@ nm_device_get_ip6_config (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	return NM_DEVICE_GET_PRIVATE (self)->ip6_config;
+	return NM_DEVICE_GET_PRIVATE (self)->ip_config_6;
 }
 
 /*****************************************************************************/
@@ -10758,7 +11373,7 @@ start_ping (NMDevice *self,
 	priv->gw_ping.log_domain = log_domain;
 	priv->gw_ping.address = g_strdup (address);
 	priv->gw_ping.binary = g_strdup (binary);
-	priv->gw_ping.deadline = timeout + 10;	/* the proper termination is enforced by a timer */
+	priv->gw_ping.deadline = timeout + 10; /* the proper termination is enforced by a timer */
 
 	if (spawn_ping (self)) {
 		priv->gw_ping.watch = g_child_watch_add (priv->gw_ping.pid, ip_check_ping_watch_cb, self);
@@ -10801,15 +11416,15 @@ nm_device_start_ip_check (NMDevice *self)
 	if (timeout) {
 		const NMPObject *gw;
 
-		if (priv->ip4_config && priv->ip4_state == IP_DONE) {
-			gw = nm_ip4_config_best_default_route_get (priv->ip4_config);
+		if (priv->ip_config_4 && priv->ip4_state == IP_DONE) {
+			gw = nm_ip4_config_best_default_route_get (priv->ip_config_4);
 			if (gw) {
 				nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (gw)->gateway, buf);
 				ping_binary = nm_utils_find_helper ("ping", "/usr/bin/ping", NULL);
 				log_domain = LOGD_IP4;
 			}
-		} else if (priv->ip6_config && priv->ip6_state == IP_DONE) {
-			gw = nm_ip6_config_best_default_route_get (priv->ip6_config);
+		} else if (priv->ip_config_6 && priv->ip6_state == IP_DONE) {
+			gw = nm_ip6_config_best_default_route_get (priv->ip_config_6);
 			if (gw) {
 				nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (gw)->gateway, buf);
 				ping_binary = nm_utils_find_helper ("ping6", "/usr/bin/ping6", NULL);
@@ -10949,11 +11564,11 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 
 	/* when the link comes up, we must restore IP configuration if necessary. */
 	if (priv->ip4_state == IP_DONE) {
-		if (!ip4_config_merge_and_apply (self, TRUE))
+		if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 			_LOGW (LOGD_IP4, "failed applying IP4 config after bringing link up");
 	}
 	if (priv->ip6_state == IP_DONE) {
-		if (!ip6_config_merge_and_apply (self, TRUE))
+		if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 			_LOGW (LOGD_IP6, "failed applying IP6 config after bringing link up");
 	}
 
@@ -11018,122 +11633,37 @@ nm_device_get_firmware_missing (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->firmware_missing;
 }
 
-static NMIP4Config *
-find_ip4_lease_config (NMDevice *self,
-                       NMConnection *connection,
-                       NMIP4Config *ext_ip4_config)
-{
-	const char *ip_iface = nm_device_get_ip_iface (self);
-	int ip_ifindex = nm_device_get_ip_ifindex (self);
-	GSList *leases, *liter;
-	NMIP4Config *found = NULL;
-
-	g_return_val_if_fail (NM_IS_IP4_CONFIG (ext_ip4_config), NULL);
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
-
-	leases = nm_dhcp_manager_get_lease_ip_configs (nm_dhcp_manager_get (),
-	                                               nm_device_get_multi_index (self),
-	                                               AF_INET,
-	                                               ip_iface,
-	                                               ip_ifindex,
-	                                               nm_connection_get_uuid (connection),
-	                                               nm_device_get_route_table (self, AF_INET, TRUE),
-	                                               nm_device_get_route_metric (self, AF_INET));
-	for (liter = leases; liter && !found; liter = liter->next) {
-		NMIP4Config *lease_config = liter->data;
-		const NMPlatformIP4Address *address = nm_ip4_config_get_first_address (lease_config);
-		const NMPObject *gw1, *gw2;
-
-		g_assert (address);
-		if (!nm_ip4_config_address_exists (ext_ip4_config, address))
-			continue;
-		gw1 = nm_ip4_config_best_default_route_get (lease_config);
-		if (!gw1)
-			continue;
-		gw2 = nm_ip4_config_best_default_route_get (ext_ip4_config);
-		if (!gw2)
-			continue;
-		if (NMP_OBJECT_CAST_IP4_ROUTE (gw1)->gateway != NMP_OBJECT_CAST_IP4_ROUTE (gw2)->gateway)
-			continue;
-		found = g_object_ref (lease_config);
-	}
-
-	g_slist_free_full (leases, g_object_unref);
-	return found;
-}
-
 static void
-capture_lease_config (NMDevice *self,
-                      NMIP4Config *ext_ip4_config,
-                      NMIP4Config **out_ip4_config,
-                      NMIP6Config *ext_ip6_config,
-                      NMIP6Config **out_ip6_config)
+intersect_ext_config (NMDevice *self, AppliedConfig *config)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMSettingsConnection *const*connections;
-	guint i;
-	gboolean dhcp_used = FALSE;
-	NMDedupMultiIter ipconf_iter;
+	NMIPConfig *ext;
+	guint32 penalty;
+	int family;
 
-	/* Ensure at least one address on the device has a non-infinite lifetime,
-	 * otherwise DHCP cannot possibly be active on the device right now.
-	 */
-	if (ext_ip4_config && out_ip4_config) {
-		const NMPlatformIP4Address *addr;
-
-		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ext_ip4_config, &addr) {
-			if (addr->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) {
-				dhcp_used = TRUE;
-				break;
-			}
-		}
-	} else if (ext_ip6_config && out_ip6_config) {
-		const NMPlatformIP6Address *addr;
-
-		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ext_ip6_config, &addr) {
-			if (addr->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) {
-				dhcp_used = TRUE;
-				break;
-			}
-		}
-	} else {
-		g_return_if_fail (   (ext_ip6_config && out_ip6_config)
-		                  || (ext_ip4_config && out_ip4_config));
-	}
-
-	if (!dhcp_used)
+	if (!config->orig)
 		return;
 
-	connections = nm_settings_get_connections (priv->settings, NULL);
-	for (i = 0; connections[i]; i++) {
-		NMConnection *candidate = (NMConnection *) connections[i];
-		const char *method;
-
-		if (!nm_device_check_connection_compatible (self, candidate))
-			continue;
-
-		/* IPv4 leases */
-		method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP4_CONFIG);
-		if (out_ip4_config && strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
-			*out_ip4_config = find_ip4_lease_config (self, candidate, ext_ip4_config);
-			if (*out_ip4_config)
-				return;
-		}
+	family = nm_ip_config_get_addr_family (config->orig);
+	penalty = default_route_metric_penalty_get (self, family);
+	ext = family == AF_INET
+	      ? (NMIPConfig *) priv->ext_ip_config_4
+	      : (NMIPConfig *) priv->ext_ip_config_6;
 
-		/* IPv6 leases */
-		method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP6_CONFIG);
-		if (out_ip6_config && strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) {
-			/* FIXME: implement find_ip6_lease_config() */
-		}
+	if (config->current)
+		nm_ip_config_intersect (config->current, ext, penalty);
+	else {
+		config->current = nm_ip_config_intersect_alloc (config->orig,
+		                                                ext,
+		                                                penalty);
 	}
 }
 
 static gboolean
-update_ext_ip_config (NMDevice *self, int addr_family, gboolean initial, gboolean intersect_configs)
+update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_configs)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int ifindex;
-	gboolean capture_resolv_conf;
 	GSList *iter;
 
 	nm_assert_addr_family (addr_family);
@@ -11142,122 +11672,110 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean initial, gboolea
 	if (!ifindex)
 		return FALSE;
 
-	capture_resolv_conf =    initial
-	                      && nm_dns_manager_get_resolv_conf_explicit (nm_dns_manager_get ());
-
 	if (addr_family == AF_INET) {
 
-		g_clear_object (&priv->ext_ip4_config);
-		priv->ext_ip4_config = nm_ip4_config_capture (nm_device_get_multi_index (self),
+		g_clear_object (&priv->ext_ip_config_4);
+		priv->ext_ip_config_4 = nm_ip4_config_capture (nm_device_get_multi_index (self),
 		                                              nm_device_get_platform (self),
-		                                              ifindex,
-		                                              capture_resolv_conf);
-		if (priv->ext_ip4_config) {
-			if (initial) {
-				g_clear_object (&priv->dev_ip4_config);
-				capture_lease_config (self, priv->ext_ip4_config, &priv->dev_ip4_config, NULL, NULL);
-			}
-
+		                                              ifindex);
+		if (priv->ext_ip_config_4) {
 			if (intersect_configs) {
 				/* This function was called upon external changes. Remove the configuration
 				 * (addresses,routes) that is no longer present externally from the internal
 				 * config. This way, we don't re-add addresses that were manually removed
 				 * by the user. */
-				if (priv->con_ip4_config) {
-					nm_ip4_config_intersect (priv->con_ip4_config, priv->ext_ip4_config,
+				if (priv->con_ip_config_4) {
+					nm_ip4_config_intersect (priv->con_ip_config_4, priv->ext_ip_config_4,
 					                         default_route_metric_penalty_get (self, AF_INET));
 				}
-				if (priv->dev_ip4_config) {
-					nm_ip4_config_intersect (priv->dev_ip4_config, priv->ext_ip4_config,
-					                         default_route_metric_penalty_get (self, AF_INET));
-				}
-				if (priv->wwan_ip4_config) {
-					nm_ip4_config_intersect (priv->wwan_ip4_config, priv->ext_ip4_config,
-					                         default_route_metric_penalty_get (self, AF_INET));
-				}
-				for (iter = priv->vpn4_configs; iter; iter = iter->next)
-					nm_ip4_config_intersect (iter->data, priv->ext_ip4_config, 0);
+
+				intersect_ext_config (self, &priv->dev_ip4_config);
+				intersect_ext_config (self, &priv->wwan_ip_config_4);
+
+				for (iter = priv->vpn_configs_4; iter; iter = iter->next)
+					nm_ip4_config_intersect (iter->data, priv->ext_ip_config_4, 0);
 			}
 
-			/* Remove parts from ext_ip4_config to only contain the information that
+			/* Remove parts from ext_ip_config_4 to only contain the information that
 			 * was configured externally -- we already have the same configuration from
 			 * internal origins. */
-			if (priv->con_ip4_config) {
-				nm_ip4_config_subtract (priv->ext_ip4_config, priv->con_ip4_config,
+			if (priv->con_ip_config_4) {
+				nm_ip4_config_subtract (priv->ext_ip_config_4, priv->con_ip_config_4,
 				                        default_route_metric_penalty_get (self, AF_INET));
 			}
-			if (priv->dev_ip4_config) {
-				nm_ip4_config_subtract (priv->ext_ip4_config, priv->dev_ip4_config,
-				                        default_route_metric_penalty_get (self, AF_INET));
+			if (applied_config_get_current (&priv->dev_ip4_config)) {
+				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_4,
+				                       applied_config_get_current (&priv->dev_ip4_config),
+				                       default_route_metric_penalty_get (self, AF_INET));
 			}
-			if (priv->wwan_ip4_config) {
-				nm_ip4_config_subtract (priv->ext_ip4_config, priv->wwan_ip4_config,
-				                        default_route_metric_penalty_get (self, AF_INET));
+			if (applied_config_get_current (&priv->wwan_ip_config_4)) {
+				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_4,
+				                       applied_config_get_current (&priv->wwan_ip_config_4),
+				                       default_route_metric_penalty_get (self, AF_INET));
 			}
-			for (iter = priv->vpn4_configs; iter; iter = iter->next)
-				nm_ip4_config_subtract (priv->ext_ip4_config, iter->data, 0);
+			for (iter = priv->vpn_configs_4; iter; iter = iter->next)
+				nm_ip4_config_subtract (priv->ext_ip_config_4, iter->data, 0);
 		}
 
 	} else {
 		nm_assert (addr_family == AF_INET6);
 
-		g_clear_object (&priv->ext_ip6_config);
+		g_clear_object (&priv->ext_ip_config_6);
 		g_clear_object (&priv->ext_ip6_config_captured);
 		priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
 		                                                       nm_device_get_platform (self),
 		                                                       ifindex,
-		                                                       capture_resolv_conf,
 		                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 		if (priv->ext_ip6_config_captured) {
 
-			priv->ext_ip6_config = nm_ip6_config_new_cloned (priv->ext_ip6_config_captured);
+			priv->ext_ip_config_6 = nm_ip6_config_new_cloned (priv->ext_ip6_config_captured);
 
 			if (intersect_configs) {
 				/* This function was called upon external changes. Remove the configuration
 				 * (addresses,routes) that is no longer present externally from the internal
 				 * config. This way, we don't re-add addresses that were manually removed
 				 * by the user. */
-				if (priv->con_ip6_config) {
-					nm_ip6_config_intersect (priv->con_ip6_config, priv->ext_ip6_config,
+				if (priv->con_ip_config_6) {
+					nm_ip6_config_intersect (priv->con_ip_config_6, priv->ext_ip_config_6,
 					                         default_route_metric_penalty_get (self, AF_INET6));
 				}
-				if (priv->ac_ip6_config) {
-					nm_ip6_config_intersect (priv->ac_ip6_config, priv->ext_ip6_config,
-					                         default_route_metric_penalty_get (self, AF_INET6));
-				}
-				if (priv->dhcp6.ip6_config) {
-					nm_ip6_config_intersect (priv->dhcp6.ip6_config, priv->ext_ip6_config,
-					                         default_route_metric_penalty_get (self, AF_INET6));
-				}
-				if (priv->wwan_ip6_config) {
-					nm_ip6_config_intersect (priv->wwan_ip6_config, priv->ext_ip6_config,
-					                         default_route_metric_penalty_get (self, AF_INET6));
-				}
-				for (iter = priv->vpn6_configs; iter; iter = iter->next)
-					nm_ip6_config_intersect (iter->data, priv->ext_ip6_config, 0);
+
+				intersect_ext_config (self, &priv->ac_ip6_config);
+				intersect_ext_config (self, &priv->dhcp6.ip6_config);
+				intersect_ext_config (self, &priv->wwan_ip_config_6);
+
+				for (iter = priv->vpn_configs_6; iter; iter = iter->next)
+					nm_ip6_config_intersect (iter->data, priv->ext_ip_config_6, 0);
+
+				if (   priv->ipv6ll_has
+				    && !nm_ip6_config_lookup_address (priv->ext_ip_config_6, &priv->ipv6ll_addr))
+					priv->ipv6ll_has = FALSE;
 			}
 
-			/* Remove parts from ext_ip6_config to only contain the information that
+			/* Remove parts from ext_ip_config_6 to only contain the information that
 			 * was configured externally -- we already have the same configuration from
 			 * internal origins. */
-			if (priv->con_ip6_config) {
-				nm_ip6_config_subtract (priv->ext_ip6_config, priv->con_ip6_config,
+			if (priv->con_ip_config_6) {
+				nm_ip6_config_subtract (priv->ext_ip_config_6, priv->con_ip_config_6,
 				                        default_route_metric_penalty_get (self, AF_INET6));
 			}
-			if (priv->ac_ip6_config) {
-				nm_ip6_config_subtract (priv->ext_ip6_config, priv->ac_ip6_config,
-				                        default_route_metric_penalty_get (self, AF_INET6));
+			if (applied_config_get_current (&priv->ac_ip6_config)) {
+				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_6,
+				                       applied_config_get_current (&priv->ac_ip6_config),
+				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
-			if (priv->dhcp6.ip6_config) {
-				nm_ip6_config_subtract (priv->ext_ip6_config, priv->dhcp6.ip6_config,
-				                        default_route_metric_penalty_get (self, AF_INET6));
+			if (applied_config_get_current (&priv->dhcp6.ip6_config)) {
+				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_6,
+				                       applied_config_get_current (&priv->dhcp6.ip6_config),
+				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
-			if (priv->wwan_ip6_config) {
-				nm_ip6_config_subtract (priv->ext_ip6_config, priv->wwan_ip6_config,
-				                        default_route_metric_penalty_get (self, AF_INET6));
+			if (applied_config_get_current (&priv->wwan_ip_config_6)) {
+				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_6,
+				                       applied_config_get_current (&priv->wwan_ip_config_6),
+				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
-			for (iter = priv->vpn6_configs; iter; iter = iter->next)
-				nm_ip6_config_subtract (priv->ext_ip6_config, iter->data, 0);
+			for (iter = priv->vpn_configs_6; iter; iter = iter->next)
+				nm_ip6_config_subtract (priv->ext_ip_config_6, iter->data, 0);
 		}
 	}
 
@@ -11265,156 +11783,139 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean initial, gboolea
 }
 
 static void
-update_ip_config (NMDevice *self, int addr_family, gboolean initial)
+update_ip_config (NMDevice *self, int addr_family)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	nm_assert_addr_family (addr_family);
 
-	if (update_ext_ip_config (self, addr_family, initial, TRUE)) {
+	if (addr_family == AF_INET)
+		priv->update_ip_config_completed_v4 = TRUE;
+	else
+		priv->update_ip_config_completed_v6 = TRUE;
+
+	if (update_ext_ip_config (self, addr_family, TRUE)) {
 		if (addr_family == AF_INET) {
-			if (priv->ext_ip4_config)
-				ip4_config_merge_and_apply (self, FALSE);
+			if (priv->ext_ip_config_4)
+				ip_config_merge_and_apply (self, AF_INET, FALSE);
 		} else {
 			if (priv->ext_ip6_config_captured)
-				ip6_config_merge_and_apply (self, FALSE);
+				ip_config_merge_and_apply (self, AF_INET6, FALSE);
 		}
 	}
-
-	if (   addr_family == AF_INET6
-	    && priv->linklocal6_timeout_id
-	    && priv->ext_ip6_config_captured
-	    && nm_ip6_config_get_address_first_nontentative (priv->ext_ip6_config_captured, TRUE)) {
-		/* linklocal6 is ready now, do the state transition... we are also
-		 * invoked as g_idle_add, so no problems with reentrance doing it now.
-		 */
-		linklocal6_complete (self);
-	}
 }
 
 void
 nm_device_capture_initial_config (NMDevice *self)
 {
-	update_ip_config (self, AF_INET,  TRUE);
-	update_ip_config (self, AF_INET6, TRUE);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!priv->update_ip_config_completed_v4)
+		update_ip_config (self, AF_INET);
+	if (!priv->update_ip_config_completed_v6)
+		update_ip_config (self, AF_INET6);
 }
 
 static gboolean
-queued_ip4_config_change (gpointer user_data)
+queued_ip_config_change (NMDevice *self, int addr_family)
 {
-	NMDevice *self = user_data;
 	NMDevicePrivate *priv;
+	gboolean need_ipv6ll = FALSE;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+	NMPlatform *platform;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), G_SOURCE_REMOVE);
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	nm_assert (!priv->queued_ip4_config_pending);
+	nm_assert (IS_IPv4 ? !priv->queued_ip4_config_pending : !priv->queued_ip6_config_pending);
 
 	/* Wait for any queued state changes */
 	if (priv->queued_state.id)
-		return TRUE;
+		return G_SOURCE_CONTINUE;
 
-	priv->queued_ip4_config_id = 0;
+	priv->queued_ip_config_id_x[IS_IPv4] = 0;
 
 	/* If a commit is scheduled, this function would potentially interfere with
 	 * it changing IP configurations before they are applied. Postpone the
 	 * update in such case.
 	 */
 	if (activation_source_is_scheduled (self,
-	                                    activate_stage5_ip4_config_result,
-	                                    AF_INET)) {
-		priv->queued_ip4_config_pending = FALSE;
-		priv->queued_ip4_config_id = g_idle_add (queued_ip4_config_change, self);
-		_LOGT (LOGD_DEVICE, "IP4 update was postponed");
-	} else
-		update_ip_config (self, AF_INET, FALSE);
-
-	set_unmanaged_external_down (self, TRUE);
+	                                    IS_IPv4
+	                                      ? activate_stage5_ip4_config_result
+	                                      : activate_stage5_ip6_config_commit,
+	                                    addr_family)) {
+		if (IS_IPv4) {
+			priv->queued_ip4_config_pending = FALSE;
+			priv->queued_ip_config_id_4 = g_idle_add (queued_ip4_config_change, self);
+		} else {
+			priv->queued_ip6_config_pending = FALSE;
+			priv->queued_ip_config_id_6 = g_idle_add (queued_ip6_config_change, self);
+		}
+		_LOGT (LOGD_DEVICE, "IP%c update was postponed",
+		       nm_utils_addr_family_to_char (addr_family));
+	} else {
+		update_ip_config (self, addr_family);
 
-	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
-		priv->v4_has_shadowed_routes = _v4_has_shadowed_routes_detect (self);;
-		ip4_rp_filter_update (self);
+		if (!IS_IPv4) {
+			/* Check whether we need to complete waiting for link-local.
+			 * We are also called from an idle handler, so no problem doing state transitions
+			 * now. */
+			linklocal6_check_complete (self);
+		}
 	}
 
-	return FALSE;
-}
-
-static gboolean
-queued_ip6_config_change (gpointer user_data)
-{
-	NMDevice *self = user_data;
-	NMDevicePrivate *priv;
-	GSList *iter;
-	gboolean need_ipv6ll = FALSE;
-
-	g_return_val_if_fail (NM_IS_DEVICE (self), G_SOURCE_REMOVE);
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-
-	nm_assert (!priv->queued_ip4_config_pending);
+	if (!IS_IPv4) {
+		if (   priv->state < NM_DEVICE_STATE_DEACTIVATING
+		    && (platform = nm_device_get_platform (self))
+		    && nm_platform_link_get (platform, priv->ifindex)) {
+			/* Handle DAD failures */
+			while (priv->dad6_failed_addrs) {
+				nm_auto_nmpobj const NMPObject *obj = NULL;
+				const NMPlatformIP6Address *addr;
 
-	/* Wait for any queued state changes */
-	if (priv->queued_state.id)
-		return TRUE;
+				obj = priv->dad6_failed_addrs->data;
+				priv->dad6_failed_addrs = g_slist_delete_link (priv->dad6_failed_addrs, priv->dad6_failed_addrs);
 
-	priv->queued_ip6_config_id = 0;
-
-	/* If a commit is scheduled, this function would potentially interfere with
-	 * it changing IP configurations before they are applied. Postpone the
-	 * update in such case.
-	 */
-	if (activation_source_is_scheduled (self,
-	                                    activate_stage5_ip6_config_commit,
-	                                    AF_INET6)) {
-		priv->queued_ip6_config_pending = FALSE;
-		priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self);
-		_LOGT (LOGD_DEVICE, "IP6 update was postponed");
-	} else
-		update_ip_config (self, AF_INET6, FALSE);
+				if (!nm_ndisc_dad_addr_is_fail_candidate (platform, obj))
+					continue;
 
-	if (priv->state < NM_DEVICE_STATE_DEACTIVATING
-	    && nm_platform_link_get (nm_device_get_platform (self), priv->ifindex)) {
-		/* Handle DAD failures */
-		for (iter = priv->dad6_failed_addrs; iter; iter = g_slist_next (iter)) {
-			NMPlatformIP6Address *addr = iter->data;
+				addr = NMP_OBJECT_CAST_IP6_ADDRESS (obj);
 
-			if (addr->addr_source >= NM_IP_CONFIG_SOURCE_USER)
-				continue;
+				_LOGI (LOGD_IP6, "ipv6: duplicate address check failed for the %s address",
+				       nm_platform_ip6_address_to_string (addr, NULL, 0));
 
-			_LOGI (LOGD_IP6, "ipv6: duplicate address check failed for the %s address",
-			       nm_platform_ip6_address_to_string (addr, NULL, 0));
+				if (IN6_IS_ADDR_LINKLOCAL (&addr->address))
+					need_ipv6ll = TRUE;
+				else if (priv->ndisc)
+					nm_ndisc_dad_failed (priv->ndisc, &addr->address);
+			}
 
-			if (IN6_IS_ADDR_LINKLOCAL (&addr->address))
+			/* If no IPv6 link-local address exists but other addresses do then we
+			 * must add the LL address to remain conformant with RFC 3513 chapter 2.1
+			 * ("Addressing Model"): "All interfaces are required to have at least
+			 * one link-local unicast address".
+			 */
+			if (   priv->ip_config_6
+			    && nm_ip6_config_get_num_addresses (priv->ip_config_6))
 				need_ipv6ll = TRUE;
-			else if (priv->ndisc)
-				nm_ndisc_dad_failed (priv->ndisc, &addr->address);
-		}
-
-		/* If no IPv6 link-local address exists but other addresses do then we
-		 * must add the LL address to remain conformant with RFC 3513 chapter 2.1
-		 * ("Addressing Model"): "All interfaces are required to have at least
-		 * one link-local unicast address".
-		 */
-		if (priv->ip6_config && nm_ip6_config_get_num_addresses (priv->ip6_config))
-			need_ipv6ll = TRUE;
-
-		if (need_ipv6ll)
-			check_and_add_ipv6ll_addr (self);
-	}
 
-	g_slist_free_full (priv->dad6_failed_addrs, g_free);
-	priv->dad6_failed_addrs = NULL;
+			if (need_ipv6ll)
+				check_and_add_ipv6ll_addr (self);
+		} else {
+			g_slist_free_full (priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
+			priv->dad6_failed_addrs = NULL;
+		}
 
-	/* Check if DAD is still pending */
-	if (   priv->ip6_state == IP_CONF
-	    && priv->dad6_ip6_config
-	    && priv->ext_ip6_config_captured) {
-		if (!nm_ip6_config_has_any_dad_pending (priv->ext_ip6_config_captured,
-		                                        priv->dad6_ip6_config)) {
+		/* Check if DAD is still pending */
+		if (   priv->ip6_state == IP_CONF
+		    && priv->dad6_ip6_config
+		    && priv->ext_ip6_config_captured
+		    && !nm_ip6_config_has_any_dad_pending (priv->ext_ip6_config_captured,
+		                                           priv->dad6_ip6_config)) {
 			_LOGD (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD terminated");
 			g_clear_object (&priv->dad6_ip6_config);
-			_set_ip_state (self, AF_INET6, IP_DONE);
+			_set_ip_state (self, addr_family, IP_DONE);
 			check_ip_state (self, FALSE, TRUE);
 			if (priv->rt6_temporary_not_available)
 				nm_device_activate_schedule_ip6_config_result (self);
@@ -11423,21 +11924,40 @@ queued_ip6_config_change (gpointer user_data)
 
 	set_unmanaged_external_down (self, TRUE);
 
-	return FALSE;
+	if (IS_IPv4) {
+		if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
+			priv->v4_has_shadowed_routes = _v4_has_shadowed_routes_detect (self);;
+			ip4_rp_filter_update (self);
+		}
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+queued_ip4_config_change (gpointer user_data)
+{
+	return queued_ip_config_change (user_data, AF_INET);
+}
+
+static gboolean
+queued_ip6_config_change (gpointer user_data)
+{
+	return queued_ip_config_change (user_data, AF_INET6);
 }
 
 static void
 device_ipx_changed (NMPlatform *platform,
                     int obj_type_i,
                     int ifindex,
-                    gpointer platform_object,
+                    gconstpointer platform_object,
                     int change_type_i,
                     NMDevice *self)
 {
 	const NMPObjectType obj_type = obj_type_i;
 	const NMPlatformSignalChangeType change_type = change_type_i;
 	NMDevicePrivate *priv;
-	NMPlatformIP6Address *addr;
+	const NMPlatformIP6Address *addr;
 
 	if (nm_device_get_ip_ifindex (self) != ifindex)
 		return;
@@ -11449,10 +11969,10 @@ device_ipx_changed (NMPlatform *platform,
 	case NMP_OBJECT_TYPE_IP4_ROUTE:
 		if (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
 			priv->queued_ip4_config_pending = TRUE;
-			nm_assert_se (!nm_clear_g_source (&priv->queued_ip4_config_id));
-		} else if (!priv->queued_ip4_config_id) {
+			nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_4));
+		} else if (!priv->queued_ip_config_id_4) {
 			priv->queued_ip4_config_pending = FALSE;
-			priv->queued_ip4_config_id = g_idle_add (queued_ip4_config_change, self);
+			priv->queued_ip_config_id_4 = g_idle_add (queued_ip4_config_change, self);
 			_LOGD (LOGD_DEVICE, "queued IP4 config change");
 		}
 		break;
@@ -11461,19 +11981,18 @@ device_ipx_changed (NMPlatform *platform,
 
 		if (   priv->state > NM_DEVICE_STATE_DISCONNECTED
 		    && priv->state < NM_DEVICE_STATE_DEACTIVATING
-		    && (   (change_type == NM_PLATFORM_SIGNAL_CHANGED && addr->n_ifa_flags & IFA_F_DADFAILED)
-		        || (change_type == NM_PLATFORM_SIGNAL_REMOVED && addr->n_ifa_flags & IFA_F_TENTATIVE))) {
-			priv->dad6_failed_addrs = g_slist_append (priv->dad6_failed_addrs,
-			                                          g_memdup (addr, sizeof (NMPlatformIP6Address)));
+		    && nm_ndisc_dad_addr_is_fail_candidate_event (change_type, addr)) {
+			priv->dad6_failed_addrs = g_slist_prepend (priv->dad6_failed_addrs,
+			                                           (gpointer) nmp_object_ref (NMP_OBJECT_UP_CAST (addr)));
 		}
 		/* fall through */
 	case NMP_OBJECT_TYPE_IP6_ROUTE:
 		if (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
 			priv->queued_ip6_config_pending = TRUE;
-			nm_assert_se (!nm_clear_g_source (&priv->queued_ip6_config_id));
-		} else if (!priv->queued_ip6_config_id) {
+			nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_6));
+		} else if (!priv->queued_ip_config_id_6) {
 			priv->queued_ip6_config_pending = FALSE;
-			priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self);
+			priv->queued_ip_config_id_6 = g_idle_add (queued_ip6_config_change, self);
 			_LOGD (LOGD_DEVICE, "queued IP6 config change");
 		}
 		break;
@@ -11741,14 +12260,14 @@ _set_unmanaged_flags (NMDevice *self,
 
 		if (priv->queued_ip4_config_pending) {
 			priv->queued_ip4_config_pending = FALSE;
-			nm_assert_se (!nm_clear_g_source (&priv->queued_ip4_config_id));
-			priv->queued_ip4_config_id = g_idle_add (queued_ip4_config_change, self);
+			nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_4));
+			priv->queued_ip_config_id_4 = g_idle_add (queued_ip4_config_change, self);
 		}
 
 		if (priv->queued_ip6_config_pending) {
 			priv->queued_ip6_config_pending = FALSE;
-			nm_assert_se (!nm_clear_g_source (&priv->queued_ip6_config_id));
-			priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self);
+			nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_6));
+			priv->queued_ip_config_id_6 = g_idle_add (queued_ip6_config_change, self);
 		}
 
 		if (!priv->pending_actions) {
@@ -12014,7 +12533,7 @@ nm_device_reapply_settings_immediately (NMDevice *self)
 	if (g_strcmp0 ((zone = nm_setting_connection_get_zone (s_con_settings)),
 	               nm_setting_connection_get_zone (s_con_applied)) != 0) {
 
-		version_id = nm_active_connection_version_id_bump ((NMActiveConnection *) self->_priv->act_request);
+		version_id = nm_active_connection_version_id_bump ((NMActiveConnection *) self->_priv->act_request.obj);
 		_LOGD (LOGD_DEVICE, "reapply setting: zone = %s%s%s (version-id %llu)", NM_PRINT_FMT_QUOTE_STRING (zone), (unsigned long long) version_id);
 
 		g_object_set (G_OBJECT (s_con_applied),
@@ -12026,7 +12545,7 @@ nm_device_reapply_settings_immediately (NMDevice *self)
 
 	if ((metered = nm_setting_connection_get_metered (s_con_settings)) != nm_setting_connection_get_metered (s_con_applied)) {
 
-		version_id = nm_active_connection_version_id_bump ((NMActiveConnection *) self->_priv->act_request);
+		version_id = nm_active_connection_version_id_bump ((NMActiveConnection *) self->_priv->act_request.obj);
 		_LOGD (LOGD_DEVICE, "reapply setting: metered = %d (version-id %llu)", (int) metered, (unsigned long long) version_id);
 
 		g_object_set (G_OBJECT (s_con_applied),
@@ -12082,13 +12601,27 @@ nm_device_update_metered (NMDevice *self)
 
 	/* Try to guess a value using the metered flag in IP configuration */
 	if (value == NM_METERED_INVALID) {
-		if (   priv->ip4_config
+		if (   priv->ip_config_4
 		    && priv->ip4_state == IP_DONE
-		    && nm_ip4_config_get_metered (priv->ip4_config))
+		    && nm_ip4_config_get_metered (priv->ip_config_4))
+			value = NM_METERED_GUESS_YES;
+	}
+
+	/* Otherwise look at connection type. For Bluetooth, we look at the type of
+	 * Bluetooth sharing: for PANU/DUN (where we are receiving internet from
+	 * another device) we set GUESS_YES; for NAP (where we are sharing internet
+	 * to another device) we set GUESS_NO. We ignore WiMAX here as it’s no
+	 * longer supported by NetworkManager. */
+	if (   value == NM_METERED_INVALID
+	    && nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME)) {
+
+		if (_nm_connection_get_setting_bluetooth_for_nap (connection)) {
+			/* NAP types are not metered, but other types are. */
+			value = NM_METERED_GUESS_NO;
+		} else
 			value = NM_METERED_GUESS_YES;
 	}
 
-	/* Otherwise look at connection type */
 	if (value == NM_METERED_INVALID) {
 		if (   nm_connection_is_type (connection, NM_SETTING_GSM_SETTING_NAME)
 		    || nm_connection_is_type (connection, NM_SETTING_CDMA_SETTING_NAME))
@@ -12179,7 +12712,7 @@ nm_device_check_connection_available (NMDevice *self,
 
 		for (i = 0; i <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; i++) {
 			for (j = 1; j <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; j <<= 1) {
-				if (NM_FLAGS_HAS (i, j)) {
+				if (NM_FLAGS_ANY (i, j)) {
 					k = i & ~j;
 					nm_assert (   available_all[i] == available_all[k]
 					           || available_all[i]);
@@ -12204,7 +12737,7 @@ available_connections_del_all (NMDevice *self)
 static gboolean
 available_connections_add (NMDevice *self, NMConnection *connection)
 {
-	return nm_g_hash_table_add (self->_priv->available_connections, g_object_ref (connection));
+	return g_hash_table_add (self->_priv->available_connections, g_object_ref (connection));
 }
 
 static gboolean
@@ -12262,7 +12795,7 @@ nm_device_recheck_available_connections (NMDevice *self)
 	priv = NM_DEVICE_GET_PRIVATE(self);
 
 	if (g_hash_table_size (priv->available_connections) > 0) {
-		prune_list = g_hash_table_new (g_direct_hash, g_direct_equal);
+		prune_list = g_hash_table_new (nm_direct_hash, NULL);
 		g_hash_table_iter_init (&h_iter, priv->available_connections);
 		while (g_hash_table_iter_next (&h_iter, (gpointer *) &connection, NULL))
 			g_hash_table_add (prune_list, connection);
@@ -12371,19 +12904,19 @@ cp_connection_added_or_updated (NMDevice *self, NMConnection *connection)
 }
 
 static void
-cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+cp_connection_added (NMSettings *settings, NMConnection *connection, gpointer user_data)
 {
 	cp_connection_added_or_updated (user_data, connection);
 }
 
 static void
-cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gboolean by_user, gpointer user_data)
+cp_connection_updated (NMSettings *settings, NMConnection *connection, gboolean by_user, gpointer user_data)
 {
 	cp_connection_added_or_updated (user_data, connection);
 }
 
 static void
-cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+cp_connection_removed (NMSettings *settings, NMConnection *connection, gpointer user_data)
 {
 	NMDevice *self = user_data;
 
@@ -12565,8 +13098,8 @@ _cleanup_generic_pre (NMDevice *self, CleanupType cleanup_type)
 
 	queued_state_clear (self);
 
-	_cleanup_ip4_pre (self, cleanup_type);
-	_cleanup_ip6_pre (self, cleanup_type);
+	_cleanup_ip_pre (self, AF_INET, cleanup_type);
+	_cleanup_ip_pre (self, AF_INET6, cleanup_type);
 }
 
 static void
@@ -12588,39 +13121,41 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	/* Clean up IP configs; this does not actually deconfigure the
 	 * interface; the caller must flush routes and addresses explicitly.
 	 */
-	nm_device_set_ip4_config (self, NULL, TRUE, NULL);
-	nm_device_set_ip6_config (self, NULL, TRUE);
+	nm_device_set_ip_config (self, AF_INET, NULL, TRUE, NULL);
+	nm_device_set_ip_config (self, AF_INET6, NULL, TRUE, NULL);
 	g_clear_object (&priv->proxy_config);
-	g_clear_object (&priv->con_ip4_config);
-	g_clear_object (&priv->dev_ip4_config);
-	g_clear_object (&priv->ext_ip4_config);
-	g_clear_object (&priv->wwan_ip4_config);
-	g_clear_object (&priv->ip4_config);
-	g_clear_object (&priv->con_ip6_config);
-	g_clear_object (&priv->ac_ip6_config);
-	g_clear_object (&priv->ext_ip6_config);
+	g_clear_object (&priv->con_ip_config_4);
+	applied_config_clear (&priv->dev_ip4_config);
+	applied_config_clear (&priv->wwan_ip_config_4);
+	g_clear_object (&priv->ext_ip_config_4);
+	g_clear_object (&priv->ip_config_4);
+	g_clear_object (&priv->con_ip_config_6);
+	applied_config_clear (&priv->ac_ip6_config);
+	g_clear_object (&priv->ext_ip_config_6);
 	g_clear_object (&priv->ext_ip6_config_captured);
-	g_clear_object (&priv->wwan_ip6_config);
-	g_clear_object (&priv->ip6_config);
+	applied_config_clear (&priv->wwan_ip_config_6);
+	g_clear_object (&priv->ip_config_6);
 	g_clear_object (&priv->dad6_ip6_config);
+	priv->ipv6ll_has = FALSE;
+	memset (&priv->ipv6ll_addr, 0, sizeof (priv->ipv6ll_addr));
 
 	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
 	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
 
-	g_slist_free_full (priv->vpn4_configs, g_object_unref);
-	priv->vpn4_configs = NULL;
-	g_slist_free_full (priv->vpn6_configs, g_object_unref);
-	priv->vpn6_configs = NULL;
+	g_slist_free_full (priv->vpn_configs_4, g_object_unref);
+	priv->vpn_configs_4 = NULL;
+	g_slist_free_full (priv->vpn_configs_6, g_object_unref);
+	priv->vpn_configs_6 = NULL;
 
-	/* We no longer accept the delegations. nm_device_set_ip6_config(NULL)
+	/* We no longer accept the delegations. nm_device_set_ip_config(NULL)
 	 * above disables them. */
 	nm_assert (priv->needs_ip6_subnet == FALSE);
 
-	if (priv->act_request) {
-		nm_active_connection_set_default (NM_ACTIVE_CONNECTION (priv->act_request), AF_INET, FALSE);
+	if (priv->act_request.obj) {
+		nm_active_connection_set_default (NM_ACTIVE_CONNECTION (priv->act_request.obj), AF_INET, FALSE);
 
 		priv->master_ready_handled = FALSE;
-		nm_clear_g_signal_handler (priv->act_request, &priv->master_ready_id);
+		nm_clear_g_signal_handler (priv->act_request.obj, &priv->master_ready_id);
 
 		act_request_set (self, NULL);
 	}
@@ -12642,7 +13177,7 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	 * those are identified by ip_iface, not by iface (which might be a tty
 	 * or ATM device).
 	 */
-	nm_device_set_ip_iface (self, NULL);
+	_set_ip_ifindex (self, 0, NULL);
 }
 
 /*
@@ -12762,10 +13297,10 @@ find_dhcp4_address (NMDevice *self)
 	const NMPlatformIP4Address *a;
 	NMDedupMultiIter ipconf_iter;
 
-	if (!priv->ip4_config)
+	if (!priv->ip_config_4)
 		return NULL;
 
-	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, priv->ip4_config, &a) {
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, priv->ip_config_4, &a) {
 		if (a->addr_source == NM_IP_CONFIG_SOURCE_DHCP)
 			return g_strdup (nm_utils_inet4_ntop (a->address, NULL));
 	}
@@ -13072,7 +13607,7 @@ _set_state_full (NMDevice *self,
 		g_cancellable_cancel (priv->deactivating_cancellable);
 
 	/* Cache the activation request for the dispatcher */
-	req = nm_g_object_ref (priv->act_request);
+	req = nm_g_object_ref (priv->act_request.obj);
 
 	if (   state >  NM_DEVICE_STATE_UNMANAGED
 	    && state <= NM_DEVICE_STATE_ACTIVATED
@@ -13180,8 +13715,8 @@ _set_state_full (NMDevice *self,
 			/* Clean up any half-done IP operations if the device's layer2
 			 * finds out it needs authentication during IP config.
 			 */
-			_cleanup_ip4_pre (self, CLEANUP_TYPE_DECONFIGURE);
-			_cleanup_ip6_pre (self, CLEANUP_TYPE_DECONFIGURE);
+			_cleanup_ip_pre (self, AF_INET, CLEANUP_TYPE_DECONFIGURE);
+			_cleanup_ip_pre (self, AF_INET6, CLEANUP_TYPE_DECONFIGURE);
 		}
 		break;
 	default:
@@ -13195,6 +13730,13 @@ _set_state_full (NMDevice *self,
 
 	_notify (self, PROP_STATE);
 	_notify (self, PROP_STATE_REASON);
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
+	                            &interface_info_device,
+	                            &signal_info_state_changed,
+	                            "(uuu)",
+	                            (guint32) state,
+	                            (guint32) old_state,
+	                            (guint32) reason);
 	g_signal_emit (self, signals[STATE_CHANGED], 0, (guint) state, (guint) old_state, (guint) reason);
 
 	/* Post-process the event after internal notification */
@@ -13345,7 +13887,7 @@ _set_state_full (NMDevice *self,
 	if (ip_config_valid (old_state) && !ip_config_valid (state))
 	    notify_ip_properties (self);
 
-	concheck_periodic_update (self);
+	nm_device_check_connectivity_update_interval (self);
 
 	/* Dispose of the cached activation request */
 	if (req)
@@ -14307,8 +14849,15 @@ nm_device_init (NMDevice *self)
 
 	self->_priv = priv;
 
+	c_list_init (&priv->concheck_lst_head);
+	c_list_init (&self->devices_lst);
 	c_list_init (&priv->slaves);
 
+	priv->connectivity_state = NM_CONNECTIVITY_UNKNOWN;
+
+	nm_dbus_track_obj_path_init (&priv->parent_device, G_OBJECT (self), obj_properties[PROP_PARENT]);
+	nm_dbus_track_obj_path_init (&priv->act_request, G_OBJECT (self), obj_properties[PROP_ACTIVE_CONNECTION]);
+
 	priv->netns = g_object_ref (NM_NETNS_GET);
 
 	priv->autoconnect_blocked_flags = DEFAULT_AUTOCONNECT
@@ -14323,7 +14872,7 @@ nm_device_init (NMDevice *self)
 	priv->rfkill_type = RFKILL_TYPE_UNKNOWN;
 	priv->unmanaged_flags = NM_UNMANAGED_PLATFORM_INIT;
 	priv->unmanaged_mask = priv->unmanaged_flags;
-	priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
+	priv->available_connections = g_hash_table_new_full (nm_direct_hash, NULL, g_object_unref, NULL);
 	priv->ip6_saved_properties = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
 	priv->sys_iface_state = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL;
 
@@ -14422,9 +14971,19 @@ dispose (GObject *object)
 	NMDevice *self = NM_DEVICE (object);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMPlatform *platform;
+	NMDeviceConnectivityHandle *con_handle;
+	gs_free_error GError *cancelled_error = NULL;
 
 	_LOGD (LOGD_DEVICE, "disposing");
 
+	nm_assert (c_list_is_empty (&self->devices_lst));
+
+	while ((con_handle = c_list_first_entry (&priv->concheck_lst_head, NMDeviceConnectivityHandle, concheck_lst))) {
+		if (!cancelled_error)
+			nm_utils_error_set_cancelled (&cancelled_error, FALSE, "NMDevice");
+		concheck_handle_complete (con_handle, cancelled_error);
+	}
+
 	nm_clear_g_cancellable (&priv->deactivating_cancellable);
 
 	nm_device_assume_state_reset (self);
@@ -14435,8 +14994,8 @@ dispose (GObject *object)
 	g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (device_ipx_changed), self);
 	g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (link_changed_cb), self);
 
-	g_slist_free_full (priv->arping.dad_list, (GDestroyNotify) nm_arping_manager_destroy);
-	priv->arping.dad_list = NULL;
+	g_slist_free_full (priv->acd.dad_list, (GDestroyNotify) nm_acd_manager_destroy);
+	priv->acd.dad_list = NULL;
 
 	arp_cleanup (self);
 
@@ -14498,6 +15057,8 @@ dispose (GObject *object)
 		g_clear_object (&priv->lldp_listener);
 	}
 
+	nm_clear_g_source (&priv->concheck_p_cur_id);
+
 	G_OBJECT_CLASS (nm_device_parent_class)->dispose (object);
 
 	if (nm_clear_g_source (&priv->queued_state.id)) {
@@ -14520,7 +15081,7 @@ finalize (GObject *object)
 	g_free (priv->hw_addr_perm);
 	g_free (priv->hw_addr_initial);
 	g_slist_free (priv->pending_actions);
-	g_slist_free_full (priv->dad6_failed_addrs, g_free);
+	g_slist_free_full (priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
 	g_clear_pointer (&priv->physical_port_id, g_free);
 	g_free (priv->udi);
 	g_free (priv->iface);
@@ -14529,19 +15090,22 @@ finalize (GObject *object)
 	g_free (priv->driver_version);
 	g_free (priv->firmware_version);
 	g_free (priv->type_desc);
-	g_free (priv->type_description);
 	g_free (priv->dhcp_anycast_address);
 	g_free (priv->current_stable_id);
 
 	g_hash_table_unref (priv->ip6_saved_properties);
 	g_hash_table_unref (priv->available_connections);
 
+	nm_dbus_track_obj_path_deinit (&priv->parent_device);
+	nm_dbus_track_obj_path_deinit (&priv->act_request);
+
 	G_OBJECT_CLASS (nm_device_parent_class)->finalize (object);
 
 	/* for testing, NMDeviceTest does not invoke NMDevice::constructed,
 	 * and thus @settings might be unset. */
-	if (priv->settings)
-		g_object_unref (priv->settings);
+	nm_g_object_unref (priv->settings);
+
+	nm_g_object_unref (priv->concheck_mgr);
 
 	g_object_unref (priv->netns);
 }
@@ -14651,9 +15215,6 @@ get_property (GObject *object, guint prop_id,
 {
 	NMDevice *self = NM_DEVICE (object);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	GPtrArray *array;
-	GHashTableIter iter;
-	NMConnection *connection;
 	GVariantBuilder array_builder;
 
 	switch (prop_id) {
@@ -14709,16 +15270,16 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, priv->mtu);
 		break;
 	case PROP_IP4_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip4_config : NULL);
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip_config_4 : NULL);
 		break;
 	case PROP_DHCP4_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp4.config : NULL);
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp4.config : NULL);
 		break;
 	case PROP_IP6_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip6_config : NULL);
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip_config_6 : NULL);
 		break;
 	case PROP_DHCP6_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp6.config : NULL);
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp6.config : NULL);
 		break;
 	case PROP_STATE:
 		g_value_set_uint (value, priv->state);
@@ -14728,7 +15289,7 @@ get_property (GObject *object, guint prop_id,
 		                      g_variant_new ("(uu)", priv->state, priv->state_reason));
 		break;
 	case PROP_ACTIVE_CONNECTION:
-		nm_utils_g_value_set_object_path (value, priv->act_request_public ? priv->act_request : NULL);
+		g_value_set_string (value, nm_dbus_track_obj_path_get (&priv->act_request));
 		break;
 	case PROP_DEVICE_TYPE:
 		g_value_set_uint (value, priv->type);
@@ -14759,12 +15320,9 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, priv->rfkill_type);
 		break;
 	case PROP_AVAILABLE_CONNECTIONS:
-		array = g_ptr_array_sized_new (g_hash_table_size (priv->available_connections));
-		g_hash_table_iter_init (&iter, priv->available_connections);
-		while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL))
-			g_ptr_array_add (array, g_strdup (nm_connection_get_path (connection)));
-		g_ptr_array_add (array, NULL);
-		g_value_take_boxed (value, (char **) g_ptr_array_free (array, FALSE));
+		nm_dbus_utils_g_value_set_object_path_from_hash (value,
+		                                                 priv->available_connections,
+		                                                 TRUE);
 		break;
 	case PROP_PHYSICAL_PORT_ID:
 		g_value_set_string (value, priv->physical_port_id);
@@ -14773,7 +15331,7 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_object (value, nm_device_get_master (self));
 		break;
 	case PROP_PARENT:
-		nm_utils_g_value_set_object_path (value, priv->parent_device);
+		g_value_set_string (value, nm_dbus_track_obj_path_get (&priv->parent_device));
 		break;
 	case PROP_HW_ADDRESS:
 		g_value_set_string (value, priv->hw_addr);
@@ -14818,7 +15376,7 @@ get_property (GObject *object, guint prop_id,
 
 			if (!NM_DEVICE_GET_PRIVATE (info->slave)->is_enslaved)
 				continue;
-			path = nm_exported_object_get_path ((NMExportedObject *) info->slave);
+			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (info->slave));
 			if (path)
 				slave_list[i++] = g_strdup (path);
 		}
@@ -14845,15 +15403,115 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
+static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"StateChanged",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("new_state", "u"),
+		NM_DEFINE_GDBUS_ARG_INFO ("old_state", "u"),
+		NM_DEFINE_GDBUS_ARG_INFO ("reason",    "u"),
+	),
+);
+
+static const NMDBusInterfaceInfoExtended interface_info_device = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Reapply",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connection", "a{sa{sv}}"),
+						NM_DEFINE_GDBUS_ARG_INFO ("version_id", "t"),
+						NM_DEFINE_GDBUS_ARG_INFO ("flags",      "u"),
+					),
+				),
+				.handle = impl_device_reapply,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetAppliedConnection",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("flags", "u"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connection", "a{sa{sv}}"),
+						NM_DEFINE_GDBUS_ARG_INFO ("version_id", "t"),
+					),
+				),
+				.handle = impl_device_get_applied_connection,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Disconnect",
+				),
+				.handle = impl_device_disconnect,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Delete",
+				),
+				.handle = impl_device_delete,
+			),
+		),
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&signal_info_state_changed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Udi",                  "s",      NM_DEVICE_UDI),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Interface",            "s",      NM_DEVICE_IFACE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("IpInterface",          "s",      NM_DEVICE_IP_IFACE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Driver",               "s",      NM_DEVICE_DRIVER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("DriverVersion",        "s",      NM_DEVICE_DRIVER_VERSION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("FirmwareVersion",      "s",      NM_DEVICE_FIRMWARE_VERSION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Capabilities",         "u",      NM_DEVICE_CAPABILITIES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Ip4Address",           "u",      NM_DEVICE_IP4_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("State",                "u",      NM_DEVICE_STATE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("StateReason",          "(uu)",   NM_DEVICE_STATE_REASON),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("ActiveConnection",     "o",      NM_DEVICE_ACTIVE_CONNECTION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Ip4Config",            "o",      NM_DEVICE_IP4_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Dhcp4Config",          "o",      NM_DEVICE_DHCP4_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Ip6Config",            "o",      NM_DEVICE_IP6_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Dhcp6Config",          "o",      NM_DEVICE_DHCP6_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("Managed",              "b",      NM_DEVICE_MANAGED,               NM_AUTH_PERMISSION_NETWORK_CONTROL, NM_AUDIT_OP_DEVICE_MANAGED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("Autoconnect",          "b",      NM_DEVICE_AUTOCONNECT,           NM_AUTH_PERMISSION_NETWORK_CONTROL, NM_AUDIT_OP_DEVICE_AUTOCONNECT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("FirmwareMissing",      "b",      NM_DEVICE_FIRMWARE_MISSING),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("NmPluginMissing",      "b",      NM_DEVICE_NM_PLUGIN_MISSING),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("DeviceType",           "u",      NM_DEVICE_DEVICE_TYPE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("AvailableConnections", "ao",     NM_DEVICE_AVAILABLE_CONNECTIONS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("PhysicalPortId",       "s",      NM_DEVICE_PHYSICAL_PORT_ID),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Mtu",                  "u",      NM_DEVICE_MTU),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Metered",              "u",      NM_DEVICE_METERED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("LldpNeighbors",        "aa{sv}", NM_DEVICE_LLDP_NEIGHBORS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Real",                 "b",      NM_DEVICE_REAL),
+		),
+	),
+};
+
+const NMDBusInterfaceInfoExtended nm_interface_info_device_statistics = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_STATISTICS,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE ("RefreshRateMs", "u", NM_DEVICE_STATISTICS_REFRESH_RATE_MS, NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS, NM_AUDIT_OP_STATISTICS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE     ("TxBytes",       "t", NM_DEVICE_STATISTICS_TX_BYTES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE     ("RxBytes",       "t", NM_DEVICE_STATISTICS_RX_BYTES),
+		),
+	),
+};
+
 static void
 nm_device_class_init (NMDeviceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 
 	g_type_class_add_private (object_class, sizeof (NMDevicePrivate));
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/Devices");
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/Devices");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device,
+	                                                              &nm_interface_info_device_statistics);
 
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
@@ -15085,7 +15743,6 @@ nm_device_class_init (NMDeviceClass *klass)
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
 
-	/* Statistics */
 	obj_properties[PROP_REFRESH_RATE_MS] =
 	    g_param_spec_uint (NM_DEVICE_STATISTICS_REFRESH_RATE_MS, "", "",
 	                       0, UINT32_MAX, 0,
@@ -15102,7 +15759,6 @@ nm_device_class_init (NMDeviceClass *klass)
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
-	/* Connectivity */
 	obj_properties[PROP_CONNECTIVITY] =
 	     g_param_spec_uint (NM_DEVICE_CONNECTIVITY, "", "",
 	                        NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
@@ -15185,15 +15841,11 @@ nm_device_class_init (NMDeviceClass *klass)
 	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 0);
 
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_SKELETON,
-	                                        "Reapply", impl_device_reapply,
-	                                        "GetAppliedConnection", impl_device_get_applied_connection,
-	                                        "Disconnect", impl_device_disconnect,
-	                                        "Delete", impl_device_delete,
-	                                        NULL);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_STATISTICS_SKELETON,
-	                                        NULL);
+	signals[CONNECTIVITY_CHANGED] =
+	    g_signal_new (NM_DEVICE_CONNECTIVITY_CHANGED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL,
+	                  g_cclosure_marshal_VOID__VOID,
+	                  G_TYPE_NONE, 0);
 }
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index ac73ee0c..66720f01 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -24,7 +24,8 @@
 
 #include <netinet/in.h>
 
-#include "nm-exported-object.h"
+#include "nm-setting-connection.h"
+#include "nm-dbus-object.h"
 #include "nm-dbus-interface.h"
 #include "nm-connection.h"
 #include "nm-rfkill-manager.h"
@@ -113,8 +114,7 @@ nm_device_state_reason_check (NMDeviceStateReason reason)
 #define NM_DEVICE_PARENT           "parent"
 
 /* the "slaves" property is internal in the parent class, but exposed
- * by the derived classes NMDeviceBond, NMDeviceBridge and NMDeviceTeam.
- * It is thus important that the property name matches. */
+ * by the derived classes NMDeviceBond, NMDeviceBridge and NMDeviceTeam. */
 #define NM_DEVICE_SLAVES           "slaves"         /* partially internal */
 
 #define NM_DEVICE_TYPE_DESC        "type-desc"      /* Internal only */
@@ -135,6 +135,7 @@ nm_device_state_reason_check (NMDeviceStateReason reason)
 #define NM_DEVICE_STATE_CHANGED         "state-changed"
 #define NM_DEVICE_LINK_INITIALIZED      "link-initialized"
 #define NM_DEVICE_AUTOCONNECT_ALLOWED   "autoconnect-allowed"
+#define NM_DEVICE_CONNECTIVITY_CHANGED  "connectivity-changed"
 
 #define NM_DEVICE_STATISTICS_REFRESH_RATE_MS "refresh-rate-ms"
 #define NM_DEVICE_STATISTICS_TX_BYTES        "tx-bytes"
@@ -172,10 +173,9 @@ typedef enum { /*< skip >*/
 struct _NMDevicePrivate;
 
 struct _NMDevice {
-	NMExportedObject parent;
-
-	/* private */
+	NMDBusObject parent;
 	struct _NMDevicePrivate *_priv;
+	CList devices_lst;
 };
 
 /* The flags have an relaxing meaning, that means, specifying more flags, can make
@@ -190,7 +190,9 @@ typedef enum { /*< skip >*/
 } NMDeviceCheckDevAvailableFlags;
 
 typedef struct {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
+
+	const char *default_type_description;
 
 	const char *connection_type;
 	const NMLinkType *link_types;
@@ -312,7 +314,7 @@ typedef struct {
 	gboolean    (* complete_connection)         (NMDevice *self,
 	                                             NMConnection *connection,
 	                                             const char *specific_object,
-	                                             const GSList *existing_connections,
+	                                             NMConnection *const*existing_connections,
 	                                             GError **error);
 
 	NMActStageReturn    (* act_stage1_prepare)  (NMDevice *self,
@@ -520,7 +522,7 @@ gboolean nm_device_can_auto_connect (NMDevice *self,
 gboolean nm_device_complete_connection (NMDevice *device,
                                         NMConnection *connection,
                                         const char *specific_object,
-                                        const GSList *existing_connection,
+                                        NMConnection *const*existing_connections,
                                         GError **error);
 
 gboolean nm_device_check_connection_compatible (NMDevice *device, NMConnection *connection);
@@ -754,12 +756,10 @@ void nm_device_update_firewall_zone (NMDevice *self);
 void nm_device_update_metered (NMDevice *self);
 void nm_device_reactivate_ip4_config (NMDevice *device,
                                       NMSettingIPConfig *s_ip4_old,
-                                      NMSettingIPConfig *s_ip4_new,
-                                      gboolean force_restart);
+                                      NMSettingIPConfig *s_ip4_new);
 void nm_device_reactivate_ip6_config (NMDevice *device,
                                       NMSettingIPConfig *s_ip6_old,
-                                      NMSettingIPConfig *s_ip6_new,
-                                      gboolean force_restart);
+                                      NMSettingIPConfig *s_ip6_new);
 
 gboolean nm_device_update_hw_address (NMDevice *self);
 void nm_device_update_initial_hw_address (NMDevice *self);
@@ -776,12 +776,22 @@ gboolean nm_device_hw_addr_get_cloned (NMDevice *self,
                                        gboolean *preserve,
                                        GError **error);
 
+typedef struct _NMDeviceConnectivityHandle NMDeviceConnectivityHandle;
+
 typedef void (*NMDeviceConnectivityCallback) (NMDevice *self,
+                                              NMDeviceConnectivityHandle *handle,
                                               NMConnectivityState state,
+                                              GError *error,
                                               gpointer user_data);
-void nm_device_check_connectivity (NMDevice *self,
-                                   NMDeviceConnectivityCallback callback,
-                                   gpointer user_data);
+
+void nm_device_check_connectivity_update_interval (NMDevice *self);
+
+NMDeviceConnectivityHandle *nm_device_check_connectivity (NMDevice *self,
+                                                          NMDeviceConnectivityCallback callback,
+                                                          gpointer user_data);
+
+void nm_device_check_connectivity_cancel (NMDeviceConnectivityHandle *handle);
+
 NMConnectivityState nm_device_get_connectivity_state (NMDevice *self);
 
 typedef struct _NMBtVTableNetworkServer NMBtVTableNetworkServer;
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index 2ed2a7d9..f637825b 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -286,19 +286,21 @@ lldp_neighbor_id_hash (gconstpointer ptr)
 }
 
 static int
-lldp_neighbor_id_cmp (gconstpointer a, gconstpointer b)
+lldp_neighbor_id_cmp (const LldpNeighbor *x, const LldpNeighbor *y)
 {
-	const LldpNeighbor *x = a, *y = b;
-	int c;
-
-	if (x->chassis_id_type != y->chassis_id_type)
-		return x->chassis_id_type < y->chassis_id_type ? -1 : 1;
-	if (x->port_id_type != y->port_id_type)
-		return x->port_id_type < y->port_id_type ? -1 : 1;
-	c = g_strcmp0 (x->chassis_id, y->chassis_id);
-	if (c == 0)
-		c = g_strcmp0 (x->port_id, y->port_id);
-	return c < 0 ? -1 : (c > 0 ? 1 : 0);
+	NM_CMP_SELF (x, y);
+	NM_CMP_FIELD (x, y, chassis_id_type);
+	NM_CMP_FIELD (x, y, port_id_type);
+	NM_CMP_FIELD_STR0 (x, y, chassis_id);
+	NM_CMP_FIELD_STR0 (x, y, port_id);
+	return 0;
+}
+
+static int
+lldp_neighbor_id_cmp_p (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+	return lldp_neighbor_id_cmp (*((const LldpNeighbor *const*) a),
+	                             *((const LldpNeighbor *const*) b));
 }
 
 static gboolean
@@ -838,20 +840,23 @@ GVariant *
 nm_lldp_listener_get_neighbors (NMLldpListener *self)
 {
 	NMLldpListenerPrivate *priv;
-	GVariantBuilder array_builder;
-	GList *neighbors, *iter;
 
 	g_return_val_if_fail (NM_IS_LLDP_LISTENER (self), FALSE);
 
 	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
 
-	if (!priv->variant) {
+	if (G_UNLIKELY (!priv->variant)) {
+		GVariantBuilder array_builder;
+		gs_free LldpNeighbor **neighbors = NULL;
+		guint i, n;
+
 		g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
-		neighbors = g_hash_table_get_keys (priv->lldp_neighbors);
-		neighbors = g_list_sort (neighbors, lldp_neighbor_id_cmp);
-		for (iter = neighbors; iter; iter = iter->next)
-			g_variant_builder_add_value (&array_builder, lldp_neighbor_to_variant (iter->data));
-		g_list_free (neighbors);
+		neighbors = (LldpNeighbor **) nm_utils_hash_keys_to_array (priv->lldp_neighbors,
+		                                                           lldp_neighbor_id_cmp_p,
+		                                                           NULL,
+		                                                           &n);
+		for (i = 0; i < n; i++)
+			g_variant_builder_add_value (&array_builder, lldp_neighbor_to_variant (neighbors[i]));
 		priv->variant = g_variant_ref_sink (g_variant_builder_end (&array_builder));
 	}
 	return priv->variant;
diff --git a/src/devices/ovs/meson.build b/src/devices/ovs/meson.build
new file mode 100644
index 00000000..7b1c4617
--- /dev/null
+++ b/src/devices/ovs/meson.build
@@ -0,0 +1,38 @@
+sources = files(
+  'nm-device-ovs-bridge.c',
+  'nm-device-ovs-interface.c',
+  'nm-device-ovs-port.c',
+  'nm-ovsdb.c',
+  'nm-ovs-factory.c'
+)
+
+deps = [
+  jansson_dep,
+  nm_dep
+]
+
+libnm_device_plugin_ovs = shared_module(
+  'nm-device-plugin-ovs',
+  sources: sources,
+  dependencies: deps,
+  c_args: '-DRUNSTATEDIR="@0@"'.format(nm_runstatedir),
+  link_args: ldflags_linker_script_devices,
+  link_depends: linker_script_devices,
+  install: true,
+  install_dir: nm_pkglibdir
+)
+
+core_plugins += libnm_device_plugin_ovs
+
+run_target(
+  'check-local-devices-ovs',
+  command: [check_exports, libnm_device_plugin_ovs.full_path(), linker_script_devices],
+  depends: libnm_device_plugin_ovs
+)
+
+# FIXME: check_so_symbols replacement
+'''
+check-local-devices-ovs: src/devices/ovs/libnm-device-plugin-ovs.la
+  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/ovs/.libs/libnm-device-plugin-ovs.so "$(srcdir)/linker-script-devices.ver"
+  $(call check_so_symbols,$(builddir)/src/devices/ovs/.libs/libnm-device-plugin-ovs.so)
+'''
diff --git a/src/devices/ovs/nm-device-ovs-bridge.c b/src/devices/ovs/nm-device-ovs-bridge.c
index 53ea2b82..5244ca8b 100644
--- a/src/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/devices/ovs/nm-device-ovs-bridge.c
@@ -28,10 +28,8 @@
 #include "nm-setting-connection.h"
 #include "nm-setting-ovs-bridge.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.OvsBridge.h"
-
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceOvsBridge);
+_LOG_DECLARE_SELF (NMDeviceOvsBridge);
 
 /*****************************************************************************/
 
@@ -133,11 +131,24 @@ nm_device_ovs_bridge_init (NMDeviceOvsBridge *self)
 {
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_ovs_bridge = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_OVS_BRIDGE,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_ovs_bridge_class_init (NMDeviceOvsBridgeClass *klass)
 {
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_ovs_bridge);
+
 	device_class->connection_type = NM_SETTING_OVS_BRIDGE_SETTING_NAME;
 	device_class->is_master = TRUE;
 	device_class->get_type_description = get_type_description;
@@ -149,8 +160,4 @@ nm_device_ovs_bridge_class_init (NMDeviceOvsBridgeClass *klass)
 	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
 	device_class->enslave_slave = enslave_slave;
 	device_class->release_slave = release_slave;
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_OVS_BRIDGE_SKELETON,
-	                                        NULL);
 }
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index ce32c2dd..eefe38c8 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -28,8 +28,6 @@
 #include "nm-setting-ovs-interface.h"
 #include "nm-setting-ovs-port.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.OvsInterface.h"
-
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceOvsInterface);
 
@@ -185,13 +183,26 @@ nm_device_ovs_interface_init (NMDeviceOvsInterface *self)
 {
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_ovs_interface = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_OVS_INTERFACE,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_ovs_interface_class_init (NMDeviceOvsInterfaceClass *klass)
 {
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_OPENVSWITCH);
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_ovs_interface);
+
 	device_class->connection_type = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
 	device_class->get_type_description = get_type_description;
 	device_class->create_and_realize = create_and_realize;
@@ -202,8 +213,4 @@ nm_device_ovs_interface_class_init (NMDeviceOvsInterfaceClass *klass)
 	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
 	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
 	device_class->can_unmanaged_external_down = can_unmanaged_external_down;
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_OVS_INTERFACE_SKELETON,
-	                                        NULL);
 }
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/devices/ovs/nm-device-ovs-port.c
index cb0915af..3f1fe974 100644
--- a/src/devices/ovs/nm-device-ovs-port.c
+++ b/src/devices/ovs/nm-device-ovs-port.c
@@ -28,10 +28,8 @@
 #include "nm-setting-ovs-port.h"
 #include "nm-setting-ovs-port.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.OvsPort.h"
-
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceOvsPort);
+_LOG_DECLARE_SELF (NMDeviceOvsPort);
 
 /*****************************************************************************/
 
@@ -180,11 +178,24 @@ nm_device_ovs_port_init (NMDeviceOvsPort *self)
 {
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_ovs_port = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_OVS_PORT,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_ovs_port_class_init (NMDeviceOvsPortClass *klass)
 {
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_ovs_port);
+
 	device_class->connection_type = NM_SETTING_OVS_PORT_SETTING_NAME;
 	device_class->is_master = TRUE;
 	device_class->get_type_description = get_type_description;
@@ -195,8 +206,4 @@ nm_device_ovs_port_class_init (NMDeviceOvsPortClass *klass)
 	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
 	device_class->enslave_slave = enslave_slave;
 	device_class->release_slave = release_slave;
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_OVS_PORT_SKELETON,
-	                                        NULL);
 }
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index 92fcfa01..b8f5a935 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -22,37 +22,20 @@
 #include "nm-ovsdb.h"
 
 #include <string.h>
-#include <jansson.h>
 #include <gmodule.h>
 #include <gio/gunixsocketaddress.h>
 
+#include "nm-utils/nm-jansson.h"
 #include "devices/nm-device.h"
 #include "platform/nm-platform.h"
 #include "nm-core-internal.h"
 
-/* Added in Jansson v2.4 (released Sep 23 2012), but travis.ci has v2.2. */
-#ifndef json_boolean
-#define json_boolean(val) ((val) ? json_true() : json_false())
-#endif
-
-/* Added in Jansson v2.5 (released Sep 19 2013), but travis.ci has v2.2. */
-#ifndef json_array_foreach
-#define json_array_foreach(array, index, value) \
-	for (index = 0; \
-	     index < json_array_size(array) && (value = json_array_get(array, index)); \
-	     index++)
-#endif
+/*****************************************************************************/
 
-/* Added in Jansson v2.3 (released Jan 27 2012) */
-#ifndef json_object_foreach
-#define json_object_foreach(object, key, value) \
-    for(key = json_object_iter_key(json_object_iter(object)); \
-        key && (value = json_object_iter_value(json_object_key_to_iter(key))); \
-        key = json_object_iter_key(json_object_iter_next(object, json_object_key_to_iter(key))))
+#if JANSSON_VERSION_HEX < 0x020400
+#warning "requires at least libjansson 2.4"
 #endif
 
-/*****************************************************************************/
-
 typedef struct {
 	char *name;
 	char *connection_uuid;
@@ -95,7 +78,7 @@ typedef struct {
 	GHashTable *interfaces;         /* interface uuid => OpenvswitchInterface */
 	GHashTable *ports;              /* port uuid => OpenvswitchPort */
 	GHashTable *bridges;            /* bridge uuid => OpenvswitchBridge */
-	const char *db_uuid;
+	char *db_uuid;
 } NMOvsdbPrivate;
 
 struct _NMOvsdb {
@@ -144,7 +127,7 @@ typedef struct {
 	OvsdbMethodCallback callback;
 	gpointer user_data;
 	union {
-		const char *ifname;
+		char *ifname;
 		struct {
 			NMConnection *bridge;
 			NMConnection *port;
@@ -884,7 +867,7 @@ ovsdb_got_update (NMOvsdb *self, json_t *msg)
 
 	if (ovs) {
 		iter = json_object_iter (ovs);
-		priv->db_uuid = g_strdup (iter ? json_object_iter_key (iter) : NULL);
+		priv->db_uuid = iter ? g_strdup (json_object_iter_key (iter)) : NULL;
 	}
 
 	/* Interfaces */
diff --git a/src/devices/team/meson.build b/src/devices/team/meson.build
new file mode 100644
index 00000000..4a533bc5
--- /dev/null
+++ b/src/devices/team/meson.build
@@ -0,0 +1,35 @@
+sources = files(
+  'nm-device-team.c',
+  'nm-team-factory.c'
+)
+
+deps = [
+  jansson_dep,
+  libteamdctl_dep,
+  nm_dep
+]
+
+libnm_device_plugin_team = shared_module(
+  'nm-device-plugin-team',
+  sources: sources,
+  dependencies: deps,
+  link_args: ldflags_linker_script_devices,
+  link_depends: linker_script_devices,
+  install: true,
+  install_dir: nm_pkglibdir
+)
+
+core_plugins += libnm_device_plugin_team
+
+run_target(
+  'check-local-devices-team',
+  command: [check_exports, libnm_device_plugin_team.full_path(), linker_script_devices],
+  depends: libnm_device_plugin_team
+)
+
+# FIXME: check_so_symbols replacement
+'''
+check-local-devices-team: src/devices/team/libnm-device-plugin-team.la
+  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/team/.libs/libnm-device-plugin-team.so "$(srcdir)/linker-script-devices.ver"
+  $(call check_so_symbols,$(builddir)/src/devices/team/.libs/libnm-device-plugin-team.so)
+'''
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index 098cd437..f83ba22b 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -28,8 +28,8 @@
 #include <sys/wait.h>
 #include <teamdctl.h>
 #include <stdlib.h>
-#include <jansson.h>
 
+#include "nm-utils/nm-jansson.h"
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
 #include "platform/nm-platform.h"
@@ -37,8 +37,6 @@
 #include "nm-ip4-config.h"
 #include "nm-dbus-compat.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Team.h"
-
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceTeam);
 
@@ -106,7 +104,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingTeam *s_team;
@@ -891,10 +889,27 @@ dispose (GObject *object)
 	G_OBJECT_CLASS (nm_device_team_parent_class)->dispose (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_team = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_TEAM,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress", "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Carrier",   "b",  NM_DEVICE_CARRIER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Slaves",    "ao", NM_DEVICE_SLAVES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Config",    "s",  NM_DEVICE_TEAM_CONFIG),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_team_class_init (NMDeviceTeamClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_TEAM_SETTING_NAME, NM_LINK_TYPE_TEAM)
@@ -903,6 +918,8 @@ nm_device_team_class_init (NMDeviceTeamClass *klass)
 	object_class->dispose = dispose;
 	object_class->get_property = get_property;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_team);
+
 	parent_class->is_master = TRUE;
 	parent_class->create_and_realize = create_and_realize;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
@@ -924,8 +941,4 @@ nm_device_team_class_init (NMDeviceTeamClass *klass)
 	                         G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_TEAM_SKELETON,
-	                                        NULL);
 }
diff --git a/src/devices/tests/meson.build b/src/devices/tests/meson.build
new file mode 100644
index 00000000..02c61ced
--- /dev/null
+++ b/src/devices/tests/meson.build
@@ -0,0 +1,18 @@
+test_units = [
+  'test-acd',
+  'test-lldp'
+]
+
+foreach test_unit: test_units
+  exe = executable(
+    test_unit,
+    test_unit + '.c',
+    dependencies: test_nm_dep
+  )
+
+  test(
+    'devices/' + test_unit,
+    test_script,
+    args: test_args + [exe.full_path()]
+  )
+endforeach
diff --git a/src/devices/tests/test-arping.c b/src/devices/tests/test-acd.c
index bcbc1140..8a2852a2 100644
--- a/src/devices/tests/test-arping.c
+++ b/src/devices/tests/test-acd.c
@@ -20,7 +20,7 @@
 
 #include "nm-default.h"
 
-#include "devices/nm-arping-manager.h"
+#include "devices/nm-acd-manager.h"
 #include "platform/tests/test-common.h"
 
 #define IFACE_VETH0 "nm-test-veth0"
@@ -34,6 +34,10 @@
 typedef struct {
 	int ifindex0;
 	int ifindex1;
+	const guint8 *hwaddr0;
+	const guint8 *hwaddr1;
+	size_t hwaddr0_len;
+	size_t hwaddr1_len;
 } test_fixture;
 
 static void
@@ -45,6 +49,9 @@ fixture_setup (test_fixture *fixture, gconstpointer user_data)
 
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex0, NULL));
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex1, NULL));
+
+	fixture->hwaddr0 = nm_platform_link_get_address (NM_PLATFORM_GET, fixture->ifindex0, &fixture->hwaddr0_len);
+	fixture->hwaddr1 = nm_platform_link_get_address (NM_PLATFORM_GET, fixture->ifindex1, &fixture->hwaddr1_len);
 }
 
 typedef struct {
@@ -54,28 +61,33 @@ typedef struct {
 } TestInfo;
 
 static void
-arping_manager_probe_terminated (NMArpingManager *arping_manager, GMainLoop *loop)
+acd_manager_probe_terminated (NMAcdManager *acd_manager, GMainLoop *loop)
 {
 	g_main_loop_quit (loop);
 }
 
 static void
-test_arping_common (test_fixture *fixture, TestInfo *info)
+test_acd_common (test_fixture *fixture, TestInfo *info)
 {
-	gs_unref_object NMArpingManager *manager = NULL;
+	gs_unref_object NMAcdManager *manager = NULL;
 	GMainLoop *loop;
 	int i;
-
-	if (!nm_utils_find_helper ("arping", NULL, NULL)) {
-		g_test_skip ("arping binary is missing");
-		return;
-	}
-
-	manager = nm_arping_manager_new (fixture->ifindex0);
+	const guint WAIT_TIME_OPTIMISTIC = 50;
+	guint wait_time;
+	gulong signal_id;
+
+	/* first, try with a short waittime. We hope that this is long enough
+	 * to successfully complete the test. Only if that's not the case, we
+	 * assume the computer is currently busy (high load) and we retry with
+	 * a longer timeout. */
+	wait_time = WAIT_TIME_OPTIMISTIC;
+again:
+
+	manager = nm_acd_manager_new (fixture->ifindex0, fixture->hwaddr0, fixture->hwaddr0_len);
 	g_assert (manager != NULL);
 
 	for (i = 0; info->addresses[i]; i++)
-		g_assert (nm_arping_manager_add_address (manager, info->addresses[i]));
+		g_assert (nm_acd_manager_add_address (manager, info->addresses[i]));
 
 	for (i = 0; info->peer_addresses[i]; i++) {
 		nmtstp_ip4_address_add (NULL, FALSE, fixture->ifindex1, info->peer_addresses[i],
@@ -83,38 +95,70 @@ test_arping_common (test_fixture *fixture, TestInfo *info)
 	}
 
 	loop = g_main_loop_new (NULL, FALSE);
-	g_signal_connect (manager, NM_ARPING_MANAGER_PROBE_TERMINATED,
-	                  G_CALLBACK (arping_manager_probe_terminated), loop);
-	g_assert (nm_arping_manager_start_probe (manager, 250, NULL));
+	signal_id = g_signal_connect (manager, NM_ACD_MANAGER_PROBE_TERMINATED,
+	                              G_CALLBACK (acd_manager_probe_terminated), loop);
+	g_assert (nm_acd_manager_start_probe (manager, wait_time));
 	g_assert (nmtst_main_loop_run (loop, 2000));
+	g_signal_handler_disconnect (manager, signal_id);
+	g_main_loop_unref (loop);
 
 	for (i = 0; info->addresses[i]; i++) {
-		g_assert_cmpint (nm_arping_manager_check_address (manager, info->addresses[i]),
-		                 ==,
-		                 info->expected_result[i]);
+		gboolean val;
+
+		val = nm_acd_manager_check_address (manager, info->addresses[i]);
+		if (val == info->expected_result[i])
+			continue;
+
+		if (wait_time == WAIT_TIME_OPTIMISTIC) {
+			/* probably we just had a glitch and the system took longer than
+			 * expected. Re-verify with a large timeout this time. */
+			wait_time = 1000;
+			g_clear_object (&manager);
+			goto again;
+		}
+
+		g_error ("expected check for address #%d (%s) to %s, but it didn't",
+		         i, nm_utils_inet4_ntop (info->addresses[i], NULL),
+		         info->expected_result[i] ? "detect no duplicated" : "detect a duplicate");
 	}
-
-	g_main_loop_unref (loop);
 }
 
 static void
-test_arping_1 (test_fixture *fixture, gconstpointer user_data)
+test_acd_probe_1 (test_fixture *fixture, gconstpointer user_data)
 {
 	TestInfo info = { .addresses       = { ADDR1, ADDR2, ADDR3 },
 	                  .peer_addresses  = { ADDR4 },
 	                  .expected_result = { TRUE, TRUE, TRUE } };
 
-	test_arping_common (fixture, &info);
+	test_acd_common (fixture, &info);
 }
 
 static void
-test_arping_2 (test_fixture *fixture, gconstpointer user_data)
+test_acd_probe_2 (test_fixture *fixture, gconstpointer user_data)
 {
 	TestInfo info = { .addresses       = { ADDR1, ADDR2, ADDR3, ADDR4 },
 	                  .peer_addresses  = { ADDR3, ADDR2 },
 	                  .expected_result = { TRUE, FALSE, FALSE, TRUE } };
 
-	test_arping_common (fixture, &info);
+	test_acd_common (fixture, &info);
+}
+
+static void
+test_acd_announce (test_fixture *fixture, gconstpointer user_data)
+{
+	gs_unref_object NMAcdManager *manager = NULL;
+	GMainLoop *loop;
+
+	manager = nm_acd_manager_new (fixture->ifindex0, fixture->hwaddr0, fixture->hwaddr0_len);
+	g_assert (manager != NULL);
+
+	g_assert (nm_acd_manager_add_address (manager, ADDR1));
+	g_assert (nm_acd_manager_add_address (manager, ADDR2));
+
+	loop = g_main_loop_new (NULL, FALSE);
+	nm_acd_manager_announce_addresses (manager);
+	g_assert (!nmtst_main_loop_run (loop, 200));
+	g_main_loop_unref (loop);
 }
 
 static void
@@ -135,6 +179,7 @@ _nmtstp_init_tests (int *argc, char ***argv)
 void
 _nmtstp_setup_tests (void)
 {
-	g_test_add ("/arping/1", test_fixture, NULL, fixture_setup, test_arping_1, fixture_teardown);
-	g_test_add ("/arping/2", test_fixture, NULL, fixture_setup, test_arping_2, fixture_teardown);
+	g_test_add ("/acd/probe/1", test_fixture, NULL, fixture_setup, test_acd_probe_1, fixture_teardown);
+	g_test_add ("/acd/probe/2", test_fixture, NULL, fixture_setup, test_acd_probe_2, fixture_teardown);
+	g_test_add ("/acd/announce", test_fixture, NULL, fixture_setup, test_acd_announce, fixture_teardown);
 }
diff --git a/src/devices/tests/test-lldp.c b/src/devices/tests/test-lldp.c
index c3c4f0d2..5d0625f8 100644
--- a/src/devices/tests/test-lldp.c
+++ b/src/devices/tests/test-lldp.c
@@ -347,8 +347,7 @@ static void
 _test_recv_fixture_setup (TestRecvFixture *fixture, gconstpointer user_data)
 {
 	const NMPlatformLink *link;
-	struct ifreq ifr = { };
-	int fd, s;
+	nm_auto_close int fd = -1;
 
 	fd = open ("/dev/net/tun", O_RDWR | O_CLOEXEC);
 	if (fd == -1) {
@@ -357,20 +356,45 @@ _test_recv_fixture_setup (TestRecvFixture *fixture, gconstpointer user_data)
 		return;
 	}
 
-	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
-	nm_utils_ifname_cpy (ifr.ifr_name, TEST_IFNAME);
-	g_assert (ioctl (fd, TUNSETIFF, &ifr) >= 0);
-
-	/* Bring the interface up */
-	s = socket (AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-	g_assert (s >= 0);
-	ifr.ifr_flags |= IFF_UP;
-	g_assert (ioctl (s, SIOCSIFFLAGS, &ifr) >= 0);
-	nm_close (s);
+	if (nmtst_get_rand_bool ()) {
+		const NMPlatformLnkTun lnk = {
+			.type = IFF_TAP,
+			.pi = FALSE,
+			.vnet_hdr = FALSE,
+			.multi_queue = FALSE,
+			.persist = FALSE,
+		};
+
+		nm_close (nm_steal_fd (&fd));
+
+		link = nmtstp_link_tun_add (NM_PLATFORM_GET,
+		                            FALSE,
+		                            TEST_IFNAME,
+		                            &lnk,
+		                            &fd);
+		g_assert (link);
+		nmtstp_link_set_updown (NM_PLATFORM_GET, -1, link->ifindex, TRUE);
+		link = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, TEST_IFNAME, NM_LINK_TYPE_TUN, 0);
+	} else {
+		int s;
+		struct ifreq ifr = { };
+
+		ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+		nm_utils_ifname_cpy (ifr.ifr_name, TEST_IFNAME);
+		g_assert (ioctl (fd, TUNSETIFF, &ifr) >= 0);
+
+		/* Bring the interface up */
+		s = socket (AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+		g_assert (s >= 0);
+		ifr.ifr_flags |= IFF_UP;
+		g_assert (ioctl (s, SIOCSIFFLAGS, &ifr) >= 0);
+		nm_close (s);
+
+		link = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, TEST_IFNAME, NM_LINK_TYPE_TUN, 100);
+	}
 
-	link = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, TEST_IFNAME, NM_LINK_TYPE_TAP, 100);
 	fixture->ifindex = link->ifindex;
-	fixture->fd = fd;
+	fixture->fd = nm_steal_fd (&fd);
 	memcpy (fixture->mac, link->addr.data, ETH_ALEN);
 }
 
diff --git a/src/devices/wifi/meson.build b/src/devices/wifi/meson.build
new file mode 100644
index 00000000..27eaeea6
--- /dev/null
+++ b/src/devices/wifi/meson.build
@@ -0,0 +1,51 @@
+common_sources = files(
+  'nm-wifi-ap.c',
+  'nm-wifi-utils.c'
+)
+
+sources = common_sources + files(
+  'nm-wifi-factory.c',
+  'nm-wifi-common.c',
+  'nm-device-wifi.c',
+  'nm-device-olpc-mesh.c'
+)
+
+if enable_iwd
+  sources += files(
+    'nm-device-iwd.c',
+    'nm-iwd-manager.c',
+  )
+endif
+
+deps = [
+  nm_dep
+]
+
+libnm_device_plugin_wifi = shared_module(
+  'nm-device-plugin-wifi',
+  sources: sources,
+  dependencies: deps,
+  link_args: ldflags_linker_script_devices,
+  link_depends: linker_script_devices,
+  install: true,
+  install_dir: nm_pkglibdir
+)
+
+core_plugins += libnm_device_plugin_wifi
+
+run_target(
+  'check-local-devices-wifi',
+  command: [check_exports, libnm_device_plugin_wifi.full_path(), linker_script_devices],
+  depends: libnm_device_plugin_wifi
+)
+
+# FIXME: check_so_symbols replacement
+'''
+check-local-devices-wifi: src/devices/wifi/libnm-device-plugin-wifi.la
+  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wifi/.libs/libnm-device-plugin-wifi.so "$(srcdir)/linker-script-devices.ver"
+  $(call check_so_symbols,$(builddir)/src/devices/wifi/.libs/libnm-device-plugin-wifi.so)
+'''
+
+if enable_tests
+  subdir('tests')
+endif
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
new file mode 100644
index 00000000..eeff5bd3
--- /dev/null
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -0,0 +1,1921 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2017 Intel Corporation
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-iwd.h"
+
+#include <string.h>
+
+#include "nm-common-macros.h"
+#include "devices/nm-device.h"
+#include "devices/nm-device-private.h"
+#include "nm-utils.h"
+#include "nm-act-request.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-wireless-security.h"
+#include "nm-setting-8021x.h"
+#include "settings/nm-settings-connection.h"
+#include "settings/nm-settings.h"
+#include "nm-wifi-utils.h"
+#include "nm-wifi-common.h"
+#include "nm-core-internal.h"
+#include "nm-config.h"
+#include "nm-iwd-manager.h"
+#include "nm-dbus-manager.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceIwd);
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceIwd,
+	PROP_MODE,
+	PROP_BITRATE,
+	PROP_ACCESS_POINTS,
+	PROP_ACTIVE_ACCESS_POINT,
+	PROP_CAPABILITIES,
+	PROP_SCANNING,
+);
+
+enum {
+	SCANNING_PROHIBITED,
+
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct {
+	GDBusObject *   dbus_obj;
+	GDBusProxy *    dbus_proxy;
+	CList           aps_lst_head;
+	NMWifiAP *      current_ap;
+	GCancellable *  cancellable;
+	NMDeviceWifiCapabilities capabilities;
+	NMActRequestGetSecretsCallId *wifi_secrets_id;
+	GDBusMethodInvocation *secrets_request;
+	guint           periodic_scan_id;
+	bool            enabled:1;
+	bool            can_scan:1;
+	bool            can_connect:1;
+	bool            scanning:1;
+	bool            scan_requested:1;
+} NMDeviceIwdPrivate;
+
+struct _NMDeviceIwd {
+	NMDevice parent;
+	NMDeviceIwdPrivate _priv;
+};
+
+struct _NMDeviceIwdClass {
+	NMDeviceClass parent;
+
+	/* Signals */
+	gboolean (*scanning_prohibited) (NMDeviceIwd *device, gboolean periodic);
+};
+
+/*****************************************************************************/
+
+G_DEFINE_TYPE (NMDeviceIwd, nm_device_iwd, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_IWD_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceIwd, NM_IS_DEVICE_IWD)
+
+/*****************************************************************************/
+
+static void schedule_periodic_scan (NMDeviceIwd *self,
+                                    NMDeviceState current_state);
+
+/*****************************************************************************/
+
+static void
+_ap_dump (NMDeviceIwd *self,
+          NMLogLevel log_level,
+          const NMWifiAP *ap,
+          const char *prefix,
+          gint32 now_s)
+{
+	char buf[1024];
+
+	buf[0] = '\0';
+	_NMLOG (log_level, LOGD_WIFI_SCAN, "wifi-ap: %-7s %s",
+	        prefix,
+	        nm_wifi_ap_to_string (ap, buf, sizeof (buf), now_s));
+}
+
+/* Callers ensure we're not removing current_ap */
+static void
+ap_add_remove (NMDeviceIwd *self,
+               gboolean is_adding, /* or else is removing */
+               NMWifiAP *ap,
+               gboolean recheck_available_connections)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (is_adding) {
+		g_object_ref (ap);
+		ap->wifi_device = NM_DEVICE (self);
+		c_list_link_tail (&priv->aps_lst_head, &ap->aps_lst);
+		nm_dbus_object_export (NM_DBUS_OBJECT (ap));
+		_ap_dump (self, LOGL_DEBUG, ap, "added", 0);
+		nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, TRUE);
+	} else {
+		ap->wifi_device = NULL;
+		c_list_unlink (&ap->aps_lst);
+		_ap_dump (self, LOGL_DEBUG, ap, "removed", 0);
+	}
+
+	_notify (self, PROP_ACCESS_POINTS);
+
+	if (!is_adding) {
+		nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, FALSE);
+		nm_dbus_object_clear_and_unexport (&ap);
+	}
+
+	nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
+	if (recheck_available_connections)
+		nm_device_recheck_available_connections (NM_DEVICE (self));
+}
+
+static void
+set_current_ap (NMDeviceIwd *self, NMWifiAP *new_ap, gboolean recheck_available_connections)
+{
+	NMDeviceIwdPrivate *priv;
+	NMWifiAP *old_ap;
+
+	g_return_if_fail (NM_IS_DEVICE_IWD (self));
+
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	old_ap = priv->current_ap;
+
+	if (old_ap == new_ap)
+		return;
+
+	if (new_ap)
+		priv->current_ap = g_object_ref (new_ap);
+	else
+		priv->current_ap = NULL;
+
+	if (old_ap) {
+		if (nm_wifi_ap_get_fake (old_ap))
+			ap_add_remove (self, FALSE, old_ap, recheck_available_connections);
+		g_object_unref (old_ap);
+	}
+
+	_notify (self, PROP_ACTIVE_ACCESS_POINT);
+	_notify (self, PROP_MODE);
+}
+
+static void
+remove_all_aps (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMWifiAP *ap, *ap_safe;
+
+	if (c_list_is_empty (&priv->aps_lst_head))
+		return;
+
+	set_current_ap (self, NULL, FALSE);
+
+	c_list_for_each_entry_safe (ap, ap_safe, &priv->aps_lst_head, aps_lst)
+		ap_add_remove (self, FALSE, ap, FALSE);
+
+	nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
+	nm_device_recheck_available_connections (NM_DEVICE (self));
+}
+
+static GVariant *
+vardict_from_network_type (const gchar *type)
+{
+	GVariantBuilder builder;
+	const gchar *key_mgmt = "";
+	const gchar *pairwise = "ccmp";
+
+	if (!strcmp (type, "psk"))
+		key_mgmt = "wpa-psk";
+	else if (!strcmp (type, "8021x"))
+		key_mgmt = "wpa-eap";
+	else
+		return NULL;
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add (&builder, "{sv}", "KeyMgmt",
+	                       g_variant_new_strv (&key_mgmt, 1));
+	g_variant_builder_add (&builder, "{sv}", "Pairwise",
+	                       g_variant_new_strv (&pairwise, 1));
+	g_variant_builder_add (&builder, "{sv}", "Group",
+	                       g_variant_new_string ("ccmp"));
+	return g_variant_new ("a{sv}", &builder);
+}
+
+static void
+get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *variant = NULL;
+	GVariantIter *networks;
+	const gchar *path, *name, *type;
+	int16_t signal;
+	NMWifiAP *ap, *ap_safe;
+	gboolean changed = FALSE;
+	GHashTableIter ap_iter;
+	gs_unref_hashtable GHashTable *new_aps = NULL;
+
+	variant = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
+	                                      G_VARIANT_TYPE ("(a(osns))"),
+	                                      &error);
+	if (!variant) {
+		_LOGE (LOGD_WIFI, "Device.GetOrderedNetworks failed: %s",
+		       error->message);
+		return;
+	}
+
+	new_aps = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_object_unref);
+
+	g_variant_get (variant, "(a(osns))", &networks);
+
+	while (g_variant_iter_next (networks, "(&o&sn&s)", &path, &name, &signal, &type)) {
+		GVariantBuilder builder;
+		gs_unref_variant GVariant *props = NULL;
+		GVariant *rsn;
+		static uint32_t ap_id = 0;
+		uint8_t bssid[6];
+
+		/*
+		 * What we get from IWD are networks, or ESSs, that may
+		 * contain multiple APs, or BSSs, each.  We don't get
+		 * information about any specific BSSs within an ESS but
+		 * we can safely present each ESS as an individual BSS to
+		 * NM, which will be seen as ESSs comprising a single BSS
+		 * each.  NM won't be able to handle roaming but IWD already
+		 * does that.  We fake the BSSIDs as they don't play any
+		 * role either.
+		 */
+		bssid[0] = 0x00;
+		bssid[1] = 0x01;
+		bssid[2] = 0x02;
+		bssid[3] = ap_id >> 16;
+		bssid[4] = ap_id >> 8;
+		bssid[5] = ap_id++;
+
+		/* WEP not supported */
+		if (!strcmp (type, "wep"))
+			continue;
+
+		g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+		g_variant_builder_add (&builder, "{sv}", "BSSID",
+		                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, bssid, 6, 1));
+		g_variant_builder_add (&builder, "{sv}", "Mode",
+		                       g_variant_new_string ("infrastructure"));
+
+		rsn = vardict_from_network_type (type);
+		if (rsn)
+			g_variant_builder_add (&builder, "{sv}", "RSN", rsn);
+
+		props = g_variant_new ("a{sv}", &builder);
+
+		ap = nm_wifi_ap_new_from_properties (path, props);
+		if (name[0] != '\0')
+			nm_wifi_ap_set_ssid (ap, (const guint8 *) name, strlen (name));
+		nm_wifi_ap_set_strength (ap, nm_wifi_utils_level_to_quality (signal / 100));
+		nm_wifi_ap_set_freq (ap, 2417);
+		nm_wifi_ap_set_max_bitrate (ap, 65000);
+		g_hash_table_insert (new_aps,
+		                     (gpointer) nm_wifi_ap_get_supplicant_path (ap),
+		                     ap);
+	}
+
+	g_variant_iter_free (networks);
+
+	c_list_for_each_entry_safe (ap, ap_safe, &priv->aps_lst_head, aps_lst) {
+
+		ap = g_hash_table_lookup (new_aps,
+		                          nm_wifi_ap_get_supplicant_path (ap));
+		if (ap) {
+			if (nm_wifi_ap_set_strength (ap, nm_wifi_ap_get_strength (ap))) {
+				_ap_dump (self, LOGL_TRACE, ap, "updated", 0);
+				changed = TRUE;
+			}
+			g_hash_table_remove (new_aps,
+			                     nm_wifi_ap_get_supplicant_path (ap));
+			continue;
+		}
+
+		if (ap == priv->current_ap) {
+			/* Normally IWD will prevent the current AP from being
+			 * removed from the list and set a low signal strength,
+			 * but just making sure.
+			 */
+			continue;
+		}
+
+		ap_add_remove (self, FALSE, ap, FALSE);
+		changed = TRUE;
+	}
+
+	g_hash_table_iter_init (&ap_iter, new_aps);
+	while (g_hash_table_iter_next (&ap_iter, NULL, (gpointer) &ap)) {
+		ap_add_remove (self, TRUE, ap, FALSE);
+		g_hash_table_iter_remove (&ap_iter);
+		changed = TRUE;
+	}
+
+	if (changed) {
+		nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
+		nm_device_recheck_available_connections (NM_DEVICE (self));
+	}
+}
+
+static void
+update_aps (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (!priv->cancellable)
+		priv->cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_call (priv->dbus_proxy, "GetOrderedNetworks",
+	                   g_variant_new ("()"), G_DBUS_CALL_FLAGS_NONE,
+	                   2000, priv->cancellable,
+	                   get_ordered_networks_cb, self);
+}
+
+static void
+send_disconnect (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	g_dbus_proxy_call (priv->dbus_proxy, "Disconnect", g_variant_new ("()"),
+	                   G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void
+wifi_secrets_cancel (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (priv->wifi_secrets_id)
+		nm_act_request_cancel_secrets (NULL, priv->wifi_secrets_id);
+	nm_assert (!priv->wifi_secrets_id);
+
+	if (priv->secrets_request) {
+		g_dbus_method_invocation_return_error_literal (priv->secrets_request, NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                                               "NM secrets request cancelled");
+		priv->secrets_request = NULL;
+	}
+
+}
+
+static void
+cleanup_association_attempt (NMDeviceIwd *self, gboolean disconnect)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	wifi_secrets_cancel (self);
+
+	set_current_ap (self, NULL, TRUE);
+
+	if (disconnect && priv->dbus_obj)
+		send_disconnect (self);
+}
+
+static void
+deactivate (NMDevice *device)
+{
+	cleanup_association_attempt (NM_DEVICE_IWD (device), TRUE);
+}
+
+static gboolean
+deactivate_async_finish (NMDevice *device, GAsyncResult *res, GError **error)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (NM_DEVICE_IWD (device));
+	gs_unref_variant GVariant *variant = NULL;
+
+	variant = g_dbus_proxy_call_finish (priv->dbus_proxy, res, error);
+	return variant != NULL;
+}
+
+typedef struct {
+	NMDeviceIwd *self;
+	GAsyncReadyCallback callback;
+	gpointer user_data;
+} DeactivateContext;
+
+static void
+disconnect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	DeactivateContext *ctx = user_data;
+
+	ctx->callback (G_OBJECT (ctx->self), res, ctx->user_data);
+
+	g_object_unref (ctx->self);
+	g_slice_free (DeactivateContext, ctx);
+}
+
+static void
+deactivate_async (NMDevice *device,
+                  GCancellable *cancellable,
+                  GAsyncReadyCallback callback,
+                  gpointer user_data)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	DeactivateContext *ctx;
+
+	ctx = g_slice_new0 (DeactivateContext);
+	ctx->self = g_object_ref (self);
+	ctx->callback = callback;
+	ctx->user_data = user_data;
+
+	g_dbus_proxy_call (priv->dbus_proxy, "Disconnect", g_variant_new ("()"),
+	                   G_DBUS_CALL_FLAGS_NONE, -1, cancellable, disconnect_cb, ctx);
+}
+
+static NMIwdNetworkSecurity
+get_connection_iwd_security (NMConnection *connection)
+{
+	NMSettingWirelessSecurity *s_wireless_sec;
+	const char *key_mgmt = NULL;
+
+	s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
+	if (!s_wireless_sec)
+		return NM_IWD_NETWORK_SECURITY_NONE;
+
+	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wireless_sec);
+	nm_assert (key_mgmt);
+
+	if (!strcmp (key_mgmt, "none") || !strcmp (key_mgmt, "ieee8021x"))
+		return NM_IWD_NETWORK_SECURITY_WEP;
+
+	if (!strcmp (key_mgmt, "wpa-psk"))
+		return NM_IWD_NETWORK_SECURITY_PSK;
+
+	nm_assert (!strcmp (key_mgmt, "wpa-eap"));
+	return NM_IWD_NETWORK_SECURITY_8021X;
+}
+
+static gboolean
+is_connection_known_network (NMConnection *connection)
+{
+	NMSettingWireless *s_wireless;
+	GBytes *ssid;
+	gs_free gchar *str_ssid = NULL;
+
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	if (!s_wireless)
+		return FALSE;
+
+	ssid = nm_setting_wireless_get_ssid (s_wireless);
+	if (!ssid)
+		return FALSE;
+
+	str_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL),
+	                                  g_bytes_get_size (ssid));
+
+	return nm_iwd_manager_is_known_network (nm_iwd_manager_get (),
+	                                        str_ssid,
+	                                        get_connection_iwd_security (connection));
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection)
+{
+	NMSettingConnection *s_con;
+	NMSettingWireless *s_wireless;
+	const char *mac;
+	const char * const *mac_blacklist;
+	int i;
+	const char *mode;
+	const char *perm_hw_addr;
+
+	if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->check_connection_compatible (device, connection))
+		return FALSE;
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (s_con);
+
+	if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_WIRELESS_SETTING_NAME))
+		return FALSE;
+
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	if (!s_wireless)
+		return FALSE;
+
+	perm_hw_addr = nm_device_get_permanent_hw_address (device);
+	mac = nm_setting_wireless_get_mac_address (s_wireless);
+	if (perm_hw_addr) {
+		if (mac && !nm_utils_hwaddr_matches (mac, -1, perm_hw_addr, -1))
+			return FALSE;
+
+		/* Check for MAC address blacklist */
+		mac_blacklist = nm_setting_wireless_get_mac_address_blacklist (s_wireless);
+		for (i = 0; mac_blacklist[i]; i++) {
+			if (!nm_utils_hwaddr_valid (mac_blacklist[i], ETH_ALEN)) {
+				g_warn_if_reached ();
+				return FALSE;
+			}
+
+			if (nm_utils_hwaddr_matches (mac_blacklist[i], -1, perm_hw_addr, -1))
+				return FALSE;
+		}
+	} else if (mac)
+		return FALSE;
+
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
+		return FALSE;
+
+	/* 8021x networks can only be used if they've been provisioned on the IWD side and
+	 * thus are Known Networks.
+	 */
+	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_connection_known_network (connection))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+check_connection_available (NMDevice *device,
+                            NMConnection *connection,
+                            NMDeviceCheckConAvailableFlags flags,
+                            const char *specific_object)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMSettingWireless *s_wifi;
+	const char *mode;
+
+	s_wifi = nm_connection_get_setting_wireless (connection);
+	g_return_val_if_fail (s_wifi, FALSE);
+
+	/* Only Infrastrusture mode at this time */
+	mode = nm_setting_wireless_get_mode (s_wifi);
+	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
+		return FALSE;
+
+	/* Hidden SSIDs not supported yet */
+	if (nm_setting_wireless_get_hidden (s_wifi))
+		return FALSE;
+
+	/* 8021x networks can only be used if they've been provisioned on the IWD side and
+	 * thus are Known Networks.
+	 */
+	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_connection_known_network (connection))
+			return FALSE;
+	}
+
+	/* a connection that is available for a certain @specific_object, MUST
+	 * also be available in general (without @specific_object). */
+
+	if (specific_object) {
+		NMWifiAP *ap;
+
+		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
+		return ap ? nm_wifi_ap_check_compatible (ap, connection) : FALSE;
+	}
+
+	if (NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP))
+		return TRUE;
+
+	/* Check at least one AP is compatible with this connection */
+	return !!nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+}
+
+static gboolean
+complete_connection (NMDevice *device,
+                     NMConnection *connection,
+                     const char *specific_object,
+                     NMConnection *const*existing_connections,
+                     GError **error)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMSettingWireless *s_wifi;
+	const char *setting_mac;
+	char *str_ssid = NULL;
+	NMWifiAP *ap;
+	const GByteArray *ssid = NULL;
+	GByteArray *tmp_ssid = NULL;
+	GBytes *setting_ssid = NULL;
+	const char *perm_hw_addr;
+	const char *mode;
+
+	s_wifi = nm_connection_get_setting_wireless (connection);
+
+	mode = s_wifi ? nm_setting_wireless_get_mode (s_wifi) : NULL;
+
+	if (s_wifi && !nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		g_set_error_literal (error,
+		                     NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                     "Only Infrastructure mode is supported.");
+		return FALSE;
+	}
+
+	if (!specific_object) {
+		/* If not given a specific object, we need at minimum an SSID */
+		if (!s_wifi) {
+			g_set_error_literal (error,
+			                     NM_DEVICE_ERROR,
+			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+			                     "A 'wireless' setting is required if no AP path was given.");
+			return FALSE;
+		}
+
+		setting_ssid = nm_setting_wireless_get_ssid (s_wifi);
+		if (!setting_ssid || g_bytes_get_size (setting_ssid) == 0) {
+			g_set_error_literal (error,
+			                     NM_DEVICE_ERROR,
+			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+			                     "A 'wireless' setting with a valid SSID is required if no AP path was given.");
+			return FALSE;
+		}
+
+		/* Find a compatible AP in the scan list */
+		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+		if (!ap) {
+			g_set_error_literal (error,
+			                     NM_DEVICE_ERROR,
+			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+			                     "No compatible AP in the scan list and hidden SSIDs not supported.");
+			return FALSE;
+		}
+	} else {
+		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
+		if (!ap) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND,
+			             "The access point %s was not in the scan list.",
+			             specific_object);
+			return FALSE;
+		}
+	}
+
+	/* Add a wifi setting if one doesn't exist yet */
+	if (!s_wifi) {
+		s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
+		nm_connection_add_setting (connection, NM_SETTING (s_wifi));
+	}
+
+	ssid = nm_wifi_ap_get_ssid (ap);
+
+	if (ssid == NULL) {
+		g_set_error_literal (error,
+		                     NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                     "A 'wireless' setting with a valid SSID is required.");
+		return FALSE;
+	}
+
+	if (!nm_wifi_ap_complete_connection (ap,
+	                                     connection,
+	                                     nm_wifi_utils_is_manf_default_ssid (ssid),
+	                                     error)) {
+		if (tmp_ssid)
+			g_byte_array_unref (tmp_ssid);
+		return FALSE;
+	}
+
+	str_ssid = nm_utils_ssid_to_utf8 (ssid->data, ssid->len);
+
+	nm_utils_complete_generic (nm_device_get_platform (device),
+	                           connection,
+	                           NM_SETTING_WIRELESS_SETTING_NAME,
+	                           existing_connections,
+	                           str_ssid,
+	                           str_ssid,
+	                           NULL,
+	                           TRUE);
+	g_free (str_ssid);
+	if (tmp_ssid)
+		g_byte_array_unref (tmp_ssid);
+
+	/* 8021x networks can only be used if they've been provisioned on the IWD side and
+	 * thus are Known Networks.
+	 */
+	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_connection_known_network (connection)) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+			                     "This 8021x network has not been provisioned on this machine");
+			return FALSE;
+		}
+	}
+
+	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;
+}
+
+static gboolean
+is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	gs_unref_variant GVariant *value = NULL;
+
+	if (!priv->enabled || !priv->dbus_obj)
+		return FALSE;
+
+	value = g_dbus_proxy_get_cached_property (priv->dbus_proxy, "Powered");
+	return g_variant_get_boolean (value);
+}
+
+static gboolean
+get_autoconnect_allowed (NMDevice *device)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (NM_DEVICE_IWD (device));
+
+	return is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE)
+	       && priv->can_connect;
+}
+
+static gboolean
+can_auto_connect (NMDevice *device,
+                  NMConnection *connection,
+                  char **specific_object)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMSettingWireless *s_wifi;
+	NMWifiAP *ap;
+	const char *mode;
+	guint64 timestamp = 0;
+
+	nm_assert (!specific_object || !*specific_object);
+
+	if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->can_auto_connect (device, connection, NULL))
+		return FALSE;
+
+	s_wifi = nm_connection_get_setting_wireless (connection);
+	g_return_val_if_fail (s_wifi, FALSE);
+
+	/* Only Infrastrusture mode */
+	mode = nm_setting_wireless_get_mode (s_wifi);
+	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
+		return FALSE;
+
+	/* Don't autoconnect to networks that have been tried at least once
+	 * but haven't been successful, since these are often accidental choices
+	 * from the menu and the user may not know the password.
+	 */
+	if (nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), &timestamp)) {
+		if (timestamp == 0)
+			return FALSE;
+	}
+
+	/* 8021x networks can only be used if they've been provisioned on the IWD side and
+	 * thus are Known Networks.
+	 */
+	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_connection_known_network (connection))
+			return FALSE;
+	}
+
+	ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+	if (ap) {
+		/* All good; connection is usable */
+		NM_SET_OUT (specific_object, g_strdup (nm_dbus_object_get_path (NM_DBUS_OBJECT (ap))));
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+const CList *
+_nm_device_iwd_get_aps (NMDeviceIwd *self)
+{
+	return &NM_DEVICE_IWD_GET_PRIVATE (self)->aps_lst_head;
+}
+
+static gboolean
+check_scanning_prohibited (NMDeviceIwd *self, gboolean periodic)
+{
+	gboolean prohibited = FALSE;
+
+	g_signal_emit (self, signals[SCANNING_PROHIBITED], 0, periodic, &prohibited);
+	return prohibited;
+}
+
+static void
+scan_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv;
+	gs_free_error GError *error = NULL;
+
+	if (   !_nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
+	                                   G_VARIANT_TYPE ("()"), &error)
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	priv->scan_requested = FALSE;
+
+	/* On success, priv->scanning becomes true right before or right
+	 * after this callback, so the next automatic scan will be
+	 * scheduled when priv->scanning goes back to false.  On error,
+	 * schedule a retry now.
+	 */
+	if (error && !priv->scanning) {
+		NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
+
+		schedule_periodic_scan (self, state);
+	}
+}
+
+static void
+dbus_request_scan_cb (NMDevice *device,
+                      GDBusMethodInvocation *context,
+                      NMAuthSubject *subject,
+                      GError *error,
+                      gpointer user_data)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv;
+	gs_unref_variant GVariant *scan_options = user_data;
+
+	if (error) {
+		g_dbus_method_invocation_return_gerror (context, error);
+		return;
+	}
+
+	if (check_scanning_prohibited (self, FALSE)) {
+		g_dbus_method_invocation_return_error_literal (context,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+		                                               "Scanning not allowed at this time");
+		return;
+	}
+
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (   !priv->enabled
+	    || !priv->dbus_obj
+	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
+	    || nm_device_is_activating (device)) {
+		g_dbus_method_invocation_return_error_literal (context,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+		                                               "Scanning not allowed while unavailable");
+		return;
+	}
+
+	if (scan_options) {
+		gs_unref_variant GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
+
+		if (val) {
+			g_dbus_method_invocation_return_error_literal (context,
+			                                               NM_DEVICE_ERROR,
+			                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+			                                               "'ssid' scan option not supported");
+			return;
+		}
+	}
+
+	if (!priv->scanning && !priv->scan_requested) {
+		g_dbus_proxy_call (priv->dbus_proxy, "Scan",
+		                   g_variant_new ("()"),
+		                   G_DBUS_CALL_FLAGS_NONE, -1,
+		                   priv->cancellable, scan_cb, self);
+		priv->scan_requested = TRUE;
+	}
+
+	g_dbus_method_invocation_return_value (context, NULL);
+}
+
+void
+_nm_device_iwd_request_scan (NMDeviceIwd *self,
+                             GVariant *options,
+                             GDBusMethodInvocation *invocation)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+
+	if (   !priv->enabled
+	    || !priv->dbus_obj
+	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
+	    || nm_device_is_activating (device)) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+		                                               "Scanning not allowed while unavailable");
+		return;
+	}
+
+	g_signal_emit_by_name (device,
+	                       NM_DEVICE_AUTH_REQUEST,
+	                       invocation,
+	                       NULL,
+	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                       TRUE,
+	                       dbus_request_scan_cb,
+	                       options ? g_variant_ref (options) : NULL);
+}
+
+static gboolean
+scanning_prohibited (NMDeviceIwd *self, gboolean periodic)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	g_return_val_if_fail (priv->dbus_obj != NULL, TRUE);
+
+	switch (nm_device_get_state (NM_DEVICE (self))) {
+	case NM_DEVICE_STATE_UNKNOWN:
+	case NM_DEVICE_STATE_UNMANAGED:
+	case NM_DEVICE_STATE_UNAVAILABLE:
+	case NM_DEVICE_STATE_PREPARE:
+	case NM_DEVICE_STATE_CONFIG:
+	case NM_DEVICE_STATE_NEED_AUTH:
+	case NM_DEVICE_STATE_IP_CONFIG:
+	case NM_DEVICE_STATE_IP_CHECK:
+	case NM_DEVICE_STATE_SECONDARIES:
+	case NM_DEVICE_STATE_DEACTIVATING:
+		/* Prohibit scans when unusable or activating */
+		return TRUE;
+	case NM_DEVICE_STATE_DISCONNECTED:
+	case NM_DEVICE_STATE_FAILED:
+		/* Can always scan when disconnected */
+		return FALSE;
+	case NM_DEVICE_STATE_ACTIVATED:
+		break;
+	}
+
+	/* Prohibit scans if IWD is busy */
+	return !priv->can_scan;
+}
+
+static void
+wifi_secrets_cb (NMActRequest *req,
+                 NMActRequestGetSecretsCallId *call_id,
+                 NMSettingsConnection *s_connection,
+                 GError *error,
+                 gpointer user_data)
+{
+	NMDevice *device = user_data;
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv;
+	NMSettingWirelessSecurity *s_wireless_sec;
+	const gchar *psk;
+
+	g_return_if_fail (NM_IS_DEVICE_IWD (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->wifi_secrets_id == call_id);
+
+	priv->wifi_secrets_id = NULL;
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	g_return_if_fail (priv->secrets_request);
+	g_return_if_fail (req == nm_device_get_act_request (device));
+	g_return_if_fail (nm_act_request_get_settings_connection (req) == s_connection);
+
+	if (nm_device_get_state (device) != NM_DEVICE_STATE_NEED_AUTH)
+		goto secrets_error;
+
+	if (error) {
+		_LOGW (LOGD_WIFI, "%s", error->message);
+		goto secrets_error;
+	}
+
+	s_wireless_sec = nm_connection_get_setting_wireless_security (nm_act_request_get_applied_connection (req));
+	if (!s_wireless_sec)
+		goto secrets_error;
+
+	psk = nm_setting_wireless_security_get_psk (s_wireless_sec);
+	if (!psk)
+		goto secrets_error;
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI,
+	       "Returning a new PSK to the IWD Agent");
+
+	g_dbus_method_invocation_return_value (priv->secrets_request,
+	                                       g_variant_new ("(s)", psk));
+	priv->secrets_request = NULL;
+
+	/* Change state back to what it was before NEED_AUTH */
+	nm_device_state_changed (device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+	return;
+
+secrets_error:
+	if (priv->secrets_request) {
+		g_dbus_method_invocation_return_error_literal (priv->secrets_request, NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                                               "NM secrets request failed");
+		priv->secrets_request = NULL;
+	}
+
+	nm_device_state_changed (device,
+	                         NM_DEVICE_STATE_FAILED,
+	                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+
+	cleanup_association_attempt (self, TRUE);
+}
+
+static void
+wifi_secrets_get_secrets (NMDeviceIwd *self,
+                          const char *setting_name,
+                          NMSecretAgentGetSecretsFlags flags)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMActRequest *req;
+
+	wifi_secrets_cancel (self);
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	priv->wifi_secrets_id = nm_act_request_get_secrets (req,
+	                                                    TRUE,
+	                                                    setting_name,
+	                                                    flags,
+	                                                    NULL,
+	                                                    wifi_secrets_cb,
+	                                                    self);
+}
+
+static void
+network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDevice *device = NM_DEVICE (self);
+	gs_free_error GError *error = NULL;
+	NMConnection *connection;
+	NMSettingWireless *s_wifi;
+	GBytes *ssid;
+	gs_free gchar *str_ssid = NULL;
+
+	if (!_nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
+	                                 G_VARIANT_TYPE ("()"),
+	                                 &error)) {
+		gs_free gchar *dbus_error = NULL;
+
+		/* Connection failed; radio problems or if the network wasn't
+		 * open, the passwords or certificates may be wrong.
+		 */
+
+		_LOGE (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) Network.Connect failed: %s",
+		       error->message);
+
+		connection = nm_device_get_applied_connection (device);
+		if (!connection)
+			goto failed;
+
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR))
+			dbus_error = g_dbus_error_get_remote_error (error);
+
+		/* If secrets were wrong, we'd be getting a net.connman.iwd.Failed */
+		if (nm_streq0 (dbus_error, "net.connman.iwd.Failed")) {
+			nm_connection_clear_secrets (connection);
+
+			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+		} else if (   !nm_utils_error_is_cancelled (error, TRUE)
+		           && nm_device_is_activating (device))
+			goto failed;
+
+		/* Call Disconnect to make sure IWD's autoconnect is disabled */
+		cleanup_association_attempt (self, TRUE);
+
+		return;
+	}
+
+	nm_assert (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG);
+
+	connection = nm_device_get_applied_connection (device);
+	if (!connection)
+		goto failed;
+
+	s_wifi = nm_connection_get_setting_wireless (connection);
+	if (!s_wifi)
+		goto failed;
+
+	ssid = nm_setting_wireless_get_ssid (s_wifi);
+	if (!ssid)
+		goto failed;
+
+	str_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL),
+	                                  g_bytes_get_size (ssid));
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) Stage 2 of 5 (Device Configure) successful.  Connected to '%s'.",
+	       str_ssid);
+	nm_device_activate_schedule_stage3_ip_config_start (device);
+
+	nm_iwd_manager_network_connected (nm_iwd_manager_get (), str_ssid,
+	                                  get_connection_iwd_security (connection));
+
+	return;
+
+failed:
+	cleanup_association_attempt (self, FALSE);
+	nm_device_queue_state (device, NM_DEVICE_STATE_FAILED,
+	                       NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+}
+
+static void
+set_powered (NMDeviceIwd *self, gboolean powered)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	g_dbus_proxy_call (priv->dbus_proxy,
+	                   "org.freedesktop.DBus.Properties.Set",
+	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
+	                                  "Powered",
+	                                  g_variant_new ("b", powered)),
+	                   G_DBUS_CALL_FLAGS_NONE, 2000,
+	                   NULL, NULL, NULL);
+}
+
+/*****************************************************************************/
+
+static NMActStageReturn
+act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMActStageReturn ret;
+	NMWifiAP *ap = NULL;
+	NMActRequest *req;
+	NMConnection *connection;
+	NMSettingWireless *s_wireless;
+	const char *ap_path;
+
+	ret = NM_DEVICE_CLASS (nm_device_iwd_parent_class)->act_stage1_prepare (device, out_failure_reason);
+	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
+		return ret;
+
+	req = nm_device_get_act_request (device);
+	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
+
+	connection = nm_act_request_get_applied_connection (req);
+	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
+
+	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) {
+		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+
+		/* TODO: assuming hidden networks aren't supported do we need
+		 * to consider the case of APs that are not in the scan list
+		 * yet, for which nm-device-wifi.c creates the temporary fake
+		 * AP object?
+		 */
+
+		nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
+	                                              nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
+	}
+
+	set_current_ap (self, ap, FALSE);
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMActRequest *req;
+	NMWifiAP *ap;
+	NMConnection *connection;
+	GError *error = NULL;
+	GDBusProxy *network_proxy;
+
+	req = nm_device_get_act_request (device);
+	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
+
+	ap = priv->current_ap;
+	if (!ap) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		goto out;
+	}
+
+	connection = nm_act_request_get_applied_connection (req);
+	g_assert (connection);
+
+	/* 802.1x networks that are not IWD Known Networks will definitely
+	 * fail, for other combinations we will let the Connect call fail
+	 * or ask us for any missing secrets through the Agent.
+	 */
+	if (   !is_connection_known_network (connection)
+	    && nm_connection_get_setting_802_1x (connection)) {
+		_LOGI (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) access point '%s' has 802.1x security, but is not configured.",
+		       nm_connection_get_id (connection));
+
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+		ret = NM_ACT_STAGE_RETURN_FAILURE;
+		goto out;
+	}
+
+	/* Locate the IWD Network object */
+	network_proxy = g_dbus_proxy_new_for_bus_sync (NM_IWD_BUS_TYPE,
+	                                               G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+	                                               G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+	                                               NULL,
+	                                               NM_IWD_SERVICE,
+	                                               nm_wifi_ap_get_supplicant_path (ap),
+	                                               NM_IWD_NETWORK_INTERFACE,
+	                                               NULL, &error);
+	if (!network_proxy) {
+		_LOGE (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) could not get Network interface proxy for %s: %s",
+		       nm_wifi_ap_get_supplicant_path (ap),
+		       error->message);
+		g_clear_error (&error);
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		goto out;
+	}
+
+	if (!priv->cancellable)
+		priv->cancellable = g_cancellable_new ();
+
+	/* Call Network.Connect.  No timeout because IWD already handles
+	 * timeouts.
+	 */
+	g_dbus_proxy_call (network_proxy, "Connect",
+	                   g_variant_new ("()"),
+	                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+	                   priv->cancellable, network_connect_cb, self);
+
+	g_object_unref (network_proxy);
+
+	/* We'll get stage3 started when the supplicant connects */
+	ret = NM_ACT_STAGE_RETURN_POSTPONE;
+
+out:
+	if (ret == NM_ACT_STAGE_RETURN_FAILURE)
+		cleanup_association_attempt (self, FALSE);
+
+	return ret;
+}
+
+static guint32
+get_configured_mtu (NMDevice *device, gboolean *out_is_user_config)
+{
+	NMSettingWireless *setting;
+	gint64 mtu_default;
+	guint32 mtu;
+
+	nm_assert (NM_IS_DEVICE (device));
+	nm_assert (out_is_user_config);
+
+	setting = NM_SETTING_WIRELESS (nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS));
+	if (!setting)
+		g_return_val_if_reached (0);
+
+	mtu = nm_setting_wireless_get_mtu (setting);
+	if (mtu == 0) {
+		mtu_default = nm_device_get_configured_mtu_from_connection_default (device, "wifi.mtu");
+		if (mtu_default >= 0) {
+			*out_is_user_config = TRUE;
+			return (guint32) mtu_default;
+		}
+	}
+	*out_is_user_config = (mtu != 0);
+	return mtu;
+}
+
+static gboolean
+periodic_scan_timeout_cb (gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	priv->periodic_scan_id = 0;
+
+	if (priv->scanning || priv->scan_requested)
+		return FALSE;
+
+	g_dbus_proxy_call (priv->dbus_proxy, "Scan", g_variant_new ("()"),
+	                   G_DBUS_CALL_FLAGS_NONE, -1,
+	                   priv->cancellable, scan_cb, self);
+	priv->scan_requested = TRUE;
+
+	return FALSE;
+}
+
+static void
+schedule_periodic_scan (NMDeviceIwd *self, NMDeviceState current_state)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	guint interval;
+
+	if (current_state <= NM_DEVICE_STATE_UNAVAILABLE)
+		return;
+
+	if (current_state == NM_DEVICE_STATE_DISCONNECTED)
+		interval = 10;
+	else
+		interval = 20;
+
+	nm_clear_g_source (&priv->periodic_scan_id);
+	priv->periodic_scan_id = g_timeout_add_seconds (interval,
+	                                                periodic_scan_timeout_cb,
+	                                                self);
+}
+
+static void
+device_state_changed (NMDevice *device,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) {
+		remove_all_aps (self);
+		nm_clear_g_source (&priv->periodic_scan_id);
+	} else if (old_state <= NM_DEVICE_STATE_UNAVAILABLE) {
+		update_aps (self);
+		schedule_periodic_scan (self, new_state);
+	}
+
+	switch (new_state) {
+	case NM_DEVICE_STATE_UNMANAGED:
+		break;
+	case NM_DEVICE_STATE_UNAVAILABLE:
+		/*
+		 * If the device is enabled and the IWD manager is ready,
+		 * transition to DISCONNECTED because the device is now
+		 * ready to use.
+		 */
+		if (priv->enabled && priv->dbus_obj) {
+			nm_device_queue_recheck_available (device,
+			                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+			                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		}
+		break;
+	case NM_DEVICE_STATE_NEED_AUTH:
+		break;
+	case NM_DEVICE_STATE_IP_CHECK:
+		break;
+	case NM_DEVICE_STATE_ACTIVATED:
+		break;
+	case NM_DEVICE_STATE_FAILED:
+		break;
+	case NM_DEVICE_STATE_DISCONNECTED:
+		break;
+	default:
+		break;
+	}
+}
+
+static gboolean
+get_enabled (NMDevice *device)
+{
+	return NM_DEVICE_IWD_GET_PRIVATE ((NMDeviceIwd *) device)->enabled;
+}
+
+static void
+set_enabled (NMDevice *device, gboolean enabled)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDeviceState state;
+
+	enabled = !!enabled;
+
+	if (priv->enabled == enabled)
+		return;
+
+	priv->enabled = enabled;
+
+	_LOGD (LOGD_WIFI, "device now %s", enabled ? "enabled" : "disabled");
+
+	state = nm_device_get_state (device);
+	if (state < NM_DEVICE_STATE_UNAVAILABLE) {
+		_LOGD (LOGD_WIFI, "(%s): device blocked by UNMANAGED state",
+		       enabled ? "enable" : "disable");
+		return;
+	}
+
+	if (priv->dbus_proxy)
+		set_powered (self, enabled);
+
+	if (enabled) {
+		if (state != NM_DEVICE_STATE_UNAVAILABLE)
+			_LOGW (LOGD_CORE, "not in expected unavailable state!");
+
+		if (priv->dbus_obj)
+			nm_device_queue_recheck_available (device,
+			                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+			                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+	} else {
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_UNAVAILABLE,
+		                         NM_DEVICE_STATE_REASON_NONE);
+	}
+}
+
+static gboolean
+can_reapply_change (NMDevice *device,
+                    const char *setting_name,
+                    NMSetting *s_old,
+                    NMSetting *s_new,
+                    GHashTable *diffs,
+                    GError **error)
+{
+	NMDeviceClass *device_class;
+
+	/* Only handle wireless setting here, delegate other settings to parent class */
+	if (nm_streq (setting_name, NM_SETTING_WIRELESS_SETTING_NAME)) {
+		return nm_device_hash_check_invalid_keys (diffs,
+		                                          NM_SETTING_WIRELESS_SETTING_NAME,
+		                                          error,
+		                                          NM_SETTING_WIRELESS_MTU); /* reapplied with IP config */
+	}
+
+	device_class = NM_DEVICE_CLASS (nm_device_iwd_parent_class);
+	return device_class->can_reapply_change (device,
+	                                         setting_name,
+	                                         s_old,
+	                                         s_new,
+	                                         diffs,
+	                                         error);
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (object);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	const char **list;
+
+	switch (prop_id) {
+	case PROP_MODE:
+		if (priv->current_ap)
+			g_value_set_uint (value, NM_802_11_MODE_INFRA);
+		else
+			g_value_set_uint (value, NM_802_11_MODE_UNKNOWN);
+		break;
+	case PROP_BITRATE:
+		g_value_set_uint (value, 65000);
+		break;
+	case PROP_CAPABILITIES:
+		g_value_set_uint (value, priv->capabilities);
+		break;
+	case PROP_ACCESS_POINTS:
+		list = nm_wifi_aps_get_paths (&priv->aps_lst_head, TRUE);
+		g_value_take_boxed (value, nm_utils_strv_make_deep_copied (list));
+		break;
+	case PROP_ACTIVE_ACCESS_POINT:
+		nm_dbus_utils_g_value_set_object_path (value, priv->current_ap);
+		break;
+	case PROP_SCANNING:
+		g_value_set_boolean (value, priv->scanning);
+		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)
+{
+	NMDeviceIwd *device = NM_DEVICE_IWD (object);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (device);
+
+	switch (prop_id) {
+	case PROP_CAPABILITIES:
+		/* construct-only */
+		priv->capabilities = g_value_get_uint (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+state_changed (NMDeviceIwd *self, const gchar *new_state)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	NMDeviceState dev_state = nm_device_get_state (device);
+	gboolean iwd_connection = FALSE;
+	gboolean can_connect;
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state);
+
+	if (   dev_state >= NM_DEVICE_STATE_CONFIG
+	    && dev_state <= NM_DEVICE_STATE_ACTIVATED)
+		iwd_connection = TRUE;
+
+	/* Don't allow scanning while connecting, disconnecting or roaming */
+	priv->can_scan = NM_IN_STRSET (new_state, "connected", "disconnected");
+
+	/* Don't allow new connection until iwd exits disconnecting */
+	can_connect = NM_IN_STRSET (new_state, "disconnected");
+	if (can_connect != priv->can_connect) {
+		priv->can_connect = can_connect;
+		nm_device_emit_recheck_auto_activate (device);
+	}
+
+	if (NM_IN_STRSET (new_state, "connecting", "connected", "roaming")) {
+		/* If we were connecting, do nothing, the confirmation of
+		 * a connection success is handled in the Device.Connect
+		 * method return callback.  Otherwise IWD must have connected
+		 * without Network Manager's will so for simplicity force a
+		 * disconnect.
+		 */
+		if (iwd_connection)
+			return;
+
+		_LOGW (LOGD_DEVICE | LOGD_WIFI,
+		       "Unsolicited connection success, asking IWD to disconnect");
+		send_disconnect (self);
+
+		return;
+	} else if (NM_IN_STRSET (new_state, "disconnecting", "disconnected")) {
+		if (!iwd_connection)
+			return;
+
+		/* Call Disconnect on the IWD device object to make sure it
+		 * disables its own autoconnect.
+		 *
+		 * Note we could instead call net.connman.iwd.KnownNetworks.ForgetNetwork
+		 * and leave the device in autoconnect.  This way if NetworkManager
+		 * changes any settings for this connection, they'd be taken into
+		 * account on the next connection attempt.  But both methods are
+		 * a hack, we'll perhaps need an IWD API to "connect once" without
+		 * storing anything.
+		 */
+		send_disconnect (self);
+
+		/*
+		 * If IWD is still handling the Connect call, let our callback
+		 * for the dbus method handle the failure.
+		 */
+		if (dev_state == NM_DEVICE_STATE_CONFIG)
+			return;
+
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+	                                 NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+
+		return;
+	}
+
+	_LOGE (LOGD_WIFI, "State %s unknown", new_state);
+}
+
+static void
+scanning_changed (NMDeviceIwd *self, gboolean new_scanning)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
+
+	if (new_scanning == priv->scanning)
+		return;
+
+	priv->scanning = new_scanning;
+
+	_notify (self, PROP_SCANNING);
+
+	if (!priv->scanning) {
+		update_aps (self);
+
+		if (!priv->scan_requested)
+			schedule_periodic_scan (self, state);
+	}
+}
+
+static void
+powered_changed (NMDeviceIwd *self, gboolean new_powered)
+{
+	nm_device_queue_recheck_available (NM_DEVICE (self),
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+}
+
+static void
+properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
+                    GStrv invalidate_properties, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	GVariantIter *iter;
+	const gchar *key;
+	GVariant *value;
+
+	g_variant_get (changed_properties, "a{sv}", &iter);
+	while (g_variant_iter_next (iter, "{&sv}", &key, &value)) {
+		if (!strcmp (key, "State"))
+			state_changed (self, g_variant_get_string (value, NULL));
+
+		if (!strcmp (key, "Scanning"))
+			scanning_changed (self, g_variant_get_boolean (value));
+
+		if (!strcmp (key, "Powered"))
+			powered_changed (self, g_variant_get_boolean (value));
+
+		g_variant_unref (value);
+	}
+
+	g_variant_iter_free (iter);
+}
+
+void
+nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	GDBusInterface *interface;
+	GVariant *value;
+
+	if (!nm_g_object_ref_set ((GObject **) &priv->dbus_obj, (GObject *) object))
+		return;
+
+	if (priv->dbus_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_proxy,
+		                                      properties_changed, self);
+
+		g_clear_object (&priv->dbus_proxy);
+	}
+
+	if (priv->enabled)
+		nm_device_queue_recheck_available (NM_DEVICE (self),
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+
+	if (!object) {
+		priv->can_scan = FALSE;
+
+		cleanup_association_attempt (self, FALSE);
+		return;
+	}
+
+	interface = g_dbus_object_get_interface (object, NM_IWD_DEVICE_INTERFACE);
+	priv->dbus_proxy = G_DBUS_PROXY (interface);
+
+	value = g_dbus_proxy_get_cached_property (priv->dbus_proxy, "Scanning");
+	priv->scanning = g_variant_get_boolean (value);
+	g_variant_unref (value);
+	priv->scan_requested = FALSE;
+
+	value = g_dbus_proxy_get_cached_property (priv->dbus_proxy, "State");
+	state_changed (self, g_variant_get_string (value, NULL));
+	g_variant_unref (value);
+
+	g_signal_connect (priv->dbus_proxy, "g-properties-changed",
+	                  G_CALLBACK (properties_changed), self);
+
+	set_powered (self, priv->enabled);
+
+	/* Call Disconnect to make sure IWD's autoconnect is disabled.
+	 * Autoconnect is the default state after device is brought UP.
+	 */
+	if (priv->enabled)
+		send_disconnect (self);
+}
+
+gboolean
+nm_device_iwd_agent_psk_query (NMDeviceIwd *self,
+                               GDBusMethodInvocation *invocation)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMActRequest *req;
+	NMSettingWirelessSecurity *s_wireless_sec;
+	const gchar *psk;
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	if (!req)
+		return FALSE;
+
+	s_wireless_sec = nm_connection_get_setting_wireless_security (nm_act_request_get_applied_connection (req));
+	if (!s_wireless_sec)
+		return FALSE;
+
+	psk = nm_setting_wireless_security_get_psk (s_wireless_sec);
+	if (psk) {
+		_LOGD (LOGD_DEVICE | LOGD_WIFI,
+		       "Returning the PSK to the IWD Agent");
+
+		g_dbus_method_invocation_return_value (invocation,
+		                                       g_variant_new ("(s)", psk));
+		return TRUE;
+	}
+
+	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH,
+	                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+	wifi_secrets_get_secrets (self,
+	                          NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+	                          NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+	                            | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
+
+	priv->secrets_request = invocation;
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	nm_assert (NM_IS_DEVICE_IWD (device));
+
+	return "wifi";
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_iwd_init (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	c_list_init (&priv->aps_lst_head);
+
+	/* Make sure the manager is running */
+	(void) nm_iwd_manager_get ();
+}
+
+NMDevice *
+nm_device_iwd_new (const char *iface, NMDeviceWifiCapabilities capabilities)
+{
+	return g_object_new (NM_TYPE_DEVICE_IWD,
+	                     NM_DEVICE_IFACE, iface,
+	                     NM_DEVICE_TYPE_DESC, "802.11 WiFi",
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI,
+	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI,
+	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
+	                     NM_DEVICE_IWD_CAPABILITIES, (guint) capabilities,
+	                     NULL);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (object);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	nm_clear_g_cancellable (&priv->cancellable);
+
+	nm_clear_g_source (&priv->periodic_scan_id);
+
+	cleanup_association_attempt (self, TRUE);
+
+	g_clear_object (&priv->dbus_proxy);
+	g_clear_object (&priv->dbus_obj);
+
+	remove_all_aps (self);
+
+	G_OBJECT_CLASS (nm_device_iwd_parent_class)->dispose (object);
+
+	nm_assert (c_list_is_empty (&priv->aps_lst_head));
+}
+
+static void
+nm_device_iwd_class_init (NMDeviceIwdClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
+
+	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_WIRELESS_SETTING_NAME, NM_LINK_TYPE_WIFI)
+
+	object_class->get_property = get_property;
+	object_class->set_property = set_property;
+	object_class->dispose = dispose;
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&nm_interface_info_device_wireless);
+
+	parent_class->can_auto_connect = can_auto_connect;
+	parent_class->is_available = is_available;
+	parent_class->get_autoconnect_allowed = get_autoconnect_allowed;
+	parent_class->check_connection_compatible = check_connection_compatible;
+	parent_class->check_connection_available = check_connection_available;
+	parent_class->complete_connection = complete_connection;
+	parent_class->get_enabled = get_enabled;
+	parent_class->set_enabled = set_enabled;
+	parent_class->get_type_description = get_type_description;
+
+	parent_class->act_stage1_prepare = act_stage1_prepare;
+	parent_class->act_stage2_config = act_stage2_config;
+	parent_class->get_configured_mtu = get_configured_mtu;
+	parent_class->deactivate = deactivate;
+	parent_class->deactivate_async = deactivate_async;
+	parent_class->deactivate_async_finish = deactivate_async_finish;
+	parent_class->can_reapply_change = can_reapply_change;
+
+	parent_class->state_changed = device_state_changed;
+
+	klass->scanning_prohibited = scanning_prohibited;
+
+	obj_properties[PROP_MODE] =
+	    g_param_spec_uint (NM_DEVICE_IWD_MODE, "", "",
+	                       NM_802_11_MODE_UNKNOWN,
+	                       NM_802_11_MODE_AP,
+	                       NM_802_11_MODE_INFRA,
+	                       G_PARAM_READABLE |
+	                       G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_BITRATE] =
+	    g_param_spec_uint (NM_DEVICE_IWD_BITRATE, "", "",
+	                       0, G_MAXUINT32, 0,
+	                       G_PARAM_READABLE |
+	                       G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_ACCESS_POINTS] =
+	    g_param_spec_boxed (NM_DEVICE_IWD_ACCESS_POINTS, "", "",
+	                        G_TYPE_STRV,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_ACTIVE_ACCESS_POINT] =
+	    g_param_spec_string (NM_DEVICE_IWD_ACTIVE_ACCESS_POINT, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_CAPABILITIES] =
+	    g_param_spec_uint (NM_DEVICE_IWD_CAPABILITIES, "", "",
+	                       0, G_MAXUINT32, NM_WIFI_DEVICE_CAP_NONE,
+	                       G_PARAM_READWRITE |
+	                       G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_SCANNING] =
+	    g_param_spec_boolean (NM_DEVICE_IWD_SCANNING, "", "",
+	                          FALSE,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+	signals[SCANNING_PROHIBITED] =
+	    g_signal_new (NM_DEVICE_IWD_SCANNING_PROHIBITED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  G_STRUCT_OFFSET (NMDeviceIwdClass, scanning_prohibited),
+	                  NULL, NULL, NULL,
+	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
+}
diff --git a/src/devices/wifi/nm-device-iwd.h b/src/devices/wifi/nm-device-iwd.h
new file mode 100644
index 00000000..699ba1bb
--- /dev/null
+++ b/src/devices/wifi/nm-device-iwd.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2017 Intel Corporation
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_IWD_H__
+#define __NETWORKMANAGER_DEVICE_IWD_H__
+
+#include "devices/nm-device.h"
+#include "nm-wifi-ap.h"
+#include "nm-device-wifi.h"
+
+#define NM_TYPE_DEVICE_IWD              (nm_device_iwd_get_type ())
+#define NM_DEVICE_IWD(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_IWD, NMDeviceIwd))
+#define NM_DEVICE_IWD_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_IWD, NMDeviceIwdClass))
+#define NM_IS_DEVICE_IWD(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_IWD))
+#define NM_IS_DEVICE_IWD_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_IWD))
+#define NM_DEVICE_IWD_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_IWD, NMDeviceIwdClass))
+
+#define NM_DEVICE_IWD_MODE                NM_DEVICE_WIFI_MODE
+#define NM_DEVICE_IWD_BITRATE             NM_DEVICE_WIFI_BITRATE
+#define NM_DEVICE_IWD_ACCESS_POINTS       NM_DEVICE_WIFI_ACCESS_POINTS
+#define NM_DEVICE_IWD_ACTIVE_ACCESS_POINT NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT
+#define NM_DEVICE_IWD_CAPABILITIES        NM_DEVICE_WIFI_CAPABILITIES
+#define NM_DEVICE_IWD_SCANNING            NM_DEVICE_WIFI_SCANNING
+
+#define NM_DEVICE_IWD_SCANNING_PROHIBITED  NM_DEVICE_WIFI_SCANNING_PROHIBITED
+
+typedef struct _NMDeviceIwd NMDeviceIwd;
+typedef struct _NMDeviceIwdClass NMDeviceIwdClass;
+
+GType nm_device_iwd_get_type (void);
+
+NMDevice *nm_device_iwd_new (const char *iface, NMDeviceWifiCapabilities capabilities);
+
+void nm_device_iwd_set_dbus_object (NMDeviceIwd *device, GDBusObject *object);
+
+gboolean nm_device_iwd_agent_psk_query (NMDeviceIwd *device,
+                                        GDBusMethodInvocation *invocation);
+
+const CList *_nm_device_iwd_get_aps (NMDeviceIwd *self);
+
+void _nm_device_iwd_request_scan (NMDeviceIwd *self,
+                                  GVariant *options,
+                                  GDBusMethodInvocation *invocation);
+
+#endif /* __NETWORKMANAGER_DEVICE_IWD_H__ */
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 3a4a027a..cd2c68af 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -48,11 +48,6 @@
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
 
-/* This is a bug; but we can't really change API now... */
-#include "nm-vpn-dbus-interface.h"
-
-#include "introspection/org.freedesktop.NetworkManager.Device.OlpcMesh.h"
-
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceOlpcMesh);
 
@@ -118,7 +113,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingOlpcMesh *s_mesh;
@@ -392,7 +387,8 @@ static void
 find_companion (NMDeviceOlpcMesh *self)
 {
 	NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self);
-	const GSList *list;
+	const CList *tmp_lst;
+	NMDevice *candidate;
 
 	if (priv->companion)
 		return;
@@ -400,8 +396,8 @@ find_companion (NMDeviceOlpcMesh *self)
 	nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_COMPANION, TRUE);
 
 	/* Try to find the companion if it's already known to the NMManager */
-	for (list = nm_manager_get_devices (priv->manager); list ; list = g_slist_next (list)) {
-		if (check_companion (self, NM_DEVICE (list->data))) {
+	nm_manager_for_each_device (priv->manager, candidate, tmp_lst) {
+		if (check_companion (self, candidate)) {
 			nm_device_queue_recheck_available (NM_DEVICE (self),
 			                                   NM_DEVICE_STATE_REASON_NONE,
 			                                   NM_DEVICE_STATE_REASON_NONE);
@@ -440,7 +436,7 @@ get_property (GObject *object, guint prop_id,
 
 	switch (prop_id) {
 	case PROP_COMPANION:
-		nm_utils_g_value_set_object_path (value, priv->companion);
+		nm_dbus_utils_g_value_set_object_path (value, priv->companion);
 		break;
 	case PROP_ACTIVE_CHANNEL:
 		g_value_set_uint (value, nm_platform_mesh_get_channel (nm_device_get_platform (device), nm_device_get_ifindex (device)));
@@ -500,10 +496,26 @@ dispose (GObject *object)
 	G_OBJECT_CLASS (nm_device_olpc_mesh_parent_class)->dispose (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_olpc_mesh = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_OLPC_MESH,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",     "s", NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Companion",     "o", NM_DEVICE_OLPC_MESH_COMPANION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ActiveChannel", "u", NM_DEVICE_OLPC_MESH_ACTIVE_CHANNEL),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_OLPC_MESH_SETTING_NAME, NM_LINK_TYPE_OLPC_MESH)
@@ -512,6 +524,8 @@ nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass)
 	object_class->get_property = get_property;
 	object_class->dispose = dispose;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_olpc_mesh);
+
 	parent_class->check_connection_compatible = check_connection_compatible;
 	parent_class->get_autoconnect_allowed = get_autoconnect_allowed;
 	parent_class->complete_connection = complete_connection;
@@ -534,9 +548,5 @@ nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass)
 	                        G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_OLPC_MESH_SKELETON,
-	                                        NULL);
 }
 
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index bf021095..de4af42c 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -28,9 +28,11 @@
 #include <unistd.h>
 #include <errno.h>
 
+#include "nm-wifi-ap.h"
 #include "nm-common-macros.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-private.h"
+#include "nm-dbus-manager.h"
 #include "nm-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-act-request.h"
@@ -48,11 +50,11 @@
 #include "nm-auth-utils.h"
 #include "settings/nm-settings-connection.h"
 #include "settings/nm-settings.h"
+#include "nm-wifi-utils.h"
+#include "nm-wifi-common.h"
 #include "nm-core-internal.h"
 #include "nm-config.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Wireless.h"
-
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceWifi);
 
@@ -75,8 +77,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 );
 
 enum {
-	ACCESS_POINT_ADDED,
-	ACCESS_POINT_REMOVED,
 	SCANNING_PROHIBITED,
 
 	LAST_SIGNAL
@@ -87,7 +87,8 @@ static guint signals[LAST_SIGNAL] = { 0 };
 typedef struct {
 	gint8             invalid_strength_counter;
 
-	GHashTable *      aps;
+	CList             aps_lst_head;
+
 	NMWifiAP *        current_ap;
 	guint32           rate;
 	bool              enabled:1; /* rfkilled or not */
@@ -187,7 +188,7 @@ static void request_wireless_scan (NMDeviceWifi *self,
                                    const GPtrArray *ssids);
 
 static void ap_add_remove (NMDeviceWifi *self,
-                           guint signum,
+                           gboolean is_adding,
                            NMWifiAP *ap,
                            gboolean recheck_available_connections);
 
@@ -345,30 +346,6 @@ supplicant_interface_release (NMDeviceWifi *self)
 	_notify_scanning (self);
 }
 
-static NMWifiAP *
-get_ap_by_path (NMDeviceWifi *self, const char *path)
-{
-	g_return_val_if_fail (path != NULL, NULL);
-	return g_hash_table_lookup (NM_DEVICE_WIFI_GET_PRIVATE (self)->aps, path);
-
-}
-
-static NMWifiAP *
-get_ap_by_supplicant_path (NMDeviceWifi *self, const char *path)
-{
-	GHashTableIter iter;
-	NMWifiAP *ap;
-
-	g_return_val_if_fail (path != NULL, NULL);
-
-	g_hash_table_iter_init (&iter, NM_DEVICE_WIFI_GET_PRIVATE (self)->aps);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &ap)) {
-		if (g_strcmp0 (path, nm_wifi_ap_get_supplicant_path (ap)) == 0)
-			return ap;
-	}
-	return NULL;
-}
-
 static void
 update_seen_bssids_cache (NMDeviceWifi *self, NMWifiAP *ap)
 {
@@ -415,7 +392,7 @@ set_current_ap (NMDeviceWifi *self, NMWifiAP *new_ap, gboolean recheck_available
 
 		/* Remove any AP from the internal list if it was created by NM or isn't known to the supplicant */
 		if (mode == NM_802_11_MODE_ADHOC || mode == NM_802_11_MODE_AP || nm_wifi_ap_get_fake (old_ap))
-			ap_add_remove (self, ACCESS_POINT_REMOVED, old_ap, recheck_available_connections);
+			ap_add_remove (self, FALSE, old_ap, recheck_available_connections);
 		g_object_unref (old_ap);
 	}
 
@@ -482,32 +459,32 @@ periodic_update_cb (gpointer user_data)
 
 static void
 ap_add_remove (NMDeviceWifi *self,
-               guint signum,
+               gboolean is_adding, /* or else removing */
                NMWifiAP *ap,
                gboolean recheck_available_connections)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	nm_assert (NM_IN_SET (signum, ACCESS_POINT_ADDED, ACCESS_POINT_REMOVED));
-
-	if (signum == ACCESS_POINT_ADDED) {
-		g_hash_table_insert (priv->aps,
-		                     (gpointer) nm_exported_object_export ((NMExportedObject *) ap),
-		                     g_object_ref (ap));
+	if (is_adding) {
+		g_object_ref (ap);
+		ap->wifi_device = NM_DEVICE (self);
+		c_list_link_tail (&priv->aps_lst_head, &ap->aps_lst);
+		nm_dbus_object_export (NM_DBUS_OBJECT (ap));
 		_ap_dump (self, LOGL_DEBUG, ap, "added", 0);
-	} else
+		nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, TRUE);
+	} else {
+		ap->wifi_device = NULL;
+		c_list_unlink (&ap->aps_lst);
 		_ap_dump (self, LOGL_DEBUG, ap, "removed", 0);
-
-	g_signal_emit (self, signals[signum], 0, ap);
-
-	if (signum == ACCESS_POINT_REMOVED) {
-		g_hash_table_remove (priv->aps, nm_exported_object_get_path ((NMExportedObject *) ap));
-		nm_exported_object_unexport ((NMExportedObject *) ap);
-		g_object_unref (ap);
 	}
 
 	_notify (self, PROP_ACCESS_POINTS);
 
+	if (!is_adding) {
+		nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, FALSE);
+		nm_dbus_object_clear_and_unexport (&ap);
+	}
+
 	nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
 	if (recheck_available_connections)
 		nm_device_recheck_available_connections (NM_DEVICE (self));
@@ -517,20 +494,15 @@ static void
 remove_all_aps (NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	GHashTableIter iter;
 	NMWifiAP *ap;
 
-	if (!g_hash_table_size (priv->aps))
+	if (c_list_is_empty (&priv->aps_lst_head))
 		return;
 
 	set_current_ap (self, NULL, FALSE);
 
-again:
-	g_hash_table_iter_init (&iter, priv->aps);
-	if (g_hash_table_iter_next (&iter, NULL, (gpointer) &ap)) {
-		ap_add_remove (self, ACCESS_POINT_REMOVED, ap, FALSE);
-		goto again;
-	}
+	while ((ap = c_list_first_entry (&priv->aps_lst_head, NMWifiAP, aps_lst)))
+		ap_add_remove (self, FALSE, ap, FALSE);
 
 	nm_device_recheck_available_connections (NM_DEVICE (self));
 }
@@ -680,35 +652,14 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 	return TRUE;
 }
 
-static NMWifiAP *
-find_first_compatible_ap (NMDeviceWifi *self,
-                          NMConnection *connection,
-                          gboolean allow_unstable_order)
-{
-	GHashTableIter iter;
-	NMWifiAP *ap;
-	NMWifiAP *cand_ap = NULL;
-
-	g_return_val_if_fail (connection != NULL, NULL);
-
-	g_hash_table_iter_init (&iter, NM_DEVICE_WIFI_GET_PRIVATE (self)->aps);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &ap)) {
-		if (!nm_wifi_ap_check_compatible (ap, connection))
-			continue;
-		if (allow_unstable_order)
-			return ap;
-		if (!cand_ap || (nm_wifi_ap_get_id (cand_ap) < nm_wifi_ap_get_id (ap)))
-			cand_ap = ap;
-	}
-	return cand_ap;
-}
-
 static gboolean
 check_connection_available (NMDevice *device,
                             NMConnection *connection,
                             NMDeviceCheckConAvailableFlags flags,
                             const char *specific_object)
 {
+	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
 	const char *mode;
 
@@ -721,7 +672,7 @@ check_connection_available (NMDevice *device,
 	if (specific_object) {
 		NMWifiAP *ap;
 
-		ap = get_ap_by_path (NM_DEVICE_WIFI (device), specific_object);
+		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
 		return ap ? nm_wifi_ap_check_compatible (ap, connection) : FALSE;
 	}
 
@@ -745,50 +696,18 @@ check_connection_available (NMDevice *device,
 		return TRUE;
 
 	/* check at least one AP is compatible with this connection */
-	return !!find_first_compatible_ap (NM_DEVICE_WIFI (device), connection, TRUE);
-}
-
-static gboolean
-is_manf_default_ssid (const GByteArray *ssid)
-{
-	int i;
-	/*
-	 * List of manufacturer default SSIDs that are often unchanged by users.
-	 *
-	 * NOTE: this list should *not* contain networks that you would like to
-	 * automatically roam to like "Starbucks" or "AT&T" or "T-Mobile HotSpot".
-	 */
-	static const char *manf_defaults[] = {
-		"linksys",
-		"linksys-a",
-		"linksys-g",
-		"default",
-		"belkin54g",
-		"NETGEAR",
-		"o2DSL",
-		"WLAN",
-		"ALICE-WLAN",
-		"Speedport W 501V",
-		"TURBONETT",
-	};
-
-	for (i = 0; i < G_N_ELEMENTS (manf_defaults); i++) {
-		if (ssid->len == strlen (manf_defaults[i])) {
-			if (memcmp (manf_defaults[i], ssid->data, ssid->len) == 0)
-				return TRUE;
-		}
-	}
-	return FALSE;
+	return !!nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 }
 
 static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
 	const char *setting_mac;
 	char *str_ssid = NULL;
@@ -825,7 +744,7 @@ complete_connection (NMDevice *device,
 
 		if (!nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) {
 			/* Find a compatible AP in the scan list */
-			ap = find_first_compatible_ap (self, connection, FALSE);
+			ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 
 			/* If we still don't have an AP, then the WiFI settings needs to be
 			 * fully specified by the client.  Might not be able to find an AP
@@ -847,7 +766,7 @@ complete_connection (NMDevice *device,
 			return FALSE;
 		ap = NULL;
 	} else {
-		ap = get_ap_by_path (self, specific_object);
+		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
 		if (!ap) {
 			g_set_error (error,
 			             NM_DEVICE_ERROR,
@@ -900,7 +819,7 @@ complete_connection (NMDevice *device,
 		 */
 		if (!nm_wifi_ap_complete_connection (ap,
 		                                     connection,
-		                                     is_manf_default_ssid (ssid),
+		                                     nm_wifi_utils_is_manf_default_ssid (ssid),
 		                                     error)) {
 			if (tmp_ssid)
 				g_byte_array_unref (tmp_ssid);
@@ -1007,6 +926,7 @@ can_auto_connect (NMDevice *device,
                   char **specific_object)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
 	NMWifiAP *ap;
 	const char *method, *mode;
@@ -1038,102 +958,20 @@ can_auto_connect (NMDevice *device,
 			return FALSE;
 	}
 
-	ap = find_first_compatible_ap (self, connection, FALSE);
+	ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 	if (ap) {
 		/* All good; connection is usable */
-		NM_SET_OUT (specific_object, g_strdup (nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap))));
+		NM_SET_OUT (specific_object, g_strdup (nm_dbus_object_get_path (NM_DBUS_OBJECT (ap))));
 		return TRUE;
 	}
 
 	return FALSE;
 }
 
-static int
-ap_id_compare (gconstpointer p_a, gconstpointer p_b, gpointer user_data)
-{
-	guint64 a_id = nm_wifi_ap_get_id (*((NMWifiAP **) p_a));
-	guint64 b_id = nm_wifi_ap_get_id (*((NMWifiAP **) p_b));
-
-	return a_id < b_id ? -1 : (a_id == b_id ? 0 : 1);
-}
-
-static NMWifiAP **
-ap_list_get_sorted (NMDeviceWifi *self, gboolean include_without_ssid)
-{
-	NMDeviceWifiPrivate *priv;
-	NMWifiAP **list;
-	GHashTableIter iter;
-	NMWifiAP *ap;
-	gsize i, n;
-
-	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-
-	n = g_hash_table_size (priv->aps);
-	list = g_new (NMWifiAP *, n + 1);
-
-	i = 0;
-	if (n > 0) {
-		g_hash_table_iter_init (&iter, priv->aps);
-		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &ap)) {
-			nm_assert (i < n);
-			if (   include_without_ssid
-			    || nm_wifi_ap_get_ssid (ap))
-				list[i++] = ap;
-		}
-		nm_assert (i <= n);
-		nm_assert (!include_without_ssid || i == n);
-
-		g_qsort_with_data (list,
-		                   i,
-		                   sizeof (gpointer),
-		                   ap_id_compare,
-		                   NULL);
-	}
-	list[i] = NULL;
-	return list;
-}
-
-static const char **
-ap_list_get_sorted_paths (NMDeviceWifi *self, gboolean include_without_ssid)
+const CList *
+_nm_device_wifi_get_aps (NMDeviceWifi *self)
 {
-	gpointer *list;
-	gsize i, j;
-
-	list = (gpointer *) ap_list_get_sorted (self, include_without_ssid);
-	for (i = 0, j = 0; list[i]; i++) {
-		NMWifiAP *ap = list[i];
-		const char *path;
-
-		/* update @list inplace to hold instead the export-path. */
-		path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap));
-		nm_assert (path);
-		list[j++] = (gpointer) path;
-	}
-	return (const char **) list;
-}
-
-static void
-impl_device_wifi_get_access_points (NMDeviceWifi *self,
-                                    GDBusMethodInvocation *context)
-{
-	gs_free const char **list = NULL;
-	GVariant *v;
-
-	list = ap_list_get_sorted_paths (self, FALSE);
-	v = g_variant_new_objv (list, -1);
-	g_dbus_method_invocation_return_value (context, g_variant_new_tuple (&v, 1));
-}
-
-static void
-impl_device_wifi_get_all_access_points (NMDeviceWifi *self,
-                                        GDBusMethodInvocation *context)
-{
-	gs_free const char **list = NULL;
-	GVariant *v;
-
-	list = ap_list_get_sorted_paths (self, TRUE);
-	v = g_variant_new_objv (list, -1);
-	g_dbus_method_invocation_return_value (context, g_variant_new_tuple (&v, 1));
+	return &NM_DEVICE_WIFI_GET_PRIVATE (self)->aps_lst_head;
 }
 
 static void
@@ -1153,7 +991,7 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
 	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	randomize = nm_config_data_get_device_config_boolean (NM_CONFIG_GET_DATA,
-	                                                      "wifi.scan-rand-mac-address",
+	                                                      NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS,
 	                                                      device,
 	                                                      TRUE, TRUE);
 
@@ -1239,7 +1077,6 @@ dbus_request_scan_cb (NMDevice *device,
                       gpointer user_data)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
-	NMDeviceWifiPrivate *priv;
 	gs_unref_variant GVariant *scan_options = user_data;
 	gs_unref_ptrarray GPtrArray *ssids = NULL;
 
@@ -1256,8 +1093,6 @@ dbus_request_scan_cb (NMDevice *device,
 		return;
 	}
 
-	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-
 	if (scan_options) {
 		gs_unref_variant GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
 
@@ -1284,10 +1119,10 @@ dbus_request_scan_cb (NMDevice *device,
 	g_dbus_method_invocation_return_value (context, NULL);
 }
 
-static void
-impl_device_wifi_request_scan (NMDeviceWifi *self,
-                               GDBusMethodInvocation *context,
-                               GVariant *options)
+void
+_nm_device_wifi_request_scan (NMDeviceWifi *self,
+                              GVariant *options,
+                              GDBusMethodInvocation *invocation)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
@@ -1297,7 +1132,7 @@ impl_device_wifi_request_scan (NMDeviceWifi *self,
 	    || !priv->sup_iface
 	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
 	    || nm_device_is_activating (device)) {
-		g_dbus_method_invocation_return_error_literal (context,
+		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
 		                                               "Scanning not allowed while unavailable or activating");
@@ -1305,7 +1140,7 @@ impl_device_wifi_request_scan (NMDeviceWifi *self,
 	}
 
 	if (nm_supplicant_interface_get_scanning (priv->sup_iface)) {
-		g_dbus_method_invocation_return_error_literal (context,
+		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
 		                                               "Scanning not allowed while already scanning");
@@ -1314,17 +1149,16 @@ impl_device_wifi_request_scan (NMDeviceWifi *self,
 
 	last_scan = nm_supplicant_interface_get_last_scan_time (priv->sup_iface);
 	if (last_scan && (nm_utils_get_monotonic_timestamp_s () - last_scan) < 10) {
-		g_dbus_method_invocation_return_error_literal (context,
+		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
 		                                               "Scanning not allowed immediately following previous scan");
 		return;
 	}
 
-	/* Ask the manager to authenticate this request for us */
 	g_signal_emit_by_name (device,
 	                       NM_DEVICE_AUTH_REQUEST,
-	                       context,
+	                       invocation,
 	                       NULL,
 	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	                       TRUE,
@@ -1608,17 +1442,15 @@ ap_list_dump (gpointer user_data)
 	priv->ap_dump_id = 0;
 
 	if (_LOGD_ENABLED (LOGD_WIFI_SCAN)) {
-		gs_free NMWifiAP **list = NULL;
-		gsize i;
+		NMWifiAP *ap;
 		gint32 now_s = nm_utils_get_monotonic_timestamp_s ();
 
 		_LOGD (LOGD_WIFI_SCAN, "APs: [now:%u last:%u next:%u]",
 		       now_s,
 		       priv->last_scan,
 		       priv->scheduled_scan_time);
-		list = ap_list_get_sorted (self, TRUE);
-		for (i = 0; list[i]; i++)
-			_ap_dump (self, LOGL_DEBUG, list[i], "dump", now_s);
+		c_list_for_each_entry (ap, &priv->aps_lst_head, aps_lst)
+			_ap_dump (self, LOGL_DEBUG, ap, "dump", now_s);
 	}
 	return G_SOURCE_REMOVE;
 }
@@ -1689,7 +1521,7 @@ supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
 	if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP)
 		return;
 
-	found_ap = get_ap_by_supplicant_path (self, object_path);
+	found_ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, object_path);
 	if (found_ap) {
 		if (!nm_wifi_ap_update_from_properties (found_ap, object_path, properties))
 			return;
@@ -1721,7 +1553,7 @@ supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
 			}
 		}
 
-		ap_add_remove (self, ACCESS_POINT_ADDED, ap, TRUE);
+		ap_add_remove (self, TRUE, ap, TRUE);
 	}
 
 	/* Update the current AP if the supplicant notified a current BSS change
@@ -1745,7 +1577,7 @@ supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface,
 	g_return_if_fail (object_path != NULL);
 
 	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	ap = get_ap_by_supplicant_path (self, object_path);
+	ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, object_path);
 	if (!ap)
 		return;
 
@@ -1758,7 +1590,7 @@ supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface,
 		if (nm_wifi_ap_set_fake (ap, TRUE))
 			_ap_dump (self, LOGL_DEBUG, ap, "updated", 0);
 	} else {
-		ap_add_remove (self, ACCESS_POINT_REMOVED, ap, TRUE);
+		ap_add_remove (self, FALSE, ap, TRUE);
 		schedule_ap_list_dump (self);
 	}
 }
@@ -2289,7 +2121,7 @@ supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
 
 	current_bss = nm_supplicant_interface_get_current_bss (iface);
 	if (current_bss)
-		new_ap = get_ap_by_supplicant_path (self, current_bss);
+		new_ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, current_bss);
 
 	if (new_ap != priv->current_ap) {
 		const char *new_bssid = NULL;
@@ -2497,6 +2329,7 @@ build_supplicant_config (NMDeviceWifi *self,
 	NMSettingWireless *s_wireless;
 	NMSettingWirelessSecurity *s_wireless_sec;
 	NMSettingWirelessSecurityPmf pmf;
+	NMSettingWirelessSecurityFils fils;
 	gs_free char *value = NULL;
 
 	g_return_val_if_fail (priv->sup_iface, NULL);
@@ -2504,7 +2337,9 @@ build_supplicant_config (NMDeviceWifi *self,
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless != NULL, NULL);
 
-	config = nm_supplicant_config_new ();
+	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);
 
 	/* Warn if AP mode may not be supported */
 	if (   g_strcmp0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP) == 0
@@ -2546,24 +2381,16 @@ build_supplicant_config (NMDeviceWifi *self,
 			                                    NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL);
 		}
 
-		/* Don't try to enable PMF on non-WPA networks */
-		if (!NM_IN_STRSET (nm_setting_wireless_security_get_key_mgmt (s_wireless_sec),
-		                   "wpa-eap",
-		                   "wpa-psk"))
-			pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
-
-		/* Check if we actually support PMF */
-		if (nm_supplicant_interface_get_pmf_support (priv->sup_iface) != NM_SUPPLICANT_FEATURE_YES) {
-			if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
-				g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
-				                     "Supplicant does not support PMF");
-				goto error;
-			} else if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL) {
-				/* To be on the safe side, assume no support if we can't determine
-				 * capabilities.
-				 */
-				pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
-			}
+		/* Configure FILS (802.11ai) */
+		fils = nm_setting_wireless_security_get_fils (s_wireless_sec);
+		if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT) {
+			value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+			                                               "wifi-sec.fils",
+			                                               NM_DEVICE (self));
+			fils = _nm_utils_ascii_str_to_int64 (value, 10,
+			                                     NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE,
+			                                     NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED,
+			                                     NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL);
 		}
 
 		s_8021x = nm_connection_get_setting_802_1x (connection);
@@ -2573,6 +2400,7 @@ build_supplicant_config (NMDeviceWifi *self,
 		                                                         con_uuid,
 		                                                         mtu,
 		                                                         pmf,
+		                                                         fils,
 		                                                         error)) {
 			g_prefix_error (error, "802-11-wireless-security: ");
 			goto error;
@@ -2654,16 +2482,16 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	/* AP mode never uses a specific object or existing scanned AP */
 	if (priv->mode != NM_802_11_MODE_AP) {
 		ap_path = nm_active_connection_get_specific_object (NM_ACTIVE_CONNECTION (req));
-		ap = ap_path ? get_ap_by_path (self, ap_path) : NULL;
+		ap = ap_path ? nm_wifi_ap_lookup_for_device (NM_DEVICE (self), ap_path) : NULL;
 		if (ap)
 			goto done;
 
-		ap = find_first_compatible_ap (self, connection, FALSE);
+		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 	}
 
 	if (ap) {
 		nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
-		                                          nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap)));
+		                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
 		goto done;
 	}
 
@@ -2680,11 +2508,11 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		nm_wifi_ap_set_address (ap, nm_device_get_hw_address (device));
 
 	g_object_freeze_notify (G_OBJECT (self));
-	ap_add_remove (self, ACCESS_POINT_ADDED, ap, TRUE);
+	ap_add_remove (self, TRUE, ap, TRUE);
 	g_object_thaw_notify (G_OBJECT (self));
 	set_current_ap (self, ap, FALSE);
 	nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
-	                                          nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap)));
+	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 
 done:
@@ -3055,13 +2883,10 @@ activation_success_handler (NMDevice *device)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	int ifindex = nm_device_get_ifindex (device);
 	NMActRequest *req;
-	NMConnection *applied_connection;
 
 	req = nm_device_get_act_request (device);
 	g_assert (req);
 
-	applied_connection = nm_act_request_get_applied_connection (req);
-
 	/* Clear any critical protocol notification in the wifi stack */
 	nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), ifindex, FALSE);
 
@@ -3099,7 +2924,7 @@ activation_success_handler (NMDevice *device)
 		}
 
 		nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
-		                                          nm_exported_object_get_path (NM_EXPORTED_OBJECT (priv->current_ap)));
+		                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (priv->current_ap)));
 	}
 
 	periodic_update (self);
@@ -3277,8 +3102,7 @@ get_property (GObject *object, guint prop_id,
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (object);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	gsize i;
-	char **list;
+	const char **list;
 
 	switch (prop_id) {
 	case PROP_MODE:
@@ -3291,13 +3115,11 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, priv->capabilities);
 		break;
 	case PROP_ACCESS_POINTS:
-		list = (char **) ap_list_get_sorted_paths (self, TRUE);
-		for (i = 0; list[i]; i++)
-			list[i] = g_strdup (list[i]);
-		g_value_take_boxed (value, list);
+		list = nm_wifi_aps_get_paths (&priv->aps_lst_head, TRUE);
+		g_value_take_boxed (value, nm_utils_strv_make_deep_copied (list));
 		break;
 	case PROP_ACTIVE_ACCESS_POINT:
-		nm_utils_g_value_set_object_path (value, priv->current_ap);
+		nm_dbus_utils_g_value_set_object_path (value, priv->current_ap);
 		break;
 	case PROP_SCANNING:
 		g_value_set_boolean (value, priv->is_scanning);
@@ -3333,8 +3155,9 @@ nm_device_wifi_init (NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
+	c_list_init (&priv->aps_lst_head);
+
 	priv->mode = NM_802_11_MODE_INFRA;
-	priv->aps = g_hash_table_new (nm_str_hash, g_str_equal);
 }
 
 static void
@@ -3392,9 +3215,7 @@ finalize (GObject *object)
 	NMDeviceWifi *self = NM_DEVICE_WIFI (object);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	nm_assert (g_hash_table_size (priv->aps) == 0);
-
-	g_hash_table_unref (priv->aps);
+	nm_assert (c_list_is_empty (&priv->aps_lst_head));
 
 	G_OBJECT_CLASS (nm_device_wifi_parent_class)->finalize (object);
 }
@@ -3403,6 +3224,7 @@ static void
 nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_WIRELESS_SETTING_NAME, NM_LINK_TYPE_WIFI)
@@ -3413,6 +3235,8 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&nm_interface_info_device_wireless);
+
 	parent_class->can_auto_connect = can_auto_connect;
 	parent_class->get_autoconnect_allowed = get_autoconnect_allowed;
 	parent_class->is_available = is_available;
@@ -3479,24 +3303,6 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-	signals[ACCESS_POINT_ADDED] =
-	    g_signal_new (NM_DEVICE_WIFI_ACCESS_POINT_ADDED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1,
-	                  NM_TYPE_WIFI_AP);
-
-	signals[ACCESS_POINT_REMOVED] =
-	    g_signal_new (NM_DEVICE_WIFI_ACCESS_POINT_REMOVED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1,
-	                  NM_TYPE_WIFI_AP);
-
 	signals[SCANNING_PROHIBITED] =
 	    g_signal_new (NM_DEVICE_WIFI_SCANNING_PROHIBITED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
@@ -3504,13 +3310,4 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_prohibited),
 	                  NULL, NULL, NULL,
 	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_WIFI_SKELETON,
-	                                        "GetAccessPoints", impl_device_wifi_get_access_points,
-	                                        "GetAllAccessPoints", impl_device_wifi_get_all_access_points,
-	                                        "RequestScan", impl_device_wifi_request_scan,
-	                                        NULL);
 }
-
-
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 09707d4f..c13b00de 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -23,7 +23,6 @@
 #define __NETWORKMANAGER_DEVICE_WIFI_H__
 
 #include "devices/nm-device.h"
-#include "nm-wifi-ap.h"
 
 #define NM_TYPE_DEVICE_WIFI             (nm_device_wifi_get_type ())
 #define NM_DEVICE_WIFI(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_WIFI, NMDeviceWifi))
@@ -39,11 +38,6 @@
 #define NM_DEVICE_WIFI_CAPABILITIES        "wireless-capabilities"
 #define NM_DEVICE_WIFI_SCANNING            "scanning"
 
-/* signals */
-#define NM_DEVICE_WIFI_ACCESS_POINT_ADDED  "access-point-added"
-#define NM_DEVICE_WIFI_ACCESS_POINT_REMOVED "access-point-removed"
-
-/* internal signals */
 #define NM_DEVICE_WIFI_SCANNING_PROHIBITED    "scanning-prohibited"
 
 typedef struct _NMDeviceWifi NMDeviceWifi;
@@ -53,4 +47,10 @@ GType nm_device_wifi_get_type (void);
 
 NMDevice * nm_device_wifi_new (const char *iface, NMDeviceWifiCapabilities capabilities);
 
+const CList *_nm_device_wifi_get_aps (NMDeviceWifi *self);
+
+void _nm_device_wifi_request_scan (NMDeviceWifi *self,
+                                   GVariant *options,
+                                   GDBusMethodInvocation *invocation);
+
 #endif /* __NETWORKMANAGER_DEVICE_WIFI_H__ */
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
new file mode 100644
index 00000000..450009f0
--- /dev/null
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -0,0 +1,694 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2017 Intel Corporation
+ */
+
+#include "nm-default.h"
+
+#include "nm-iwd-manager.h"
+
+#include <string.h>
+#include <net/if.h>
+
+#include "nm-logging.h"
+#include "nm-core-internal.h"
+#include "nm-manager.h"
+#include "nm-device-iwd.h"
+#include "nm-utils/nm-random-utils.h"
+
+/*****************************************************************************/
+
+typedef struct {
+	gchar *name;
+	NMIwdNetworkSecurity security;
+} KnownNetworkData;
+
+typedef struct {
+	NMManager *manager;
+	GCancellable *cancellable;
+	gboolean running;
+	GDBusObjectManager *object_manager;
+	guint agent_id;
+	gchar *agent_path;
+	GSList *known_networks;
+} NMIwdManagerPrivate;
+
+struct _NMIwdManager {
+	GObject parent;
+	NMIwdManagerPrivate _priv;
+};
+
+struct _NMIwdManagerClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMIwdManager, nm_iwd_manager, G_TYPE_OBJECT)
+
+#define NM_IWD_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMIwdManager, NM_IS_IWD_MANAGER)
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME                "iwd-manager"
+#define _NMLOG_DOMAIN                     LOGD_WIFI
+
+#define _NMLOG(level, ...) \
+	G_STMT_START { \
+		if (nm_logging_enabled (level, _NMLOG_DOMAIN)) { \
+			char __prefix[32]; \
+			\
+			if (self) \
+				g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", ""_NMLOG_PREFIX_NAME"", (self)); \
+			else \
+				g_strlcpy (__prefix, _NMLOG_PREFIX_NAME, sizeof (__prefix)); \
+			_nm_log ((level), (_NMLOG_DOMAIN), 0, NULL, NULL, \
+			          "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+			          __prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+		} \
+	} G_STMT_END
+
+/*****************************************************************************/
+
+static void
+psk_agent_dbus_method_cb (GDBusConnection *connection,
+                          const gchar *sender, const gchar *object_path,
+                          const gchar *interface_name, const gchar *method_name,
+                          GVariant *parameters,
+                          GDBusMethodInvocation *invocation,
+                          gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager);
+	const gchar *network_path, *device_path, *ifname;
+	gs_unref_object GDBusInterface *network = NULL, *device_obj = NULL;
+	gs_unref_variant GVariant *value = NULL;
+	gint ifindex;
+	NMDevice *device;
+
+	/* Be paranoid and check the sender address */
+	if (!nm_streq0 (g_dbus_object_manager_client_get_name_owner (omc), sender))
+		goto return_error;
+
+	g_variant_get (parameters, "(&o)", &network_path);
+
+	network = g_dbus_object_manager_get_interface (priv->object_manager,
+	                                               network_path,
+	                                               NM_IWD_NETWORK_INTERFACE);
+	value = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (network), "Device");
+	device_path = g_variant_get_string (value, NULL);
+
+	if (!device_path) {
+		_LOGE ("Device not cached for network %s in IWD Agent request",
+		       network_path);
+		goto return_error;
+	}
+
+	device_obj = g_dbus_object_manager_get_interface (priv->object_manager,
+	                                                  device_path,
+	                                                  NM_IWD_DEVICE_INTERFACE);
+	g_variant_unref (value);
+	value = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (device_obj), "Name");
+	ifname = g_variant_get_string (value, NULL);
+
+	if (!ifname) {
+		_LOGE ("Name not cached for device %s in IWD Agent request",
+		       device_path);
+		goto return_error;
+	}
+
+	ifindex = if_nametoindex (ifname);
+	if (!ifindex) {
+		_LOGE ("if_nametoindex failed for Name %s for Device at %s: %i",
+		       ifname, device_path, errno);
+		goto return_error;
+	}
+
+	device = nm_manager_get_device_by_ifindex (priv->manager, ifindex);
+	if (!NM_IS_DEVICE_IWD (device)) {
+		_LOGE ("IWD device named %s is not a Wifi device in IWD Agent request",
+                       ifname);
+		goto return_error;
+	}
+
+	if (nm_device_iwd_agent_psk_query (NM_DEVICE_IWD (device), invocation))
+		return;
+
+	_LOGE ("Device %s did not handle the IWD Agent request", ifname);
+
+return_error:
+	/* IWD doesn't look at the specific error */
+	g_dbus_method_invocation_return_error_literal (invocation, NM_DEVICE_ERROR,
+	                                               NM_DEVICE_ERROR_INVALID_CONNECTION,
+	                                               "No PSK available for this connection");
+}
+
+
+static guint
+psk_agent_export (GDBusConnection *connection, gpointer user_data,
+                  gchar **agent_path, GError **error)
+{
+	static const GDBusArgInfo request_passphrase_arg_network = {
+		-1,
+		(gchar *) "network",
+		(gchar *) "o",
+		NULL,
+	};
+	static const GDBusArgInfo *const request_passphrase_in_args[] = {
+		&request_passphrase_arg_network,
+		NULL,
+	};
+	static const GDBusArgInfo request_passphrase_arg_passphrase = {
+		-1,
+		(gchar *) "passphrase",
+		(gchar *) "s",
+		NULL,
+	};
+	static const GDBusArgInfo *const request_passphrase_out_args[] = {
+		&request_passphrase_arg_passphrase,
+		NULL,
+	};
+	static const GDBusMethodInfo request_passphrase_info = {
+		-1,
+		(gchar *) "RequestPassphrase",
+		(GDBusArgInfo **) &request_passphrase_in_args,
+		(GDBusArgInfo **) &request_passphrase_out_args,
+		NULL,
+	};
+	static const GDBusMethodInfo *const method_info[] = {
+		&request_passphrase_info,
+		NULL,
+	};
+	static GDBusInterfaceInfo interface_info = {
+		-1,
+		(gchar *) "net.connman.iwd.Agent",
+		(GDBusMethodInfo **) &method_info,
+		NULL,
+		NULL,
+		NULL,
+	};
+	static GDBusInterfaceVTable vtable = {
+		psk_agent_dbus_method_cb,
+		NULL,
+		NULL,
+	};
+
+	gchar path[50];
+	unsigned int rnd;
+	guint id;
+
+	if  (!nm_utils_random_bytes (&rnd, sizeof (rnd))) {
+		g_set_error_literal (error,
+		                     NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_FAILED,
+		                     "Can't read urandom.");
+		return 0;
+	}
+
+	nm_sprintf_buf (path, "/agent/%u", rnd);
+
+	id = g_dbus_connection_register_object (connection, path,
+	                                        &interface_info, &vtable,
+	                                        user_data, NULL, error);
+
+	if (id)
+		*agent_path = g_strdup (path);
+	return id;
+}
+
+static void
+register_agent (NMIwdManager *self)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusInterface *agent_manager;
+
+	agent_manager = g_dbus_object_manager_get_interface (priv->object_manager,
+	                                                     "/",
+	                                                     NM_IWD_AGENT_MANAGER_INTERFACE);
+
+	/* Register our agent */
+	g_dbus_proxy_call (G_DBUS_PROXY (agent_manager),
+	                   "RegisterAgent",
+	                   g_variant_new ("(o)", priv->agent_path),
+	                   G_DBUS_CALL_FLAGS_NONE, -1,
+	                   NULL, NULL, NULL);
+
+	g_object_unref (agent_manager);
+}
+
+/*****************************************************************************/
+
+static void
+set_device_dbus_object (NMIwdManager *self, GDBusInterface *interface,
+                        GDBusObject *object)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusProxy *proxy;
+	GVariant *value;
+	const char *ifname;
+	gint ifindex;
+	NMDevice *device;
+
+	if (!priv->running)
+		return;
+
+	g_return_if_fail (G_IS_DBUS_PROXY (interface));
+
+	proxy = G_DBUS_PROXY (interface);
+
+	if (strcmp (g_dbus_proxy_get_interface_name (proxy),
+	            NM_IWD_DEVICE_INTERFACE))
+		return;
+
+	value = g_dbus_proxy_get_cached_property (proxy, "Name");
+	if (!value) {
+		_LOGE ("Name not cached for Device at %s",
+		       g_dbus_proxy_get_object_path (proxy));
+		return;
+	}
+
+	ifname = g_variant_get_string (value, NULL);
+	ifindex = if_nametoindex (ifname);
+	g_variant_unref (value);
+
+	if (!ifindex) {
+		_LOGE ("if_nametoindex failed for Name %s for Device at %s: %i",
+		       ifname, g_dbus_proxy_get_object_path (proxy), errno);
+		return;
+	}
+
+	device = nm_manager_get_device_by_ifindex (priv->manager, ifindex);
+	if (!NM_IS_DEVICE_IWD (device)) {
+		_LOGE ("IWD device named %s is not a Wifi device", ifname);
+		return;
+	}
+
+	nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object);
+}
+
+static void
+interface_added (GDBusObjectManager *object_manager, GDBusObject *object,
+                 GDBusInterface *interface, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+
+	set_device_dbus_object (self, interface, object);
+}
+
+static void
+interface_removed (GDBusObjectManager *object_manager, GDBusObject *object,
+                   GDBusInterface *interface, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+
+	/*
+	 * TODO: we may need to save the GDBusInterface or GDBusObject
+	 * pointer in the hash table because we may be no longer able to
+	 * access the Name property or map the name to ifindex with
+	 * if_nametoindex at this point.
+	 */
+
+	set_device_dbus_object (self, interface, NULL);
+}
+
+static gboolean
+_om_has_name_owner (GDBusObjectManager *object_manager)
+{
+	gs_free char *name_owner = NULL;
+
+	nm_assert (G_IS_DBUS_OBJECT_MANAGER_CLIENT (object_manager));
+
+	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (object_manager));
+	return !!name_owner;
+}
+
+static void
+object_added (NMIwdManager *self, GDBusObject *object)
+{
+	GList *interfaces, *iter;
+
+	interfaces = g_dbus_object_get_interfaces (object);
+	for (iter = interfaces; iter; iter = iter->next) {
+		GDBusInterface *interface = G_DBUS_INTERFACE (iter->data);
+
+		set_device_dbus_object (self, interface, object);
+	}
+
+	g_list_free_full (interfaces, g_object_unref);
+}
+
+static void
+known_network_free (KnownNetworkData *network)
+{
+	g_free (network->name);
+	g_free (network);
+}
+
+static void
+list_known_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *variant = NULL;
+	GVariantIter *networks, *props;
+
+	variant = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
+	                                      G_VARIANT_TYPE ("(aa{sv})"),
+	                                      &error);
+	if (!variant) {
+		_LOGE ("ListKnownNetworks() failed: %s", error->message);
+		return;
+	}
+
+	g_slist_free_full (priv->known_networks, (GDestroyNotify) known_network_free);
+	priv->known_networks = NULL;
+
+	g_variant_get (variant, "(aa{sv})", &networks);
+
+	while (g_variant_iter_next (networks, "a{sv}", &props)) {
+		const gchar *key;
+		const gchar *name = NULL;
+		const gchar *type = NULL;
+		GVariant *val;
+		KnownNetworkData *network_data;
+
+		while (g_variant_iter_next (props, "{&sv}", &key, &val)) {
+			if (!strcmp (key, "Name"))
+				name = g_variant_get_string (val, NULL);
+
+			if (!strcmp (key, "Type"))
+				type = g_variant_get_string (val, NULL);
+
+			g_variant_unref (val);
+		}
+
+		if (!name || !type)
+			goto next;
+
+		network_data = g_new (KnownNetworkData, 1);
+		network_data->name = g_strdup (name);
+		if (!strcmp (type, "open"))
+			network_data->security = NM_IWD_NETWORK_SECURITY_NONE;
+		else if (!strcmp (type, "psk"))
+			network_data->security = NM_IWD_NETWORK_SECURITY_PSK;
+		else if (!strcmp (type, "8021x"))
+			network_data->security = NM_IWD_NETWORK_SECURITY_8021X;
+
+		priv->known_networks = g_slist_append (priv->known_networks,
+		                                       network_data);
+
+next:
+		g_variant_iter_free (props);
+	}
+
+	g_variant_iter_free (networks);
+
+	/* For completness we may want to call nm_device_emit_recheck_auto_activate
+	 * and nm_device_recheck_available_connections for all affected devices
+	 * now but the ListKnownNetworks call should have been really fast,
+	 * faster than any scan on any newly created devices could have happened.
+	 */
+}
+
+static void
+update_known_networks (NMIwdManager *self)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusInterface *known_networks_if;
+
+	known_networks_if = g_dbus_object_manager_get_interface (priv->object_manager,
+	                                                         "/",
+	                                                         NM_IWD_KNOWN_NETWORKS_INTERFACE);
+
+	g_dbus_proxy_call (G_DBUS_PROXY (known_networks_if),
+	                   "ListKnownNetworks",
+	                   g_variant_new ("()"),
+	                   G_DBUS_CALL_FLAGS_NONE, -1,
+	                   priv->cancellable, list_known_networks_cb, self);
+
+	g_object_unref (known_networks_if);
+}
+
+static void prepare_object_manager (NMIwdManager *self);
+
+static void
+name_owner_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusObjectManager *object_manager = G_DBUS_OBJECT_MANAGER (object);
+
+	nm_assert (object_manager == priv->object_manager);
+
+	if (_om_has_name_owner (object_manager)) {
+		g_signal_handlers_disconnect_by_data (object_manager, self);
+		g_clear_object (&priv->object_manager);
+		prepare_object_manager (self);
+	} else {
+		const CList *tmp_lst;
+		NMDevice *device;
+
+		if (!priv->running)
+			return;
+
+		priv->running = false;
+
+		nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+			if (NM_IS_DEVICE_IWD (device)) {
+				nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device),
+				                               NULL);
+			}
+		}
+	}
+}
+
+static void
+device_added (NMManager *manager, NMDevice *device, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GList *objects, *iter;
+
+	if (!NM_IS_DEVICE_IWD (device))
+		return;
+
+	if (!priv->running)
+		return;
+
+	objects = g_dbus_object_manager_get_objects (priv->object_manager);
+	for (iter = objects; iter; iter = iter->next) {
+		GDBusObject *object = G_DBUS_OBJECT (iter->data);
+		GDBusInterface *interface;
+		GDBusProxy *proxy;
+		GVariant *value;
+		const char *obj_ifname;
+
+		interface = g_dbus_object_get_interface (object,
+		                                         NM_IWD_DEVICE_INTERFACE);
+		if (!interface)
+			continue;
+
+		proxy = G_DBUS_PROXY (interface);
+		value = g_dbus_proxy_get_cached_property (proxy, "Name");
+		if (!value) {
+			g_object_unref (interface);
+			continue;
+		}
+
+		obj_ifname = g_variant_get_string (value, NULL);
+		g_variant_unref (value);
+		g_object_unref (interface);
+
+		if (strcmp (nm_device_get_iface (device), obj_ifname))
+			continue;
+
+		nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object);
+		break;
+	}
+
+	g_list_free_full (objects, g_object_unref);
+}
+
+static void
+got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GError *error = NULL;
+	GDBusObjectManager *object_manager;
+	GDBusConnection *connection;
+
+	object_manager = g_dbus_object_manager_client_new_for_bus_finish (result, &error);
+	if (object_manager == NULL) {
+		_LOGE ("failed to acquire IWD Object Manager: Wi-Fi will not be available (%s)",
+		       NM_G_ERROR_MSG (error));
+		g_clear_error (&error);
+		return;
+	}
+
+	priv->object_manager = object_manager;
+
+	g_signal_connect (priv->object_manager, "notify::name-owner",
+	                  G_CALLBACK (name_owner_changed), self);
+
+	nm_assert (G_IS_DBUS_OBJECT_MANAGER_CLIENT (object_manager));
+
+	connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (object_manager));
+
+	priv->agent_id = psk_agent_export (connection, self,
+	                                   &priv->agent_path, &error);
+	if (!priv->agent_id) {
+		_LOGE ("failed to export the IWD Agent: PSK/8021x WiFi networks will not work: %s",
+		       NM_G_ERROR_MSG (error));
+		g_clear_error (&error);
+	}
+
+	if (_om_has_name_owner (object_manager)) {
+		GList *objects, *iter;
+
+		priv->running = true;
+
+		g_signal_connect (priv->object_manager, "interface-added",
+		                  G_CALLBACK (interface_added), self);
+		g_signal_connect (priv->object_manager, "interface-removed",
+		                  G_CALLBACK (interface_removed), self);
+
+		objects = g_dbus_object_manager_get_objects (object_manager);
+		for (iter = objects; iter; iter = iter->next)
+			object_added (self, G_DBUS_OBJECT (iter->data));
+
+		g_list_free_full (objects, g_object_unref);
+
+		if (priv->agent_id)
+			register_agent (self);
+
+		update_known_networks (self);
+	}
+}
+
+static void
+prepare_object_manager (NMIwdManager *self)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+
+	g_dbus_object_manager_client_new_for_bus (NM_IWD_BUS_TYPE,
+	                                          G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
+	                                          NM_IWD_SERVICE, "/",
+	                                          NULL, NULL, NULL,
+	                                          priv->cancellable,
+	                                          got_object_manager, self);
+}
+
+gboolean
+nm_iwd_manager_is_known_network (NMIwdManager *self, const gchar *name,
+                                 NMIwdNetworkSecurity security)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	const GSList *iter;
+
+	for (iter = priv->known_networks; iter; iter = g_slist_next (iter)) {
+		const KnownNetworkData *network = iter->data;
+
+		if (!strcmp (network->name, name) && network->security == security)
+			return true;
+	}
+
+	return false;
+}
+
+void
+nm_iwd_manager_network_connected (NMIwdManager *self, const gchar *name,
+                                  NMIwdNetworkSecurity security)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	KnownNetworkData *network_data;
+
+	if (nm_iwd_manager_is_known_network (self, name, security))
+		return;
+
+	network_data = g_new (KnownNetworkData, 1);
+	network_data->name = g_strdup (name);
+	network_data->security = security;
+	priv->known_networks = g_slist_append (priv->known_networks, network_data);
+}
+
+/*****************************************************************************/
+
+NM_DEFINE_SINGLETON_GETTER (NMIwdManager, nm_iwd_manager_get,
+                            NM_TYPE_IWD_MANAGER);
+
+static void
+nm_iwd_manager_init (NMIwdManager *self)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+
+	priv->manager = g_object_ref (nm_manager_get ());
+	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED,
+	                  G_CALLBACK (device_added), self);
+
+	priv->cancellable = g_cancellable_new ();
+
+	prepare_object_manager (self);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMIwdManager *self = (NMIwdManager *) object;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+
+	if (priv->object_manager) {
+		if (priv->agent_id) {
+			GDBusConnection *connection;
+			GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager);
+
+			/* No need to unregister the agent as IWD will detect
+			 * our DBus connection being closed.
+			 */
+
+			connection = g_dbus_object_manager_client_get_connection (omc);
+
+			g_dbus_connection_unregister_object (connection, priv->agent_id);
+			priv->agent_id = 0;
+		}
+
+		g_clear_object (&priv->object_manager);
+	}
+
+	nm_clear_g_free (&priv->agent_path);
+
+	nm_clear_g_cancellable (&priv->cancellable);
+
+	g_slist_free_full (priv->known_networks, (GDestroyNotify) known_network_free);
+	priv->known_networks = NULL;
+
+	if (priv->manager) {
+		g_signal_handlers_disconnect_by_data (priv->manager, self);
+		g_clear_object (&priv->manager);
+	}
+
+	G_OBJECT_CLASS (nm_iwd_manager_parent_class)->dispose (object);
+}
+
+static void
+nm_iwd_manager_class_init (NMIwdManagerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = dispose;
+}
diff --git a/src/devices/wifi/nm-iwd-manager.h b/src/devices/wifi/nm-iwd-manager.h
new file mode 100644
index 00000000..8e6b66ff
--- /dev/null
+++ b/src/devices/wifi/nm-iwd-manager.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2017 Intel Corporation
+ */
+
+#ifndef __NETWORKMANAGER_IWD_MANAGER_H__
+#define __NETWORKMANAGER_IWD_MANAGER_H__
+
+#include "devices/nm-device.h"
+
+#define NM_IWD_BUS_TYPE                 G_BUS_TYPE_SYSTEM
+#define NM_IWD_SERVICE                  "net.connman.iwd"
+
+#define NM_IWD_AGENT_MANAGER_INTERFACE  "net.connman.iwd.AgentManager"
+#define NM_IWD_WIPHY_INTERFACE          "net.connman.iwd.Adapter"
+#define NM_IWD_DEVICE_INTERFACE         "net.connman.iwd.Device"
+#define NM_IWD_NETWORK_INTERFACE        "net.connman.iwd.Network"
+#define NM_IWD_AGENT_INTERFACE          "net.connman.iwd.Agent"
+#define NM_IWD_WSC_INTERFACE            \
+	"net.connman.iwd.WiFiSimpleConfiguration"
+#define NM_IWD_KNOWN_NETWORKS_INTERFACE "net.connman.iwd.KnownNetworks"
+#define NM_IWD_SIGNAL_AGENT_INTERFACE   "net.connman.iwd.SignalLevelAgent"
+
+typedef enum {
+	NM_IWD_NETWORK_SECURITY_NONE,
+	NM_IWD_NETWORK_SECURITY_WEP,
+	NM_IWD_NETWORK_SECURITY_PSK,
+	NM_IWD_NETWORK_SECURITY_8021X,
+} NMIwdNetworkSecurity;
+
+#define NM_TYPE_IWD_MANAGER              (nm_iwd_manager_get_type ())
+#define NM_IWD_MANAGER(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IWD_MANAGER, NMIwdManager))
+#define NM_IWD_MANAGER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_IWD_MANAGER, NMIwdManagerClass))
+#define NM_IS_IWD_MANAGER(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_IWD_MANAGER))
+#define NM_IS_IWD_MANAGER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_IWD_MANAGER))
+#define NM_IWD_MANAGER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_IWD_MANAGER, NMIwdManagerClass))
+
+typedef struct _NMIwdManager NMIwdManager;
+typedef struct _NMIwdManagerClass NMIwdManagerClass;
+
+GType nm_iwd_manager_get_type (void);
+
+NMIwdManager *nm_iwd_manager_get (void);
+
+gboolean nm_iwd_manager_is_known_network (NMIwdManager *self, const gchar *name,
+                                          NMIwdNetworkSecurity security);
+void nm_iwd_manager_network_connected (NMIwdManager *self, const gchar *name,
+                                       NMIwdNetworkSecurity security);
+
+#endif /* __NETWORKMANAGER_IWD_MANAGER_H__ */
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index bc823af0..dd6d1deb 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -21,19 +21,20 @@
 
 #include "nm-default.h"
 
+#include "nm-wifi-ap.h"
+
 #include <string.h>
 #include <stdlib.h>
 
-#include "nm-wifi-ap.h"
+#include "nm-setting-wireless.h"
+
 #include "nm-wifi-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
-
-#include "nm-setting-wireless.h"
-
-#include "introspection/org.freedesktop.NetworkManager.AccessPoint.h"
+#include "devices/nm-device.h"
+#include "nm-dbus-manager.h"
 
 #define PROTO_WPA "wpa"
 #define PROTO_RSN "rsn"
@@ -53,7 +54,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMWifiAP,
 	PROP_LAST_SEEN,
 );
 
-typedef struct {
+struct _NMWifiAPPrivate {
 	char *supplicant_path;   /* D-Bus object path of this AP from wpa_supplicant */
 
 	/* Scanned or cached values */
@@ -72,20 +73,17 @@ typedef struct {
 	bool                fake:1;       /* Whether or not the AP is from a scan */
 	bool                hotspot:1;    /* Whether the AP is a local device's hotspot network */
 	gint32              last_seen;    /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
-} NMWifiAPPrivate;
-
-struct _NMWifiAP {
-	NMExportedObject parent;
-	NMWifiAPPrivate _priv;
 };
 
+typedef struct _NMWifiAPPrivate NMWifiAPPrivate;
+
 struct _NMWifiAPClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 };
 
-G_DEFINE_TYPE (NMWifiAP, nm_wifi_ap, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMWifiAP, nm_wifi_ap, NM_TYPE_DBUS_OBJECT)
 
-#define NM_WIFI_AP_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMWifiAP, NM_IS_WIFI_AP)
+#define NM_WIFI_AP_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMWifiAP, NM_IS_WIFI_AP)
 
 /*****************************************************************************/
 
@@ -97,30 +95,24 @@ nm_wifi_ap_get_supplicant_path (NMWifiAP *ap)
 	return NM_WIFI_AP_GET_PRIVATE (ap)->supplicant_path;
 }
 
-guint64
-nm_wifi_ap_get_id (NMWifiAP *ap)
+const GByteArray *
+nm_wifi_ap_get_ssid (const NMWifiAP *ap)
 {
-	const char *path;
-	guint64 i;
-
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), 0);
-
-	path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap));
-	g_return_val_if_fail (path, 0);
-
-	nm_assert (g_str_has_prefix (path, NM_DBUS_PATH_ACCESS_POINT"/"));
-
-	i = _nm_utils_ascii_str_to_int64 (&path[NM_STRLEN (NM_DBUS_PATH_ACCESS_POINT"/")], 10, 1, G_MAXINT64, 0);
+	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NULL);
 
-	nm_assert (i);
-	return i;
+	return NM_WIFI_AP_GET_PRIVATE (ap)->ssid;
 }
 
-const GByteArray * nm_wifi_ap_get_ssid (const NMWifiAP *ap)
+static GVariant *
+nm_wifi_ap_get_ssid_as_variant (const NMWifiAP *self)
 {
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NULL);
+	const NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (self);
 
-	return NM_WIFI_AP_GET_PRIVATE (ap)->ssid;
+	if (priv->ssid) {
+		return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                  priv->ssid->data, priv->ssid->len, 1);
+	} else
+		return g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0);
 }
 
 gboolean
@@ -330,7 +322,7 @@ guint32
 nm_wifi_ap_get_max_bitrate (NMWifiAP *ap)
 {
 	g_return_val_if_fail (NM_IS_WIFI_AP (ap), 0);
-	g_return_val_if_fail (nm_exported_object_is_exported (NM_EXPORTED_OBJECT (ap)), 0);
+	g_return_val_if_fail (nm_dbus_object_is_exported (NM_DBUS_OBJECT (ap)), 0);
 
 	return NM_WIFI_AP_GET_PRIVATE (ap)->max_bitrate;
 }
@@ -415,7 +407,9 @@ security_from_vardict (GVariant *security)
 	    && array) {
 		if (g_strv_contains (array, "wpa-psk"))
 			flags |= NM_802_11_AP_SEC_KEY_MGMT_PSK;
-		if (g_strv_contains (array, "wpa-eap"))
+		if (g_strv_contains (array, "wpa-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;
 		g_free (array);
 	}
@@ -636,7 +630,7 @@ get_max_rate_vht_160_ss3 (int mcs)
 static gboolean
 get_max_rate_ht (const guint8 *bytes, guint len, guint32 *out_maxrate)
 {
-	guint32 mcs, i;
+	guint32 i;
 	guint8 ht_cap_info;
 	const guint8 *supported_mcs_set;
 	guint32 rate;
@@ -653,7 +647,6 @@ get_max_rate_ht (const guint8 *bytes, guint len, guint32 *out_maxrate)
 	*out_maxrate = 0;
 
 	/* Find the maximum supported mcs rate */
-	mcs = -1;
 	for (i = 0; i <= 76; i++) {
 		unsigned int mcs_octet = i / 8;
 		unsigned int MCS_RATE_BIT = 1 << i % 8;
@@ -977,7 +970,7 @@ nm_wifi_ap_to_string (const NMWifiAP *self,
 	if (priv->supplicant_path)
 		supplicant_id = strrchr (priv->supplicant_path, '/') ?: supplicant_id;
 
-	export_path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (self));
+	export_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
 	if (export_path)
 		export_path = strrchr (export_path, '/') ?: export_path;
 	else
@@ -1119,8 +1112,8 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE ((NMWifiAP *) object);
-	GVariant *ssid;
+	NMWifiAP *self = NM_WIFI_AP (object);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (self);
 
 	switch (prop_id) {
 	case PROP_FLAGS:
@@ -1133,12 +1126,7 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, priv->rsn_flags);
 		break;
 	case PROP_SSID:
-		if (priv->ssid) {
-			ssid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-			                                  priv->ssid->data, priv->ssid->len, 1);
-		} else
-			ssid = g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0);
-		g_value_take_variant (value, ssid);
+		g_value_take_variant (value, nm_wifi_ap_get_ssid_as_variant (self));
 		break;
 	case PROP_FREQUENCY:
 		g_value_set_uint (value, priv->freq);
@@ -1170,9 +1158,15 @@ get_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_wifi_ap_init (NMWifiAP *ap)
+nm_wifi_ap_init (NMWifiAP *self)
 {
-	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
+	NMWifiAPPrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_WIFI_AP, NMWifiAPPrivate);
+
+	self->_priv = priv;
+
+	c_list_init (&self->aps_lst);
 
 	priv->mode = NM_802_11_MODE_INFRA;
 	priv->flags = NM_802_11_AP_FLAGS_NONE;
@@ -1332,7 +1326,11 @@ error:
 static void
 finalize (GObject *object)
 {
-	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE ((NMWifiAP *) object);
+	NMWifiAP *self = NM_WIFI_AP (object);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (self);
+
+	nm_assert (!self->wifi_device);
+	nm_assert (c_list_is_empty (&self->aps_lst));
 
 	g_free (priv->supplicant_path);
 	if (priv->ssid)
@@ -1342,6 +1340,28 @@ finalize (GObject *object)
 	G_OBJECT_CLASS (nm_wifi_ap_parent_class)->finalize (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_access_point = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_ACCESS_POINT,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Flags",      "u",  NM_WIFI_AP_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WpaFlags",   "u",  NM_WIFI_AP_WPA_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("RsnFlags",   "u",  NM_WIFI_AP_RSN_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Ssid",       "ay", NM_WIFI_AP_SSID),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Frequency",  "u",  NM_WIFI_AP_FREQUENCY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",  "s",  NM_WIFI_AP_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",       "u",  NM_WIFI_AP_MODE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("MaxBitrate", "u",  NM_WIFI_AP_MAX_BITRATE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Strength",   "y",  NM_WIFI_AP_STRENGTH),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("LastSeen",   "i",  NM_WIFI_AP_LAST_SEEN),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 {
@@ -1359,9 +1379,12 @@ nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 	| NM_802_11_AP_SEC_KEY_MGMT_802_1X )
 
 	GObjectClass *object_class = G_OBJECT_CLASS (ap_class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (ap_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (ap_class);
+
+	g_type_class_add_private (object_class, sizeof (NMWifiAPPrivate));
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH_ACCESS_POINT);
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH_ACCESS_POINT);
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_access_point);
 
 	object_class->get_property = get_property;
 	object_class->finalize = finalize;
@@ -1424,9 +1447,85 @@ nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
+
+/*****************************************************************************/
 
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (ap_class),
-	                                        NMDBUS_TYPE_ACCESS_POINT_SKELETON,
-	                                        NULL);
+const char **
+nm_wifi_aps_get_paths (const CList *aps_lst_head, gboolean include_without_ssid)
+{
+	NMWifiAP *ap;
+	gsize i, n;
+	const char **list;
+	const char *path;
+
+	n = c_list_length (aps_lst_head);
+	list = g_new (const char *, n + 1);
+
+	i = 0;
+	if (n > 0) {
+		c_list_for_each_entry (ap, aps_lst_head, aps_lst) {
+			nm_assert (i < n);
+			if (   !include_without_ssid
+			    && !nm_wifi_ap_get_ssid (ap))
+				continue;
+
+			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (ap));
+			nm_assert (path);
+
+			list[i++] = path;
+		}
+		nm_assert (i <= n);
+		nm_assert (!include_without_ssid || i == n);
+	}
+	list[i] = NULL;
+	return list;
 }
 
+NMWifiAP *
+nm_wifi_aps_find_first_compatible (const CList *aps_lst_head,
+                                   NMConnection *connection)
+{
+	NMWifiAP *ap;
+
+	g_return_val_if_fail (connection, NULL);
+
+	c_list_for_each_entry (ap, aps_lst_head, aps_lst) {
+		if (nm_wifi_ap_check_compatible (ap, connection))
+			return ap;
+	}
+	return NULL;
+}
+
+NMWifiAP *
+nm_wifi_aps_find_by_supplicant_path (const CList *aps_lst_head, const char *path)
+{
+	NMWifiAP *ap;
+
+	g_return_val_if_fail (path != NULL, NULL);
+
+	c_list_for_each_entry (ap, aps_lst_head, aps_lst) {
+		if (nm_streq0 (path, nm_wifi_ap_get_supplicant_path (ap)))
+			return ap;
+	}
+	return NULL;
+}
+
+/*****************************************************************************/
+
+NMWifiAP *
+nm_wifi_ap_lookup_for_device (NMDevice *device, const char *exported_path)
+{
+	NMWifiAP *ap;
+
+	g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
+
+	ap = (NMWifiAP *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (device)),
+	                                                 exported_path);
+	if (   !ap
+	    || !NM_IS_WIFI_AP (ap)
+	    || ap->wifi_device != device)
+		return NULL;
+
+	return ap;
+}
diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h
index dd5a4ad1..4fdeee93 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/devices/wifi/nm-wifi-ap.h
@@ -22,7 +22,7 @@
 #ifndef __NM_WIFI_AP_H__
 #define __NM_WIFI_AP_H__
 
-#include "nm-exported-object.h"
+#include "nm-dbus-object.h"
 #include "nm-dbus-interface.h"
 #include "nm-connection.h"
 
@@ -44,7 +44,13 @@
 #define NM_WIFI_AP_STRENGTH             "strength"
 #define NM_WIFI_AP_LAST_SEEN            "last-seen"
 
-typedef struct _NMWifiAP NMWifiAP;
+typedef struct {
+	NMDBusObject parent;
+	NMDevice *wifi_device;
+	CList aps_lst;
+	struct _NMWifiAPPrivate *_priv;
+} NMWifiAP;
+
 typedef struct _NMWifiAPClass NMWifiAPClass;
 
 GType nm_wifi_ap_get_type (void);
@@ -66,7 +72,6 @@ gboolean          nm_wifi_ap_complete_connection      (NMWifiAP *self,
                                                        GError **error);
 
 const char *      nm_wifi_ap_get_supplicant_path      (NMWifiAP *ap);
-guint64           nm_wifi_ap_get_id                   (NMWifiAP *ap);
 const GByteArray *nm_wifi_ap_get_ssid                 (const NMWifiAP *ap);
 gboolean          nm_wifi_ap_set_ssid                 (NMWifiAP *ap,
                                                        const guint8 *ssid,
@@ -95,4 +100,14 @@ const char       *nm_wifi_ap_to_string                (const NMWifiAP *self,
                                                        gulong buf_len,
                                                        gint32 now_s);
 
+const char      **nm_wifi_aps_get_paths        (const CList *aps_lst_head,
+                                                gboolean include_without_ssid);
+
+NMWifiAP         *nm_wifi_aps_find_first_compatible (const CList *aps_lst_head,
+                                                     NMConnection *connection);
+
+NMWifiAP         *nm_wifi_aps_find_by_supplicant_path (const CList *aps_lst_head, const char *path);
+
+NMWifiAP         *nm_wifi_ap_lookup_for_device (NMDevice *device, const char *exported_path);
+
 #endif /* __NM_WIFI_AP_H__ */
diff --git a/src/devices/wifi/nm-wifi-common.c b/src/devices/wifi/nm-wifi-common.c
new file mode 100644
index 00000000..47c0ce67
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-common.c
@@ -0,0 +1,205 @@
+/*-*- 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
+ * 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 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-wifi-common.h"
+
+#include "devices/nm-device.h"
+#include "nm-wifi-ap.h"
+#include "nm-device-wifi.h"
+#include "nm-dbus-manager.h"
+
+#if WITH_IWD
+#include "nm-device-iwd.h"
+#endif
+
+/*****************************************************************************/
+
+void
+nm_device_wifi_emit_signal_access_point (NMDevice *device,
+                                         NMWifiAP *ap,
+                                         gboolean is_added /* or else is_removed */)
+{
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (device),
+	                            &nm_interface_info_device_wireless,
+	                            is_added
+	                              ? &nm_signal_info_wireless_access_point_added
+	                              : &nm_signal_info_wireless_access_point_removed,
+	                            "(o)",
+	                            nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
+}
+
+/*****************************************************************************/
+
+static const CList *
+_dispatch_get_aps (NMDevice *device)
+{
+#if WITH_IWD
+	if (NM_IS_DEVICE_IWD (device))
+		return _nm_device_iwd_get_aps (NM_DEVICE_IWD (device));
+#endif
+	return _nm_device_wifi_get_aps (NM_DEVICE_WIFI (device));
+}
+
+static void
+_dispatch_request_scan (NMDevice *device,
+                        GVariant *options,
+                        GDBusMethodInvocation *invocation)
+{
+#if WITH_IWD
+	if (NM_IS_DEVICE_IWD (device)) {
+		_nm_device_iwd_request_scan (NM_DEVICE_IWD (device),
+		                             options,
+		                             invocation);
+	}
+#endif
+	_nm_device_wifi_request_scan (NM_DEVICE_WIFI (device),
+	                              options,
+	                              invocation);
+}
+
+static void
+impl_device_wifi_get_access_points (NMDBusObject *obj,
+                                    const NMDBusInterfaceInfoExtended *interface_info,
+                                    const NMDBusMethodInfoExtended *method_info,
+                                    GDBusConnection *connection,
+                                    const char *sender,
+                                    GDBusMethodInvocation *invocation,
+                                    GVariant *parameters)
+{
+	gs_free const char **list = NULL;
+	GVariant *v;
+	const CList *all_aps;
+
+	/* NOTE: this handler is called both for NMDevicwWifi and NMDeviceIwd. */
+
+	all_aps = _dispatch_get_aps (NM_DEVICE (obj));
+	list = nm_wifi_aps_get_paths (all_aps, FALSE);
+	v = g_variant_new_objv (list, -1);
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new_tuple (&v, 1));
+}
+
+static void
+impl_device_wifi_get_all_access_points (NMDBusObject *obj,
+                                        const NMDBusInterfaceInfoExtended *interface_info,
+                                        const NMDBusMethodInfoExtended *method_info,
+                                        GDBusConnection *connection,
+                                        const char *sender,
+                                        GDBusMethodInvocation *invocation,
+                                        GVariant *parameters)
+{
+	gs_free const char **list = NULL;
+	GVariant *v;
+	const CList *all_aps;
+
+	/* NOTE: this handler is called both for NMDevicwWifi and NMDeviceIwd. */
+
+	all_aps = _dispatch_get_aps (NM_DEVICE (obj));
+	list = nm_wifi_aps_get_paths (all_aps, TRUE);
+	v = g_variant_new_objv (list, -1);
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new_tuple (&v, 1));
+}
+
+static void
+impl_device_wifi_request_scan (NMDBusObject *obj,
+                               const NMDBusInterfaceInfoExtended *interface_info,
+                               const NMDBusMethodInfoExtended *method_info,
+                               GDBusConnection *connection,
+                               const char *sender,
+                               GDBusMethodInvocation *invocation,
+                               GVariant *parameters)
+{
+	gs_unref_variant GVariant *options = NULL;
+
+	/* NOTE: this handler is called both for NMDevicwWifi and NMDeviceIwd. */
+
+	g_variant_get (parameters, "(@a{sv})", &options);
+
+	_dispatch_request_scan (NM_DEVICE (obj),
+	                        options,
+	                        invocation);
+}
+
+const GDBusSignalInfo nm_signal_info_wireless_access_point_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"AccessPointAdded",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("access_point", "o"),
+	),
+);
+
+const GDBusSignalInfo nm_signal_info_wireless_access_point_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"AccessPointRemoved",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("access_point", "o"),
+	),
+);
+
+const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_WIRELESS,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetAccessPoints",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("access_points", "ao"),
+					),
+				),
+				.handle = impl_device_wifi_get_access_points,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetAllAccessPoints",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("access_points", "ao"),
+					),
+				),
+				.handle = impl_device_wifi_get_all_access_points,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"RequestScan",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("options", "a{sv}"),
+					),
+				),
+				.handle = impl_device_wifi_request_scan,
+			),
+		),
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+			&nm_signal_info_wireless_access_point_added,
+			&nm_signal_info_wireless_access_point_removed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",            "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("PermHwAddress",        "s",  NM_DEVICE_PERM_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",                 "u",  NM_DEVICE_WIFI_MODE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("BitRate",              "u",  NM_DEVICE_WIFI_BITRATE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("AccessPoints",         "ao", NM_DEVICE_WIFI_ACCESS_POINTS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ActiveAccessPoint",    "o",  NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WirelessCapabilities", "u",  NM_DEVICE_WIFI_CAPABILITIES),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
diff --git a/src/devices/wifi/nm-wifi-common.h b/src/devices/wifi/nm-wifi-common.h
new file mode 100644
index 00000000..91cbeb55
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-common.h
@@ -0,0 +1,37 @@
+/* -*- 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
+ * 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 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_WIFI_COMMON_H__
+#define __NM_WIFI_COMMON_H__
+
+#include "nm-dbus-utils.h"
+#include "nm-wifi-ap.h"
+
+/*****************************************************************************/
+
+void nm_device_wifi_emit_signal_access_point (NMDevice *device,
+                                              NMWifiAP *ap,
+                                              gboolean is_added /* or else is_removed */);
+
+extern const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless;
+extern const GDBusSignalInfo nm_signal_info_wireless_access_point_added;
+extern const GDBusSignalInfo nm_signal_info_wireless_access_point_removed;
+
+#endif  /* __NM_WIFI_COMMON_H__ */
diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/devices/wifi/nm-wifi-factory.c
index a1752634..6b8e5fb8 100644
--- a/src/devices/wifi/nm-wifi-factory.c
+++ b/src/devices/wifi/nm-wifi-factory.c
@@ -27,8 +27,10 @@
 #include "nm-setting-olpc-mesh.h"
 #include "nm-device-wifi.h"
 #include "nm-device-olpc-mesh.h"
+#include "nm-device-iwd.h"
 #include "settings/nm-settings-connection.h"
 #include "platform/nm-platform.h"
+#include "nm-config.h"
 
 /*****************************************************************************/
 
@@ -75,6 +77,7 @@ create_device (NMDeviceFactory *factory,
 {
 	NMDeviceWifiCapabilities capabilities;
 	NM80211Mode mode;
+	gs_free char *backend = NULL;
 
 	g_return_val_if_fail (iface != NULL, NULL);
 	g_return_val_if_fail (plink != NULL, NULL);
@@ -98,10 +101,30 @@ create_device (NMDeviceFactory *factory,
 		return NULL;
 	}
 
-	if (plink->type == NM_LINK_TYPE_WIFI)
-		return nm_device_wifi_new (iface, capabilities);
-	else
+	if (plink->type != NM_LINK_TYPE_WIFI)
 		return nm_device_olpc_mesh_new (iface);
+
+	backend = nm_config_data_get_device_config_by_pllink (NM_CONFIG_GET_DATA,
+	                                                      NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND,
+	                                                      plink,
+	                                                      "wifi",
+	                                                      NULL);
+	nm_strstrip (backend);
+
+	nm_log_dbg (LOGD_PLATFORM | LOGD_WIFI,
+	            "(%s) config: backend is %s%s%s%s",
+	            iface,
+	            NM_PRINT_FMT_QUOTE_STRING (backend),
+	            WITH_IWD ? " (iwd support enabled)" : "");
+	if (!backend || !strcasecmp (backend, "wpa_supplicant"))
+		return nm_device_wifi_new (iface, capabilities);
+#if WITH_IWD
+	else if (!strcasecmp (backend, "iwd"))
+		return nm_device_iwd_new (iface, capabilities);
+#endif
+
+	nm_log_warn (LOGD_PLATFORM | LOGD_WIFI, "(%s) config: unknown or unsupported wifi-backend %s", iface, backend);
+	return NULL;
 }
 
 /*****************************************************************************/
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 3ff82004..044bd392 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -782,3 +782,35 @@ nm_wifi_utils_level_to_quality (gint val)
 	return CLAMP (val, 0, 100);
 }
 
+gboolean
+nm_wifi_utils_is_manf_default_ssid (const GByteArray *ssid)
+{
+	int i;
+	/*
+	 * List of manufacturer default SSIDs that are often unchanged by users.
+	 *
+	 * NOTE: this list should *not* contain networks that you would like to
+	 * automatically roam to like "Starbucks" or "AT&T" or "T-Mobile HotSpot".
+	 */
+	static const char *manf_defaults[] = {
+		"linksys",
+		"linksys-a",
+		"linksys-g",
+		"default",
+		"belkin54g",
+		"NETGEAR",
+		"o2DSL",
+		"WLAN",
+		"ALICE-WLAN",
+		"Speedport W 501V",
+		"TURBONETT",
+	};
+
+	for (i = 0; i < G_N_ELEMENTS (manf_defaults); i++) {
+		if (ssid->len == strlen (manf_defaults[i])) {
+			if (memcmp (manf_defaults[i], ssid->data, ssid->len) == 0)
+				return TRUE;
+		}
+	}
+	return FALSE;
+}
diff --git a/src/devices/wifi/nm-wifi-utils.h b/src/devices/wifi/nm-wifi-utils.h
index 1b6c2f4b..def64dd6 100644
--- a/src/devices/wifi/nm-wifi-utils.h
+++ b/src/devices/wifi/nm-wifi-utils.h
@@ -39,4 +39,6 @@ gboolean nm_wifi_utils_complete_connection (const GByteArray *ssid,
 
 guint32 nm_wifi_utils_level_to_quality (gint val);
 
+gboolean nm_wifi_utils_is_manf_default_ssid (const GByteArray *ssid);
+
 #endif  /* __NM_WIFI_UTILS_H__ */
diff --git a/src/devices/wifi/tests/meson.build b/src/devices/wifi/tests/meson.build
new file mode 100644
index 00000000..bb8f7c27
--- /dev/null
+++ b/src/devices/wifi/tests/meson.build
@@ -0,0 +1,13 @@
+test_unit = 'test-general'
+
+exe = executable(
+  'wifi-' + test_unit,
+  [test_unit + '.c'] + common_sources,
+  dependencies: test_nm_dep
+)
+
+test(
+  'devices/wifi/' + test_unit,
+  test_script,
+  args: test_args + [exe.full_path()]
+)
diff --git a/src/devices/wwan/libnm-wwan.ver b/src/devices/wwan/libnm-wwan.ver
index 6efcb03f..70b954c5 100644
--- a/src/devices/wwan/libnm-wwan.ver
+++ b/src/devices/wwan/libnm-wwan.ver
@@ -11,10 +11,10 @@ global:
 	nm_modem_get_capabilities;
 	nm_modem_get_configured_mtu;
 	nm_modem_get_control_port;
-	nm_modem_get_data_port;
 	nm_modem_get_driver;
 	nm_modem_get_iid;
 	nm_modem_get_path;
+	nm_modem_get_ip_ifindex;
 	nm_modem_get_secrets;
 	nm_modem_get_state;
 	nm_modem_get_type;
diff --git a/src/devices/wwan/meson.build b/src/devices/wwan/meson.build
new file mode 100644
index 00000000..032b3585
--- /dev/null
+++ b/src/devices/wwan/meson.build
@@ -0,0 +1,75 @@
+sources = files(
+  'nm-modem-broadband.c',
+  'nm-modem.c',
+  'nm-modem-manager.c'
+)
+
+deps = [
+  libsystemd_dep,
+  mm_glib_dep,
+  nm_dep
+]
+
+if enable_ofono
+  sources += files('nm-modem-ofono.c')
+endif
+
+linker_script = join_paths(meson.current_source_dir(), 'libnm-wwan.ver')
+
+libnm_wwan = shared_module(
+  'nm-wwan',
+  sources: sources,
+  dependencies: deps,
+  link_args: [
+    '-Wl,--version-script,@0@'.format(linker_script),
+  ],
+  link_depends: linker_script,
+  install: true,
+  install_dir: nm_pkglibdir
+)
+
+libnm_wwan_dep = declare_dependency(
+  include_directories: include_directories('.'),
+  link_with: libnm_wwan
+)
+
+core_plugins += libnm_wwan
+
+run_target(
+  'check-wwan',
+  command: [check_exports, libnm_wwan.full_path(), linker_script],
+  depends: libnm_wwan
+)
+
+sources = files(
+  'nm-device-modem.c',
+  'nm-wwan-factory.c'
+)
+
+libnm_device_plugin_wwan = shared_module(
+  'nm-device-plugin-wwan',
+  sources: sources,
+  dependencies: deps,
+  link_with: libnm_wwan,
+  link_args: ldflags_linker_script_devices,
+  link_depends: linker_script_devices,
+  install: true,
+  install_dir: nm_pkglibdir
+)
+
+core_plugins += libnm_device_plugin_wwan
+
+run_target(
+  'check-local-devices-wwan',
+  command: [check_exports, libnm_device_plugin_wwan.full_path(), linker_script_devices],
+  depends: libnm_device_plugin_wwan
+)
+
+# FIXME: check_so_symbols replacement
+'''
+check-local-devices-wwan: src/devices/wwan/libnm-device-plugin-wwan.la src/devices/wwan/libnm-wwan.la
+  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wwan/.libs/libnm-device-plugin-wwan.so "$(srcdir)/linker-script-devices.ver"
+  $(call check_so_symbols,$(builddir)/src/devices/wwan/.libs/libnm-device-plugin-wwan.so)
+  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wwan/.libs/libnm-wwan.so "$(srcdir)/src/devices/wwan/libnm-wwan.ver"
+  $(call check_so_symbols,$(builddir)/src/devices/wwan/.libs/libnm-wwan.so)
+'''
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index b79d145d..2a3e9ebe 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -32,8 +32,6 @@
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Modem.h"
-
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceModem);
 
@@ -260,21 +258,26 @@ modem_ip6_config_result (NMModem *modem,
 }
 
 static void
-data_port_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
+ip_ifindex_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
 {
-	NMDevice *self = NM_DEVICE (user_data);
-	gboolean changed;
+	NMDevice *device = NM_DEVICE (user_data);
+
+	if (!nm_device_is_activating (device))
+		return;
 
-	/* We set the IP iface in the device as soon as we know it, so that we
-	 * properly ifup it if needed */
-	changed = nm_device_set_ip_iface (self, nm_modem_get_data_port (modem));
+	if (!nm_device_set_ip_ifindex (device,
+	                               nm_modem_get_ip_ifindex (modem))) {
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		return;
+	}
 
 	/* Disable IPv6 immediately on the interface since NM handles IPv6
 	 * internally, and leaving it enabled could allow the kernel's IPv6
 	 * RA handling code to run before NM is ready.
 	 */
-	if (changed)
-		nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
+	nm_device_ipv6_sysctl_set (device, "disable_ipv6", "1");
 }
 
 static void
@@ -431,7 +434,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device);
@@ -629,11 +632,7 @@ set_modem (NMDeviceModem *self, NMModem *modem)
 	g_signal_connect (modem, NM_MODEM_STATE_CHANGED, G_CALLBACK (modem_state_cb), self);
 	g_signal_connect (modem, NM_MODEM_REMOVED, G_CALLBACK (modem_removed_cb), self);
 
-	/* In the old ModemManager the data port is known from the very beginning;
-	 * while in the new ModemManager the data port is set afterwards when the bearer gets
-	 * created */
-	g_signal_connect (modem, "notify::" NM_MODEM_DATA_PORT, G_CALLBACK (data_port_changed_cb), self);
-
+	g_signal_connect (modem, "notify::" NM_MODEM_IP_IFINDEX, G_CALLBACK (ip_ifindex_changed_cb), self);
 	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);
@@ -708,34 +707,23 @@ nm_device_modem_new (NMModem *modem)
 {
 	NMDeviceModemCapabilities caps = NM_DEVICE_MODEM_CAPABILITY_NONE;
 	NMDeviceModemCapabilities current_caps = NM_DEVICE_MODEM_CAPABILITY_NONE;
-	NMDevice *device;
-	const char *data_port;
 
 	g_return_val_if_fail (NM_IS_MODEM (modem), NULL);
 
 	/* Load capabilities */
 	nm_modem_get_capabilities (modem, &caps, &current_caps);
 
-	device = (NMDevice *) g_object_new (NM_TYPE_DEVICE_MODEM,
-	                                    NM_DEVICE_UDI, nm_modem_get_path (modem),
-	                                    NM_DEVICE_IFACE, nm_modem_get_uid (modem),
-	                                    NM_DEVICE_DRIVER, nm_modem_get_driver (modem),
-	                                    NM_DEVICE_TYPE_DESC, "Broadband",
-	                                    NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_MODEM,
-	                                    NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WWAN,
-	                                    NM_DEVICE_MODEM_MODEM, modem,
-	                                    NM_DEVICE_MODEM_CAPABILITIES, caps,
-	                                    NM_DEVICE_MODEM_CURRENT_CAPABILITIES, current_caps,
-	                                    NULL);
-
-	/* If the data port is known, set it as the IP interface immediately */
-	data_port = nm_modem_get_data_port (modem);
-	if (data_port) {
-		nm_device_set_ip_iface (device, data_port);
-		nm_device_ipv6_sysctl_set (device, "disable_ipv6", "1");
-	}
-
-	return device;
+	return g_object_new (NM_TYPE_DEVICE_MODEM,
+	                     NM_DEVICE_UDI, nm_modem_get_path (modem),
+	                     NM_DEVICE_IFACE, nm_modem_get_uid (modem),
+	                     NM_DEVICE_DRIVER, nm_modem_get_driver (modem),
+	                     NM_DEVICE_TYPE_DESC, "Broadband",
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_MODEM,
+	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WWAN,
+	                     NM_DEVICE_MODEM_MODEM, modem,
+	                     NM_DEVICE_MODEM_CAPABILITIES, caps,
+	                     NM_DEVICE_MODEM_CURRENT_CAPABILITIES, current_caps,
+	                     NULL);
 }
 
 static void
@@ -743,23 +731,41 @@ dispose (GObject *object)
 {
 	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) object);
 
-	if (priv->modem)
+	if (priv->modem) {
 		g_signal_handlers_disconnect_by_data (priv->modem, NM_DEVICE_MODEM (object));
-	g_clear_object (&priv->modem);
+		g_clear_object (&priv->modem);
+	}
 
 	G_OBJECT_CLASS (nm_device_modem_parent_class)->dispose (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_modem = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_MODEM,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ModemCapabilities",   "u",  NM_DEVICE_MODEM_CAPABILITIES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("CurrentCapabilities", "u",  NM_DEVICE_MODEM_CURRENT_CAPABILITIES),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
-nm_device_modem_class_init (NMDeviceModemClass *mclass)
+nm_device_modem_class_init (NMDeviceModemClass *klass)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS (mclass);
-	NMDeviceClass *device_class = NM_DEVICE_CLASS (mclass);
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->dispose = dispose;
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_modem);
+
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->get_type_description = get_type_description;
 	device_class->check_connection_compatible = check_connection_compatible;
@@ -802,8 +808,4 @@ nm_device_modem_class_init (NMDeviceModemClass *mclass)
 	                        G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (mclass),
-	                                        NMDBUS_TYPE_DEVICE_MODEM_SKELETON,
-	                                        NULL);
 }
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index dc0ce303..9a3744db 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -339,7 +339,7 @@ connect_context_clear (NMModemBroadband *self)
 		ConnectContext *ctx = self->_priv.ctx;
 
 		g_clear_error (&ctx->first_error);
-		g_clear_pointer (&ctx->ip_types, (GDestroyNotify) g_array_unref);
+		g_clear_pointer (&ctx->ip_types, g_array_unref);
 		nm_clear_g_cancellable (&ctx->cancellable);
 		g_clear_object (&ctx->connection);
 		g_clear_object (&ctx->connect_properties);
@@ -385,9 +385,10 @@ connect_ready (MMModemSimple *simple_iface,
 				g_dbus_error_strip_remote_error (error);
 			ctx->first_error = error;
 		} else
-			g_error_free (error);
+			g_clear_error (&error);
 
-		if (ctx->ip_type_tries == 0 && g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_RETRY)) {
+		if (   ctx->ip_type_tries == 0
+		    && g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_RETRY)) {
 			/* Try one more time */
 			ctx->ip_type_tries++;
 		} else {
@@ -410,21 +411,20 @@ connect_ready (MMModemSimple *simple_iface,
 	if (self->_priv.ipv6_config)
 		ip6_method = get_bearer_ip_method (self->_priv.ipv6_config);
 
-	if (ip4_method == NM_MODEM_IP_METHOD_UNKNOWN &&
-	    ip6_method == NM_MODEM_IP_METHOD_UNKNOWN) {
-		_LOGW ("failed to connect modem: invalid bearer IP configuration");
+	if (!nm_modem_set_data_port (NM_MODEM (self),
+	                             NM_PLATFORM_GET,
+	                             mm_bearer_get_interface (self->_priv.bearer),
+	                             ip4_method,
+	                             ip6_method,
+	                             mm_bearer_get_ip_timeout (self->_priv.bearer),
+	                             &error)) {
+		_LOGW ("failed to connect modem: %s", error->message);
+		g_error_free (error);
 		nm_modem_emit_prepare_result (NM_MODEM (self), FALSE, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
 		connect_context_clear (self);
 		return;
 	}
 
-	g_object_set (self,
-	              NM_MODEM_DATA_PORT,  mm_bearer_get_interface (self->_priv.bearer),
-	              NM_MODEM_IP4_METHOD, ip4_method,
-	              NM_MODEM_IP6_METHOD, ip6_method,
-	              NM_MODEM_IP_TIMEOUT, mm_bearer_get_ip_timeout (self->_priv.bearer),
-	              NULL);
-
 	ctx->step++;
 	connect_context_step (self);
 }
@@ -663,7 +663,7 @@ check_connection_compatible (NMModem *_self, NMConnection *connection)
 static gboolean
 complete_connection (NMModem *_self,
                      NMConnection *connection,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
@@ -1409,35 +1409,34 @@ nm_modem_broadband_init (NMModemBroadband *self)
 NMModem *
 nm_modem_broadband_new (GObject *object, GError **error)
 {
-	NMModem *modem;
 	MMObject *modem_object;
 	MMModem *modem_iface;
-	gchar *drivers;
+	const char *const*drivers;
+	gs_free char *driver = NULL;
 
 	g_return_val_if_fail (MM_IS_OBJECT (object), NULL);
 	modem_object = MM_OBJECT (object);
 
 	/* Ensure we have the 'Modem' interface and the primary port at least */
 	modem_iface = mm_object_peek_modem (modem_object);
-	g_return_val_if_fail (!!modem_iface, NULL);
-	g_return_val_if_fail (!!mm_modem_get_primary_port (modem_iface), NULL);
+	g_return_val_if_fail (modem_iface, NULL);
+	g_return_val_if_fail (mm_modem_get_primary_port (modem_iface), NULL);
 
 	/* Build a single string with all drivers listed */
-	drivers = g_strjoinv (", ", (gchar **)mm_modem_get_drivers (modem_iface));
-
-	modem = 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),
-	                      NM_MODEM_CONTROL_PORT, mm_modem_get_primary_port (modem_iface),
-	                      NM_MODEM_DATA_PORT, NULL, /* We don't know it until bearer created */
-	                      NM_MODEM_IP_TYPES, mm_ip_family_to_nm (mm_modem_get_supported_ip_families (modem_iface)),
-	                      NM_MODEM_STATE, (int) mm_state_to_nm (mm_modem_get_state (modem_iface)),
-	                      NM_MODEM_DEVICE_ID, mm_modem_get_device_identifier (modem_iface),
-	                      NM_MODEM_BROADBAND_MODEM, modem_object,
-	                      NM_MODEM_DRIVER, drivers,
-	                      NULL);
-	g_free (drivers);
-	return modem;
+	drivers = mm_modem_get_drivers (modem_iface);
+	if (drivers)
+		driver = g_strjoinv (", ", (char **) drivers);
+
+	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),
+	                     NM_MODEM_CONTROL_PORT, mm_modem_get_primary_port (modem_iface),
+	                     NM_MODEM_IP_TYPES, mm_ip_family_to_nm (mm_modem_get_supported_ip_families (modem_iface)),
+	                     NM_MODEM_STATE, (int) mm_state_to_nm (mm_modem_get_state (modem_iface)),
+	                     NM_MODEM_DEVICE_ID, mm_modem_get_device_identifier (modem_iface),
+	                     NM_MODEM_BROADBAND_MODEM, modem_object,
+	                     NM_MODEM_DRIVER, driver,
+	                     NULL);
 }
 
 static void
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
index 811c3afb..a1c6aef2 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -836,6 +836,7 @@ context_property_changed (GDBusProxy *proxy,
 	guint32 address_network, gateway_network;
 	guint32 ip4_route_table, ip4_route_metric;
 	int ifindex;
+	GError *error = NULL;
 
 	_LOGD ("PropertyChanged: %s", property);
 
@@ -860,27 +861,26 @@ context_property_changed (GDBusProxy *proxy,
 		_LOGW ("Settings 'Interface' missing");
 		goto out;
 	}
-	if (!interface || !interface[0]) {
-		_LOGW ("Settings 'Interface'; empty");
-		goto out;
-	}
 
-	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, interface);
-	if (ifindex <= 0) {
-		_LOGW ("Interface \"%s\" not found", interface);
+	_LOGD ("Interface: %s", interface);
+	if (!nm_modem_set_data_port (NM_MODEM (self),
+	                             NM_PLATFORM_GET,
+	                             interface,
+	                             NM_MODEM_IP_METHOD_STATIC,
+	                             NM_MODEM_IP_METHOD_UNKNOWN,
+	                             0,
+	                             &error)) {
+		_LOGW ("failed to connect to modem: %s", error->message);
+		g_clear_error (&error);
 		goto out;
 	}
 
-	_LOGD ("Interface: %s", interface);
-	g_object_set (self,
-	              NM_MODEM_DATA_PORT, interface,
-	              NM_MODEM_IP4_METHOD, NM_MODEM_IP_METHOD_STATIC,
-	              NULL);
+	ifindex = nm_modem_get_ip_ifindex (NM_MODEM (self));
+	nm_assert (ifindex > 0);
 
 	/* TODO: verify handling of ip4_config; check other places it's used... */
 	g_clear_object (&priv->ip4_config);
 
-
 	priv->ip4_config = nm_ip4_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET),
 	                                      ifindex);
 
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index 010a2b60..61b7247e 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -44,13 +44,10 @@
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMModem,
 	PROP_CONTROL_PORT,
-	PROP_DATA_PORT,
+	PROP_IP_IFINDEX,
 	PROP_PATH,
 	PROP_UID,
 	PROP_DRIVER,
-	PROP_IP4_METHOD,
-	PROP_IP6_METHOD,
-	PROP_IP_TIMEOUT,
 	PROP_STATE,
 	PROP_DEVICE_ID,
 	PROP_SIM_ID,
@@ -79,7 +76,12 @@ typedef struct _NMModemPrivate {
 	char *driver;
 	char *control_port;
 	char *data_port;
-	char *ppp_iface;
+
+	/* TODO: ip_iface is solely used for nm_modem_owns_port().
+	 * We should rework the code that it's not necessary */
+	char *ip_iface;
+
+	int ip_ifindex;
 	NMModemIPMethod ip4_method;
 	NMModemIPMethod ip6_method;
 	NMUtilsIPv6IfaceId iid;
@@ -96,7 +98,7 @@ typedef struct _NMModemPrivate {
 	guint32 secrets_tries;
 	NMActRequestGetSecretsCallId *secrets_id;
 
-	guint32 mm_ip_timeout;
+	guint mm_ip_timeout;
 
 	guint32 ip4_route_table;
 	guint32 ip4_route_metric;
@@ -153,6 +155,10 @@ _nmlog_prefix (char *prefix, NMModem *self)
     } G_STMT_END
 
 /*****************************************************************************/
+
+static void _set_ip_ifindex (NMModem *self, int ifindex, const char *ifname);
+
+/*****************************************************************************/
 /* State/enabled/connected */
 
 static const char *state_table[] = {
@@ -450,20 +456,28 @@ ppp_state_changed (NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_
 }
 
 static void
-set_data_port (NMModem *self, const char *new_data_port)
+ppp_ifindex_set (NMPPPManager *ppp_manager,
+                 int ifindex,
+                 const char *iface,
+                 gpointer user_data)
 {
-	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
+	NMModem *self = NM_MODEM (user_data);
 
-	if (g_strcmp0 (priv->data_port, new_data_port) != 0) {
-		g_free (priv->data_port);
-		priv->data_port = g_strdup (new_data_port);
-		_notify (self, PROP_DATA_PORT);
+	nm_assert (ifindex >= 0);
+	nm_assert (NM_MODEM_GET_PRIVATE (self)->ppp_manager == ppp_manager);
+
+	if (ifindex <= 0 && iface) {
+		/* this might happen, if the ifname was already deleted
+		 * and we failed to resolve ifindex.
+		 *
+		 * Forget about the name. */
+		iface = NULL;
 	}
+	_set_ip_ifindex (self, ifindex, iface);
 }
 
 static void
 ppp_ip4_config (NMPPPManager *ppp_manager,
-                const char *iface,
                 NMIP4Config *config,
                 gpointer user_data)
 {
@@ -475,9 +489,6 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 	guint32 good_dns2 = htonl (0x04020202);  /* GTE nameserver */
 	gboolean dns_workaround = FALSE;
 
-	/* Notify about the new data port to use */
-	set_data_port (self, iface);
-
 	/* Work around a PPP bug (#1732) which causes many mobile broadband
 	 * providers to return 10.11.12.13 and 10.11.12.14 for the DNS servers.
 	 * Apparently fixed in ppp-2.4.5 but we've had some reports that this is
@@ -519,16 +530,12 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 
 static void
 ppp_ip6_config (NMPPPManager *ppp_manager,
-                const char *iface,
                 const NMUtilsIPv6IfaceId *iid,
                 NMIP6Config *config,
                 gpointer user_data)
 {
 	NMModem *self = NM_MODEM (user_data);
 
-	/* Notify about the new data port to use */
-	set_data_port (self, iface);
-
 	NM_MODEM_GET_PRIVATE (self)->iid = *iid;
 
 	nm_modem_emit_ip6_config_result (self, config, NULL);
@@ -557,6 +564,18 @@ port_speed_is_zero (const char *port)
 {
 	struct termios options;
 	nm_auto_close int fd = -1;
+	gs_free char *path = NULL;
+
+	nm_assert (port);
+
+	if (port[0] != '/') {
+		if (   !port[0]
+		    || strchr (port, '/')
+		    || NM_IN_STRSET (port, ".", ".."))
+			return FALSE;
+		path = g_build_path ("/sys/class/tty", port, NULL);
+		port = path;
+	}
 
 	fd = open (port, O_RDWR | O_NONBLOCK | O_NOCTTY | O_CLOEXEC);
 	if (fd < 0)
@@ -598,6 +617,12 @@ ppp_stage3_ip_config_start (NMModem *self,
 			return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
+	if (!priv->data_port) {
+		_LOGE ("error starting PPP (no data port)");
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
 	/* Check if ModemManager requested a specific IP timeout to be used. If 0 reported,
 	 * use the default one (30s) */
 	if (priv->mm_ip_timeout > 0) {
@@ -629,9 +654,7 @@ ppp_stage3_ip_config_start (NMModem *self,
 	                              ip_timeout, baud_override, &error)) {
 		_LOGE ("error starting PPP: %s", error->message);
 		g_error_free (error);
-
 		g_clear_object (&priv->ppp_manager);
-
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
@@ -639,6 +662,9 @@ ppp_stage3_ip_config_start (NMModem *self,
 	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_STATE_CHANGED,
 	                  G_CALLBACK (ppp_state_changed),
 	                  self);
+	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IFINDEX_SET,
+	                  G_CALLBACK (ppp_ifindex_set),
+	                  self);
 	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IP4_CONFIG,
 	                  G_CALLBACK (ppp_ip4_config),
 	                  self);
@@ -1065,12 +1091,20 @@ nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection)
 gboolean
 nm_modem_complete_connection (NMModem *self,
                               NMConnection *connection,
-                              const GSList *existing_connections,
+                              NMConnection *const*existing_connections,
                               GError **error)
 {
-	if (NM_MODEM_GET_CLASS (self)->complete_connection)
-		return NM_MODEM_GET_CLASS (self)->complete_connection (self, connection, existing_connections, error);
-	return FALSE;
+	NMModemClass *klass;
+
+	klass = NM_MODEM_GET_CLASS (self);
+	if (!klass->complete_connection) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
+		             "Modem class %s had no complete_connection method",
+		             G_OBJECT_TYPE_NAME (self));
+		return FALSE;
+	}
+
+	return klass->complete_connection (self, connection, existing_connections, error);
 }
 
 /*****************************************************************************/
@@ -1095,7 +1129,10 @@ deactivate_cleanup (NMModem *self, NMDevice *device)
 
 	priv->in_bytes = priv->out_bytes = 0;
 
-	g_clear_object (&priv->ppp_manager);
+	if (priv->ppp_manager) {
+		g_signal_handlers_disconnect_by_data (priv->ppp_manager, self);
+		g_clear_object (&priv->ppp_manager);
+	}
 
 	if (device) {
 		g_return_if_fail (NM_IS_DEVICE (device));
@@ -1114,11 +1151,12 @@ deactivate_cleanup (NMModem *self, NMDevice *device)
 			}
 		}
 	}
+
+	nm_clear_g_free (&priv->data_port);
+	priv->mm_ip_timeout = 0;
 	priv->ip4_method = NM_MODEM_IP_METHOD_UNKNOWN;
 	priv->ip6_method = NM_MODEM_IP_METHOD_UNKNOWN;
-
-	g_free (priv->ppp_iface);
-	priv->ppp_iface = NULL;
+	_set_ip_ifindex (self, -1, NULL);
 }
 
 /*****************************************************************************/
@@ -1369,17 +1407,117 @@ nm_modem_get_control_port (NMModem *self)
 	return NM_MODEM_GET_PRIVATE (self)->control_port;
 }
 
-const char *
-nm_modem_get_data_port (NMModem *self)
+int
+nm_modem_get_ip_ifindex (NMModem *self)
 {
-	g_return_val_if_fail (NM_IS_MODEM (self), NULL);
+	NMModemPrivate *priv;
 
-	/* The ppp_iface takes precedence over the data interface when PPP is used,
-	 * since data_iface is the TTY over which PPP is run, and that TTY can't
-	 * do IP.  The caller really wants the thing that's doing IP.
-	 */
-	return NM_MODEM_GET_PRIVATE (self)->ppp_iface ?
-		NM_MODEM_GET_PRIVATE (self)->ppp_iface : NM_MODEM_GET_PRIVATE (self)->data_port;
+	g_return_val_if_fail (NM_IS_MODEM (self), 0);
+
+	priv = NM_MODEM_GET_PRIVATE (self);
+
+	/* internally we track an unset ip_ifindex as -1.
+	 * For the caller of nm_modem_get_ip_ifindex(), this
+	 * shall be zero too. */
+	return priv->ip_ifindex != -1 ? priv->ip_ifindex : 0;
+}
+
+static void
+_set_ip_ifindex (NMModem *self, int ifindex, const char *ifname)
+{
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
+
+	nm_assert (ifindex >= -1);
+	nm_assert ((ifindex > 0) == !!ifname);
+
+	if (!nm_streq0 (priv->ip_iface, ifname)) {
+		g_free (priv->ip_iface);
+		priv->ip_iface = g_strdup (ifname);
+	}
+
+	if (priv->ip_ifindex != ifindex) {
+		priv->ip_ifindex = ifindex;
+		_notify (self, PROP_IP_IFINDEX);
+	}
+}
+
+gboolean
+nm_modem_set_data_port (NMModem *self,
+                        NMPlatform *platform,
+                        const char *data_port,
+                        NMModemIPMethod ip4_method,
+                        NMModemIPMethod ip6_method,
+                        guint timeout,
+                        GError **error)
+{
+	NMModemPrivate *priv;
+	gboolean is_ppp;
+	int ifindex = -1;
+
+	g_return_val_if_fail (NM_IS_MODEM (self), FALSE);
+	g_return_val_if_fail (NM_IS_PLATFORM (platform), FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+
+	priv = NM_MODEM_GET_PRIVATE (self);
+
+	if (   priv->ppp_manager
+	    || priv->data_port
+	    || priv->ip_ifindex != -1) {
+		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		                     "cannot set data port in activated state");
+		/* this really shouldn't happen. Assert. */
+		g_return_val_if_reached (FALSE);
+	}
+
+	if (!data_port) {
+		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		                     "missing data port");
+		return FALSE;
+	}
+
+	is_ppp =    (ip4_method == NM_MODEM_IP_METHOD_PPP)
+	         || (ip6_method == NM_MODEM_IP_METHOD_PPP);
+	if (is_ppp) {
+		if (   !NM_IN_SET (ip4_method, NM_MODEM_IP_METHOD_UNKNOWN, NM_MODEM_IP_METHOD_PPP)
+		    || !NM_IN_SET (ip6_method, NM_MODEM_IP_METHOD_UNKNOWN, NM_MODEM_IP_METHOD_PPP)) {
+			g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+			                     "conflicting ip methods");
+			return FALSE;
+		}
+	} else if (   !NM_IN_SET (ip4_method, NM_MODEM_IP_METHOD_UNKNOWN, NM_MODEM_IP_METHOD_STATIC, NM_MODEM_IP_METHOD_AUTO)
+	           || !NM_IN_SET (ip6_method, NM_MODEM_IP_METHOD_UNKNOWN, NM_MODEM_IP_METHOD_STATIC, NM_MODEM_IP_METHOD_AUTO)
+	           || (   ip4_method == NM_MODEM_IP_METHOD_UNKNOWN
+	               && ip6_method == NM_MODEM_IP_METHOD_UNKNOWN)) {
+		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		                     "invalid ip methods");
+		return FALSE;
+	}
+
+	if (!is_ppp) {
+		ifindex = nm_platform_if_nametoindex (platform, data_port);
+		if (ifindex <= 0) {
+			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+			             "cannot find network interface %s", data_port);
+			return FALSE;
+		}
+		if (!nm_platform_process_events_ensure_link (platform, ifindex, data_port)) {
+			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+			             "cannot find network interface %s in platform cache", data_port);
+			return FALSE;
+		}
+	}
+
+	priv->mm_ip_timeout = timeout;
+	priv->ip4_method = ip4_method;
+	priv->ip6_method = ip6_method;
+	if (is_ppp) {
+		priv->data_port = g_strdup (data_port);
+		_set_ip_ifindex (self, -1, NULL);
+	} else {
+		priv->data_port = NULL;
+		_set_ip_ifindex (self, ifindex, data_port);
+	}
+	return TRUE;
 }
 
 gboolean
@@ -1392,15 +1530,10 @@ nm_modem_owns_port (NMModem *self, const char *iface)
 	if (NM_MODEM_GET_CLASS (self)->owns_port)
 		return NM_MODEM_GET_CLASS (self)->owns_port (self, iface);
 
-	/* Fall back to data/control ports */
-	if (priv->ppp_iface && (strcmp (priv->ppp_iface, iface) == 0))
-		return TRUE;
-	if (priv->data_port && (strcmp (priv->data_port, iface) == 0))
-		return TRUE;
-	if (priv->control_port && (strcmp (priv->control_port, iface) == 0))
-		return TRUE;
-
-	return FALSE;
+	return NM_IN_STRSET (iface,
+	                     priv->ip_iface,
+	                     priv->data_port,
+	                     priv->control_port);
 }
 
 gboolean
@@ -1500,7 +1633,8 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE ((NMModem *) object);
+	NMModem *self = NM_MODEM (object);
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
 
 	switch (prop_id) {
 	case PROP_PATH:
@@ -1512,21 +1646,12 @@ get_property (GObject *object, guint prop_id,
 	case PROP_CONTROL_PORT:
 		g_value_set_string (value, priv->control_port);
 		break;
-	case PROP_DATA_PORT:
-		g_value_set_string (value, nm_modem_get_data_port (NM_MODEM (object)));
+	case PROP_IP_IFINDEX:
+		g_value_set_int (value, nm_modem_get_ip_ifindex (self));
 		break;
 	case PROP_UID:
 		g_value_set_string (value, priv->uid);
 		break;
-	case PROP_IP4_METHOD:
-		g_value_set_uint (value, priv->ip4_method);
-		break;
-	case PROP_IP6_METHOD:
-		g_value_set_uint (value, priv->ip6_method);
-		break;
-	case PROP_IP_TIMEOUT:
-		g_value_set_uint (value, priv->mm_ip_timeout);
-		break;
 	case PROP_STATE:
 		g_value_set_int (value, priv->state);
 		break;
@@ -1569,23 +1694,10 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		priv->control_port = g_value_dup_string (value);
 		break;
-	case PROP_DATA_PORT:
-		g_free (priv->data_port);
-		priv->data_port = g_value_dup_string (value);
-		break;
 	case PROP_UID:
 		/* construct-only */
 		priv->uid = g_value_dup_string (value);
 		break;
-	case PROP_IP4_METHOD:
-		priv->ip4_method = g_value_get_uint (value);
-		break;
-	case PROP_IP6_METHOD:
-		priv->ip6_method = g_value_get_uint (value);
-		break;
-	case PROP_IP_TIMEOUT:
-		priv->mm_ip_timeout = g_value_get_uint (value);
-		break;
 	case PROP_STATE:
 		/* construct-only */
 		priv->state = g_value_get_int (value);
@@ -1623,6 +1735,7 @@ nm_modem_init (NMModem *self)
 	self->_priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_MODEM, NMModemPrivate);
 	priv = self->_priv;
 
+	priv->ip_ifindex = -1;
 	priv->ip4_route_table = RT_TABLE_MAIN;
 	priv->ip4_route_metric = 700;
 	priv->ip6_route_table = RT_TABLE_MAIN;
@@ -1638,7 +1751,7 @@ constructed (GObject *object)
 
 	priv = NM_MODEM_GET_PRIVATE (NM_MODEM (object));
 
-	g_return_if_fail (priv->data_port || priv->control_port);
+	g_return_if_fail (priv->control_port);
 }
 
 /*****************************************************************************/
@@ -1663,6 +1776,7 @@ finalize (GObject *object)
 	g_free (priv->driver);
 	g_free (priv->control_port);
 	g_free (priv->data_port);
+	g_free (priv->ip_iface);
 	g_free (priv->device_id);
 	g_free (priv->sim_id);
 	g_free (priv->sim_operator_id);
@@ -1711,33 +1825,11 @@ nm_modem_class_init (NMModemClass *klass)
 	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                          G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_DATA_PORT] =
-	     g_param_spec_string (NM_MODEM_DATA_PORT, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_IP4_METHOD] =
-	     g_param_spec_uint (NM_MODEM_IP4_METHOD, "", "",
-	                        NM_MODEM_IP_METHOD_UNKNOWN,
-	                        NM_MODEM_IP_METHOD_AUTO,
-	                        NM_MODEM_IP_METHOD_UNKNOWN,
-	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
-	                        G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_IP6_METHOD] =
-	     g_param_spec_uint (NM_MODEM_IP6_METHOD, "", "",
-	                        NM_MODEM_IP_METHOD_UNKNOWN,
-	                        NM_MODEM_IP_METHOD_AUTO,
-	                        NM_MODEM_IP_METHOD_UNKNOWN,
-	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
-	                        G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_IP_TIMEOUT] =
-	     g_param_spec_uint (NM_MODEM_IP_TIMEOUT, "", "",
-	                        0, 360, 20,
-	                        G_PARAM_READWRITE |
-	                        G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_IP_IFINDEX] =
+	     g_param_spec_int (NM_MODEM_IP_IFINDEX, "", "",
+	                       0, G_MAXINT, 0,
+	                       G_PARAM_READABLE |
+	                       G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_STATE] =
 	     g_param_spec_int (NM_MODEM_STATE, "", "",
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index 9546e4a1..3e281c0c 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -37,10 +37,7 @@
 #define NM_MODEM_PATH            "path"
 #define NM_MODEM_DRIVER          "driver"
 #define NM_MODEM_CONTROL_PORT    "control-port"
-#define NM_MODEM_DATA_PORT       "data-port"
-#define NM_MODEM_IP4_METHOD      "ip4-method"
-#define NM_MODEM_IP6_METHOD      "ip6-method"
-#define NM_MODEM_IP_TIMEOUT      "ip-timeout"
+#define NM_MODEM_IP_IFINDEX      "ip-ifindex"
 #define NM_MODEM_STATE           "state"
 #define NM_MODEM_DEVICE_ID       "device-id"
 #define NM_MODEM_SIM_ID          "sim-id"
@@ -129,7 +126,7 @@ typedef struct {
 
 	gboolean (*complete_connection)            (NMModem *modem,
 	                                            NMConnection *connection,
-	                                            const GSList *existing_connections,
+	                                            NMConnection *const*existing_connections,
 	                                            GError **error);
 
 	NMActStageReturn (*act_stage1_prepare)     (NMModem *modem,
@@ -167,13 +164,21 @@ GType nm_modem_get_type (void);
 const char *nm_modem_get_path            (NMModem *modem);
 const char *nm_modem_get_uid             (NMModem *modem);
 const char *nm_modem_get_control_port    (NMModem *modem);
-const char *nm_modem_get_data_port       (NMModem *modem);
+int         nm_modem_get_ip_ifindex      (NMModem *modem);
 const char *nm_modem_get_driver          (NMModem *modem);
 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);
 
+gboolean    nm_modem_set_data_port (NMModem *self,
+                                    NMPlatform *platform,
+                                    const char *data_port,
+                                    NMModemIPMethod ip4_method,
+                                    NMModemIPMethod ip6_method,
+                                    guint timeout,
+                                    GError **error);
+
 gboolean    nm_modem_owns_port        (NMModem *modem, const char *iface);
 
 void        nm_modem_get_capabilities (NMModem *self,
@@ -184,7 +189,7 @@ gboolean nm_modem_check_connection_compatible (NMModem *self, NMConnection *conn
 
 gboolean nm_modem_complete_connection (NMModem *self,
                                        NMConnection *connection,
-                                       const GSList *existing_connections,
+                                       NMConnection *const*existing_connections,
                                        GError **error);
 
 void nm_modem_get_route_parameters (NMModem *self,
diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c
index 663102de..f0aae040 100644
--- a/src/devices/wwan/nm-wwan-factory.c
+++ b/src/devices/wwan/nm-wwan-factory.c
@@ -80,7 +80,7 @@ modem_added_cb (NMModemManager *manager,
 {
 	NMWwanFactory *self = NM_WWAN_FACTORY (user_data);
 	NMDevice *device;
-	const char *driver, *port;
+	const char *driver;
 
 	/* Do nothing if the modem was consumed by some other plugin */
 	if (nm_device_factory_emit_component_added (NM_DEVICE_FACTORY (self), G_OBJECT (modem)))
@@ -93,10 +93,8 @@ modem_added_cb (NMModemManager *manager,
 	 * by the Bluetooth code during the connection process.
 	 */
 	if (driver && strstr (driver, "bluetooth")) {
-		port = nm_modem_get_data_port (modem);
-		if (!port)
-			port = nm_modem_get_control_port (modem);
-		nm_log_info (LOGD_MB, "ignoring modem '%s' (no associated Bluetooth device)", port);
+		nm_log_info (LOGD_MB, "ignoring modem '%s' (no associated Bluetooth device)",
+		             nm_modem_get_control_port (modem));
 		return;
 	}
 
diff --git a/src/dhcp/meson.build b/src/dhcp/meson.build
new file mode 100644
index 00000000..289a16ca
--- /dev/null
+++ b/src/dhcp/meson.build
@@ -0,0 +1,22 @@
+name = 'nm-dhcp-helper'
+
+cflags = [
+  '-DG_LOG_DOMAIN="@0@"'.format(name),
+  '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB',
+  '-DNMRUNDIR="@0@"'.format(nm_pkgrundir),
+]
+
+executable(
+  name,
+  name + '.c',
+  dependencies: nm_core_dep,
+  c_args: cflags,
+  link_args: ldflags_linker_script_binary,
+  link_depends: linker_script_binary,
+  install: true,
+  install_dir: nm_libexecdir
+)
+
+if enable_tests
+  subdir('tests')
+endif
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index ea3938d6..96c02653 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -52,23 +52,24 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
-	PROP_MULTI_IDX,
 	PROP_ADDR_FAMILY,
+	PROP_FLAGS,
+	PROP_HWADDR,
 	PROP_IFACE,
 	PROP_IFINDEX,
-	PROP_HWADDR,
-	PROP_UUID,
-	PROP_ROUTE_TABLE,
+	PROP_MULTI_IDX,
 	PROP_ROUTE_METRIC,
+	PROP_ROUTE_TABLE,
 	PROP_TIMEOUT,
+	PROP_UUID,
 );
 
 typedef struct _NMDhcpClientPrivate {
 	NMDedupMultiIndex *multi_idx;
 	char *       iface;
-	GByteArray * hwaddr;
+	GBytes *     hwaddr;
 	char *       uuid;
-	GByteArray * duid;
+	GBytes *     duid;
 	GBytes *     client_id;
 	char *       hostname;
 	pid_t        pid;
@@ -138,7 +139,7 @@ nm_dhcp_client_get_uuid (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->uuid;
 }
 
-const GByteArray *
+GBytes *
 nm_dhcp_client_get_duid (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
@@ -146,7 +147,7 @@ nm_dhcp_client_get_duid (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->duid;
 }
 
-const GByteArray *
+GBytes *
 nm_dhcp_client_get_hw_addr (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
@@ -238,20 +239,6 @@ nm_dhcp_client_set_client_id_bin (NMDhcpClient *self,
 	_set_client_id (self, b, TRUE);
 }
 
-void
-nm_dhcp_client_set_client_id_str (NMDhcpClient *self,
-                                  const char *dhcp_client_id)
-{
-	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
-	g_return_if_fail (!dhcp_client_id || dhcp_client_id[0]);
-
-	_set_client_id (self,
-	                dhcp_client_id
-	                  ? nm_dhcp_utils_client_id_string_to_bytes (dhcp_client_id)
-	                  : NULL,
-	                TRUE);
-}
-
 const char *
 nm_dhcp_client_get_hostname (NMDhcpClient *self)
 {
@@ -261,6 +248,14 @@ nm_dhcp_client_get_hostname (NMDhcpClient *self)
 }
 
 gboolean
+nm_dhcp_client_get_info_only (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->info_only;
+}
+
+gboolean
 nm_dhcp_client_get_use_fqdn (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
@@ -345,7 +340,7 @@ nm_dhcp_client_stop_pid (pid_t pid, const char *iface)
 }
 
 static void
-stop (NMDhcpClient *self, gboolean release, const GByteArray *duid)
+stop (NMDhcpClient *self, gboolean release, GBytes *duid)
 {
 	NMDhcpClientPrivate *priv;
 
@@ -359,7 +354,6 @@ stop (NMDhcpClient *self, gboolean release, const GByteArray *duid)
 		nm_dhcp_client_stop_pid (priv->pid, priv->iface);
 	}
 	priv->pid = -1;
-	priv->info_only = FALSE;
 }
 
 void
@@ -492,10 +486,9 @@ nm_dhcp_client_watch_child (NMDhcpClient *self, pid_t pid)
 
 gboolean
 nm_dhcp_client_start_ip4 (NMDhcpClient *self,
-                          const char *dhcp_client_id,
+                          GBytes *client_id,
                           const char *dhcp_anycast_addr,
                           const char *hostname,
-                          gboolean use_fqdn,
                           const char *last_ip4_address)
 {
 	NMDhcpClientPrivate *priv;
@@ -512,19 +505,19 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 	else
 		_LOGI ("activation: beginning transaction (timeout in %u seconds)", (guint) priv->timeout);
 
-	nm_dhcp_client_set_client_id_str (self, dhcp_client_id);
+	nm_dhcp_client_set_client_id (self, client_id);
 
 	g_clear_pointer (&priv->hostname, g_free);
 	priv->hostname = g_strdup (hostname);
-	priv->use_fqdn = use_fqdn;
 
 	return NM_DHCP_CLIENT_GET_CLASS (self)->ip4_start (self, dhcp_anycast_addr, last_ip4_address);
 }
 
-static GByteArray *
+static GBytes *
 generate_duid_from_machine_id (void)
 {
-	GByteArray *duid;
+	const int DUID_SIZE = 18;
+	guint8 *duid_buffer;
 	GChecksum *sum;
 	guint8 buffer[32]; /* SHA256 digest size */
 	gsize sumlen = sizeof (buffer);
@@ -532,6 +525,7 @@ generate_duid_from_machine_id (void)
 	uuid_t uuid;
 	gs_free char *machine_id_s = NULL;
 	gs_free char *str = NULL;
+	GBytes *duid;
 
 	machine_id_s = nm_utils_machine_id_read ();
 	if (nm_utils_machine_id_parse (machine_id_s, uuid)) {
@@ -554,36 +548,31 @@ generate_duid_from_machine_id (void)
 	 * u16: type (DUID-UUID = 4)
 	 * u8[16]: UUID bytes
 	 */
-	duid = g_byte_array_sized_new (18);
-	g_byte_array_append (duid, (guint8 *) &duid_type, sizeof (duid_type));
+	duid_buffer = g_malloc (DUID_SIZE);
+
+	G_STATIC_ASSERT_EXPR (sizeof (duid_type) == 2);
+	memcpy (&duid_buffer[0], &duid_type, 2);
 
 	/* Since SHA256 is 256 bits, but UUID is 128 bits, we just take the first
 	 * 128 bits of the SHA256 as the DUID-UUID.
 	 */
-	g_byte_array_append (duid, buffer, 16);
+	memcpy (&duid_buffer[2], buffer, 16);
 
+	duid = g_bytes_new_take (duid_buffer, DUID_SIZE);
 	nm_log_dbg (LOGD_DHCP, "dhcp: generated DUID %s",
 	            (str = nm_dhcp_utils_duid_to_string (duid)));
 	return duid;
 }
 
-static GByteArray *
+static GBytes *
 get_duid (NMDhcpClient *self)
 {
-	static GByteArray *duid = NULL;
-	GByteArray *copy = NULL;
+	static GBytes *duid = NULL;
 
-	if (G_UNLIKELY (duid == NULL)) {
+	if (G_UNLIKELY (!duid))
 		duid = generate_duid_from_machine_id ();
-		g_assert (duid);
-	}
-
-	if (G_LIKELY (duid)) {
-		copy = g_byte_array_sized_new (duid->len);
-		g_byte_array_append (copy, duid->data, duid->len);
-	}
 
-	return copy;
+	return g_bytes_ref (duid);
 }
 
 gboolean
@@ -591,7 +580,6 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
                           const char *dhcp_anycast_addr,
                           const struct in6_addr *ll_addr,
                           const char *hostname,
-                          gboolean info_only,
                           NMSettingIP6ConfigPrivacy privacy,
                           guint needed_prefixes)
 {
@@ -616,8 +604,6 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 	g_clear_pointer (&priv->hostname, g_free);
 	priv->hostname = g_strdup (hostname);
 
-	priv->info_only = info_only;
-
 	if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
 		_LOGI ("activation: beginning transaction (no timeout)");
 	else
@@ -626,7 +612,6 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 	return NM_DHCP_CLIENT_GET_CLASS (self)->ip6_start (self,
 	                                                   dhcp_anycast_addr,
 	                                                   ll_addr,
-	                                                   info_only,
 	                                                   privacy,
 	                                                   priv->duid,
 	                                                   needed_prefixes);
@@ -811,8 +796,8 @@ nm_dhcp_client_handle_event (gpointer unused,
 
 	old_state = priv->state;
 	new_state = reason_to_state (self, priv->iface, reason);
-	_LOGD ("DHCP reason '%s' -> state '%s'",
-	       reason, state_to_string (new_state));
+	_LOGD ("DHCP state '%s' -> '%s' (reason: '%s')",
+	       state_to_string (old_state), state_to_string (new_state), reason);
 
 	if (new_state == NM_DHCP_STATE_BOUND) {
 		GVariantIter iter;
@@ -923,8 +908,16 @@ set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
 	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE ((NMDhcpClient *) object);
+	guint flags;
 
 	switch (prop_id) {
+	case PROP_FLAGS:
+		/* construct-only */
+		flags = g_value_get_uint (value);
+		nm_assert ((flags & ~((guint) (NM_DHCP_CLIENT_FLAGS_INFO_ONLY | NM_DHCP_CLIENT_FLAGS_USE_FQDN))) == 0);
+		priv->info_only = NM_FLAGS_HAS (flags, NM_DHCP_CLIENT_FLAGS_INFO_ONLY);
+		priv->use_fqdn = NM_FLAGS_HAS (flags, NM_DHCP_CLIENT_FLAGS_USE_FQDN);
+		break;
 	case PROP_MULTI_IDX:
 		/* construct-only */
 		priv->multi_idx = g_value_get_pointer (value);
@@ -983,6 +976,8 @@ nm_dhcp_client_init (NMDhcpClient *self)
 	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_DHCP_CLIENT, NMDhcpClientPrivate);
 	self->_priv = priv;
 
+	c_list_init (&self->dhcp_client_lst);
+
 	priv->pid = -1;
 }
 
@@ -997,6 +992,8 @@ dispose (GObject *object)
 	 * the DHCP client.
 	 */
 
+	nm_assert (c_list_is_empty (&self->dhcp_client_lst));
+
 	watch_cleanup (self);
 	timeout_cleanup (self);
 
@@ -1004,16 +1001,8 @@ dispose (GObject *object)
 	g_clear_pointer (&priv->hostname, g_free);
 	g_clear_pointer (&priv->uuid, g_free);
 	g_clear_pointer (&priv->client_id, g_bytes_unref);
-
-	if (priv->hwaddr) {
-		g_byte_array_free (priv->hwaddr, TRUE);
-		priv->hwaddr = NULL;
-	}
-
-	if (priv->duid) {
-		g_byte_array_free (priv->duid, TRUE);
-		priv->duid = NULL;
-	}
+	g_clear_pointer (&priv->hwaddr, g_bytes_unref);
+	g_clear_pointer (&priv->duid, g_bytes_unref);
 
 	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
 
@@ -1054,7 +1043,7 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 
 	obj_properties[PROP_HWADDR] =
 	    g_param_spec_boxed (NM_DHCP_CLIENT_HWADDR, "", "",
-	                        G_TYPE_BYTE_ARRAY,
+	                        G_TYPE_BYTES,
 	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                        G_PARAM_STATIC_STRINGS);
 
@@ -1088,6 +1077,12 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                       G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_FLAGS] =
+	    g_param_spec_uint (NM_DHCP_CLIENT_FLAGS, "", "",
+	                       0, G_MAXUINT32, 0,
+	                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	signals[SIGNAL_STATE_CHANGED] =
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 2c634168..0d92d743 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -34,15 +34,16 @@
 #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_INTERFACE "iface"
-#define NM_DHCP_CLIENT_ADDR_FAMILY "addr-family"
-#define NM_DHCP_CLIENT_IFINDEX   "ifindex"
-#define NM_DHCP_CLIENT_HWADDR    "hwaddr"
-#define NM_DHCP_CLIENT_UUID      "uuid"
-#define NM_DHCP_CLIENT_ROUTE_TABLE  "route-table"
+#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_ROUTE_METRIC "route-metric"
-#define NM_DHCP_CLIENT_TIMEOUT   "timeout"
-#define NM_DHCP_CLIENT_MULTI_IDX "multi-idx"
+#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"
@@ -64,8 +65,14 @@ struct _NMDhcpClientPrivate;
 typedef struct {
 	GObject parent;
 	struct _NMDhcpClientPrivate *_priv;
+	CList dhcp_client_lst;
 } NMDhcpClient;
 
+typedef enum {
+	NM_DHCP_CLIENT_FLAGS_INFO_ONLY = (1LL <<  0),
+	NM_DHCP_CLIENT_FLAGS_USE_FQDN  = (1LL <<  1),
+} NMDhcpClientFlags;
+
 typedef struct {
 	GObjectClass parent;
 
@@ -78,14 +85,13 @@ typedef struct {
 	gboolean (*ip6_start)     (NMDhcpClient *self,
 	                           const char *anycast_addr,
 	                           const struct in6_addr *ll_addr,
-	                           gboolean info_only,
 	                           NMSettingIP6ConfigPrivacy privacy,
-	                           const GByteArray *duid,
+	                           GBytes *duid,
 	                           guint needed_prefixes);
 
 	void (*stop)              (NMDhcpClient *self,
 	                           gboolean release,
-	                           const GByteArray *duid);
+	                           GBytes *duid);
 
 	/**
 	 * get_duid:
@@ -96,7 +102,7 @@ typedef struct {
 	 * representation of the DUID.  If no DUID is found, %NULL should be
 	 * returned.
 	 */
-	GByteArray * (*get_duid) (NMDhcpClient *self);
+	GBytes *(*get_duid) (NMDhcpClient *self);
 
 	/* Signals */
 	void (*state_changed) (NMDhcpClient *self,
@@ -119,9 +125,9 @@ int         nm_dhcp_client_get_ifindex (NMDhcpClient *self);
 
 const char *nm_dhcp_client_get_uuid (NMDhcpClient *self);
 
-const GByteArray *nm_dhcp_client_get_duid (NMDhcpClient *self);
+GBytes *nm_dhcp_client_get_duid (NMDhcpClient *self);
 
-const GByteArray *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
+GBytes *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
 
 guint32 nm_dhcp_client_get_route_table (NMDhcpClient *self);
 
@@ -133,20 +139,20 @@ GBytes *nm_dhcp_client_get_client_id (NMDhcpClient *self);
 
 const char *nm_dhcp_client_get_hostname (NMDhcpClient *self);
 
+gboolean nm_dhcp_client_get_info_only (NMDhcpClient *self);
+
 gboolean nm_dhcp_client_get_use_fqdn (NMDhcpClient *self);
 
 gboolean nm_dhcp_client_start_ip4 (NMDhcpClient *self,
-                                   const char *dhcp_client_id,
+                                   GBytes *client_id,
                                    const char *dhcp_anycast_addr,
                                    const char *hostname,
-                                   gboolean use_fqdn,
                                    const char *last_ip4_address);
 
 gboolean nm_dhcp_client_start_ip6 (NMDhcpClient *self,
                                    const char *dhcp_anycast_addr,
                                    const struct in6_addr *ll_addr,
                                    const char *hostname,
-                                   gboolean info_only,
                                    NMSettingIP6ConfigPrivacy privacy,
                                    guint needed_prefixes);
 
@@ -179,8 +185,6 @@ void nm_dhcp_client_set_client_id_bin (NMDhcpClient *self,
                                        guint8 type,
                                        const guint8 *client_id,
                                        gsize len);
-void nm_dhcp_client_set_client_id_str (NMDhcpClient *self,
-                                       const char *dhcp_client_id);
 
 /*****************************************************************************
  * Client data
@@ -190,13 +194,6 @@ typedef struct {
 	GType (*get_type)(void);
 	const char *name;
 	const char *(*get_path) (void);
-	GSList *(*get_lease_ip_configs) (struct _NMDedupMultiIndex *multi_idx,
-	                                 int addr_family,
-	                                 const char *iface,
-	                                 int ifindex,
-	                                 const char *uuid,
-	                                 guint32 route_table,
-	                                 guint32 route_metric);
 } NMDhcpClientFactory;
 
 extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon;
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index 4df90d76..52923310 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -125,7 +125,7 @@ add_ip4_config (GString *str, GBytes *client_id, const char *hostname, gboolean
 		 * as long as all the characters are printable.
 		 */
 		for (i = 1; (p[0] == 0) && i < l; i++) {
-			if (!g_ascii_isprint (p[i]))
+			if (!g_ascii_isprint (p[i]) || p[i] == '\\' || p[i] == '"')
 				break;
 		}
 
@@ -138,8 +138,9 @@ add_ip4_config (GString *str, GBytes *client_id, const char *hostname, gboolean
 				g_string_append_printf (str, "%02x", (guint8) p[i]);
 			}
 		} else {
-			/* Printable; just add to the line minus the 'type' */
+			/* Printable; just add to the line with type 0 */
 			g_string_append_c (str, '"');
+			g_string_append (str, "\\x00");
 			g_string_append_len (str, p + 1, l - 1);
 			g_string_append_c (str, '"');
 		}
@@ -177,31 +178,60 @@ read_client_id (const char *str)
 {
 	gs_free char *s = NULL;
 	char *p;
+	int i = 0, j = 0;
 
 	nm_assert (!strncmp (str, CLIENTID_TAG, NM_STRLEN (CLIENTID_TAG)));
-
 	str += NM_STRLEN (CLIENTID_TAG);
+
+	if (!g_ascii_isspace (*str))
+		return NULL;
 	while (g_ascii_isspace (*str))
 		str++;
 
 	if (*str == '"') {
+		/* Parse string literal with escape sequences */
 		s = g_strdup (str + 1);
 		p = strrchr (s, '"');
 		if (p)
 			*p = '\0';
 		else
 			return NULL;
-	} else
-		s = g_strdup (str);
 
+		if (!s[0])
+			return NULL;
+
+		while (s[i]) {
+			if (   s[i] == '\\'
+			    && s[i + 1] == 'x'
+			    && g_ascii_isxdigit (s[i + 2])
+			    && g_ascii_isxdigit (s[i + 3])) {
+				s[j++] =  (g_ascii_xdigit_value (s[i + 2]) << 4)
+				         + g_ascii_xdigit_value (s[i + 3]);
+				i += 4;
+				continue;
+			}
+			if (   s[i] == '\\'
+			    && s[i + 1] >= '0' && s[i + 1] <= '7'
+			    && s[1 + 2] >= '0' && s[i + 2] <= '7'
+			    && s[1 + 3] >= '0' && s[i + 3] <= '7') {
+				s[j++] =    ((s[i + 1] - '0') << 6)
+				          + ((s[i + 2] - '0') << 3)
+				          + ( s[i + 3] - '0');
+				i += 4;
+				continue;
+			}
+			s[j++] = s[i++];
+		}
+		return g_bytes_new_take (g_steal_pointer (&s), j);
+	}
+
+	/* Otherwise, try to read a hexadecimal sequence */
+	s = g_strdup (str);
 	g_strchomp (s);
 	if (s[strlen (s) - 1] == ';')
 		s[strlen (s) - 1] = '\0';
 
-	if (!s[0])
-		return NULL;
-
-	return nm_dhcp_utils_client_id_string_to_bytes (s);
+	return nm_utils_hexstr2bin (s);
 }
 
 GBytes *
@@ -279,6 +309,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 
 	g_return_val_if_fail (!anycast_addr || nm_utils_hwaddr_valid (anycast_addr, ETH_ALEN), NULL);
 	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NULL);
+	nm_assert (!out_new_client_id || !*out_new_client_id);
 
 	new_contents = g_string_new (_("# Created by NetworkManager\n"));
 	fqdn_opts = g_ptr_array_sized_new (5);
@@ -332,6 +363,8 @@ nm_dhcp_dhclient_create_config (const char *interface,
 					continue;
 
 				/* Otherwise capture and return the existing client id */
+				if (out_new_client_id)
+					g_clear_pointer (out_new_client_id, g_bytes_unref);
 				NM_SET_OUT (out_new_client_id, read_client_id (p));
 			}
 
@@ -444,14 +477,20 @@ nm_dhcp_dhclient_create_config (const char *interface,
 
 /* Roughly follow what dhclient's quotify_buf() and pretty_escape() functions do */
 char *
-nm_dhcp_dhclient_escape_duid (const GByteArray *duid)
+nm_dhcp_dhclient_escape_duid (GBytes *duid)
 {
 	char *escaped;
-	const guint8 *s = duid->data;
+	const guint8 *s, *s0;
+	gsize len;
 	char *d;
 
-	d = escaped = g_malloc0 ((duid->len * 4) + 1);
-	while (s < (duid->data + duid->len)) {
+	g_return_val_if_fail (duid, NULL);
+
+	s0 = g_bytes_get_data (duid, &len);
+	s = s0;
+
+	d = escaped = g_malloc ((len * 4) + 1);
+	while (s < (s0 + len)) {
 		if (!g_ascii_isprint (*s)) {
 			*d++ = '\\';
 			*d++ = '0' + ((*s >> 6) & 0x7);
@@ -465,6 +504,7 @@ nm_dhcp_dhclient_escape_duid (const GByteArray *duid)
 		} else
 			*d++ = *s++;
 	}
+	*d++ = '\0';
 	return escaped;
 }
 
@@ -476,7 +516,7 @@ isoctal (const guint8 *p)
 	        && p[2] >= '0' && p[2] <= '7');
 }
 
-GByteArray *
+GBytes *
 nm_dhcp_dhclient_unescape_duid (const char *duid)
 {
 	GByteArray *unescaped;
@@ -507,7 +547,7 @@ nm_dhcp_dhclient_unescape_duid (const char *duid)
 			g_byte_array_append (unescaped, &p[i], 1);
 	}
 
-	return unescaped;
+	return g_byte_array_free_to_bytes (unescaped);
 
 error:
 	g_byte_array_free (unescaped, TRUE);
@@ -516,10 +556,10 @@ error:
 
 #define DUID_PREFIX "default-duid \""
 
-GByteArray *
+GBytes *
 nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
 {
-	GByteArray *duid = NULL;
+	GBytes *duid = NULL;
 	char *contents;
 	char **line, **split, *p, *e;
 
@@ -603,259 +643,3 @@ nm_dhcp_dhclient_save_duid (const char *leasefile,
 	g_string_free (s, TRUE);
 	return success;
 }
-
-static void
-add_lease_option (GHashTable *hash, char *line)
-{
-	char *spc;
-	size_t len;
-
-	/* Find the space after "option" */
-	spc = strchr (line, ' ');
-	if (!spc)
-		return;
-
-	/* Find the option tag's data, which is after the second space */
-	if (g_str_has_prefix (line, "option ")) {
-		while (g_ascii_isspace (*spc))
-			spc++;
-		spc = strchr (spc + 1, ' ');
-		if (!spc)
-			return;
-	}
-
-	/* Split the line at the space */
-	*spc = '\0';
-	spc++;
-
-	/* Kill the ';' at the end of the line, if any */
-	len = strlen (spc);
-	if (*(spc + len - 1) == ';')
-		*(spc + len - 1) = '\0';
-
-	/* Strip leading quote */
-	while (g_ascii_isspace (*spc))
-		spc++;
-	if (*spc == '"')
-		spc++;
-
-	/* Strip trailing quote */
-	len = strlen (spc);
-	if (len > 0 && spc[len - 1] == '"')
-		spc[len - 1] = '\0';
-
-	if (spc[0])
-		g_hash_table_insert (hash, g_strdup (line), g_strdup (spc));
-}
-
-#define LEASE_INVALID    G_MININT64
-static GTimeSpan
-lease_validity_span (const char *str_expire, GDateTime *now)
-{
-	GDateTime *expire = NULL;
-	struct tm expire_tm;
-	GTimeSpan span;
-
-	g_return_val_if_fail (now != NULL, LEASE_INVALID);
-	g_return_val_if_fail (str_expire != NULL, LEASE_INVALID);
-
-	/* Skip initial number (day of week?) */
-	if (!isdigit (*str_expire++))
-		return LEASE_INVALID;
-	if (!isspace (*str_expire++))
-		return LEASE_INVALID;
-	/* Read lease expiration (in UTC) */
-	if (!strptime (str_expire, "%t%Y/%m/%d %H:%M:%S", &expire_tm))
-		return LEASE_INVALID;
-
-	expire = g_date_time_new_utc (expire_tm.tm_year + 1900,
-	                              expire_tm.tm_mon + 1,
-	                              expire_tm.tm_mday,
-	                              expire_tm.tm_hour,
-	                              expire_tm.tm_min,
-	                              expire_tm.tm_sec);
-	if (!expire)
-		return LEASE_INVALID;
-
-	span = g_date_time_difference (expire, now);
-	g_date_time_unref (expire);
-
-	/* GDateTime only supports a range of less then 10000 years, so span can
-	 * not overflow or be equal to LEASE_INVALID */
-	return span;
-}
-
-/**
- * nm_dhcp_dhclient_read_lease_ip_configs:
- * @multi_idx: the multi index instance for the ip config object
- * @addr_family: whether to read IPv4 or IPv6 leases
- * @iface: the interface name to match leases with
- * @ifindex: interface index of @iface
- * @route_table: the route table for the default route.
- * @route_metric: the route metric for the default route.
- * @contents: the contents of a dhclient leasefile
- * @now: the current UTC date/time; pass %NULL to automatically use current
- *  UTC time.  Testcases may need a different value for 'now'
- *
- * Reads dhclient leases from @contents and parses them into either
- * #NMIP4Config or #NMIP6Config objects depending on the value of @addr_family.
- *
- * Returns: a #GSList of #NMIP4Config objects (if @addr_family is %AF_INET) or a list of
- * #NMIP6Config objects (if @addr_family is %AF_INET6) containing the lease data.
- */
-GSList *
-nm_dhcp_dhclient_read_lease_ip_configs (NMDedupMultiIndex *multi_idx,
-                                        int addr_family,
-                                        const char *iface,
-                                        int ifindex,
-                                        guint32 route_table,
-                                        guint32 route_metric,
-                                        const char *contents,
-                                        GDateTime *now)
-{
-	GSList *parsed = NULL, *iter, *leases = NULL;
-	char **line, **split = NULL;
-	GHashTable *hash = NULL;
-	gint32 now_monotonic_ts;
-
-	g_return_val_if_fail (contents != NULL, NULL);
-	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
-
-	split = g_strsplit_set (contents, "\n\r", -1);
-	if (!split)
-		return NULL;
-
-	for (line = split; line && *line; line++) {
-		*line = g_strstrip (*line);
-
-		if (*line[0] == '#') {
-			/* Comment */
-		} else if (!strcmp (*line, "}")) {
-			/* Lease ends */
-			parsed = g_slist_append (parsed, hash);
-			hash = NULL;
-		} else if (!strcmp (*line, "lease {")) {
-			/* Beginning of a new lease */
-			if (hash) {
-				/* Ignore malformed lease that doesn't end before new one starts */
-				g_hash_table_destroy (hash);
-			}
-
-			hash = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
-		} else if (hash && strlen (*line))
-			add_lease_option (hash, *line);
-	}
-	g_strfreev (split);
-
-	/* Check if the last lease in the file was properly ended */
-	if (hash) {
-		/* Ignore malformed lease that doesn't end before new one starts */
-		g_hash_table_destroy (hash);
-		hash = NULL;
-	}
-
-	if (now)
-		g_date_time_ref (now);
-	else
-		now = g_date_time_new_now_utc ();
-	now_monotonic_ts = nm_utils_get_monotonic_timestamp_s ();
-
-	for (iter = parsed; iter; iter = g_slist_next (iter)) {
-		NMIP4Config *ip4;
-		NMPlatformIP4Address address;
-		const char *value;
-		GTimeSpan expiry;
-		guint32 tmp, gw = 0;
-
-		hash = iter->data;
-
-		/* Make sure this lease is for the interface we want */
-		value = g_hash_table_lookup (hash, "interface");
-		if (!value || strcmp (value, iface))
-			continue;
-
-		value = g_hash_table_lookup (hash, "expire");
-		if (!value)
-			continue;
-		expiry = lease_validity_span (value, now);
-		if (expiry == LEASE_INVALID)
-			continue;
-
-		/* scale expiry to seconds (and CLAMP into the range of guint32) */
-		expiry = CLAMP (expiry / G_TIME_SPAN_SECOND, 0, NM_PLATFORM_LIFETIME_PERMANENT-1);
-		if (expiry <= 0) {
-			/* the address is already expired. Don't even add it. */
-			continue;
-		}
-
-		memset (&address, 0, sizeof (address));
-
-		/* IP4 address */
-		value = g_hash_table_lookup (hash, "fixed-address");
-		if (!value)
-			continue;
-		if (!inet_pton (AF_INET, value, &address.address))
-			continue;
-		address.peer_address = address.address;
-
-		/* Gateway */
-		value = g_hash_table_lookup (hash, "option routers");
-		if (!value)
-			continue;
-		if (!inet_pton (AF_INET, value, &gw))
-			continue;
-
-		/* Netmask */
-		value = g_hash_table_lookup (hash, "option subnet-mask");
-		if (value && inet_pton (AF_INET, value, &tmp))
-			address.plen = nm_utils_ip4_netmask_to_prefix (tmp);
-
-		/* Get default netmask for the IP according to appropriate class. */
-		if (!address.plen)
-			address.plen = _nm_utils_ip4_get_default_prefix (address.address);
-
-		address.timestamp = now_monotonic_ts;
-		address.lifetime = address.preferred = expiry;
-		address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
-
-		ip4 = nm_ip4_config_new (multi_idx, ifindex);
-		nm_ip4_config_add_address (ip4, &address);
-
-		{
-			const NMPlatformIP4Route r = {
-				.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
-				.gateway = gw,
-				.table_coerced = nm_platform_route_table_coerce (route_table),
-				.metric = route_metric,
-			};
-
-			nm_ip4_config_add_route (ip4, &r, NULL);
-		}
-
-		value = g_hash_table_lookup (hash, "option domain-name-servers");
-		if (value) {
-			char **dns, **dns_iter;
-
-			dns = g_strsplit_set (value, ",", -1);
-			for (dns_iter = dns; dns_iter && *dns_iter; dns_iter++) {
-				if (inet_pton (AF_INET, *dns_iter, &tmp))
-					nm_ip4_config_add_nameserver (ip4, tmp);
-			}
-			if (dns)
-				g_strfreev (dns);
-		}
-
-		value = g_hash_table_lookup (hash, "option domain-name");
-		if (value && value[0])
-			nm_ip4_config_add_domain (ip4, value);
-
-		/* FIXME: static routes */
-
-		leases = g_slist_append (leases, ip4);
-	}
-
-	g_date_time_unref (now);
-	g_slist_free_full (parsed, (GDestroyNotify) g_hash_table_destroy);
-	return leases;
-}
-
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.h b/src/dhcp/nm-dhcp-dhclient-utils.h
index 94de1963..fab9196a 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -33,25 +33,16 @@ char *nm_dhcp_dhclient_create_config (const char *interface,
                                       const char *orig_contents,
                                       GBytes **out_new_client_id);
 
-char *nm_dhcp_dhclient_escape_duid (const GByteArray *duid);
+char *nm_dhcp_dhclient_escape_duid (GBytes *duid);
 
-GByteArray *nm_dhcp_dhclient_unescape_duid (const char *duid);
+GBytes *nm_dhcp_dhclient_unescape_duid (const char *duid);
 
-GByteArray *nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error);
+GBytes *nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error);
 
 gboolean nm_dhcp_dhclient_save_duid (const char *leasefile,
                                      const char *escaped_duid,
                                      GError **error);
 
-GSList *nm_dhcp_dhclient_read_lease_ip_configs (struct _NMDedupMultiIndex *multi_idx,
-                                                int addr_family,
-                                                const char *iface,
-                                                int ifindex,
-                                                guint32 route_table,
-                                                guint32 route_metric,
-                                                const char *contents,
-                                                GDateTime *now);
-
 GBytes *nm_dhcp_dhclient_get_client_id_from_config_file (const char *path);
 
 #endif /* __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__ */
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index 74d920a8..738e9f91 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -158,32 +158,6 @@ get_dhclient_leasefile (int addr_family,
 	return NULL;
 }
 
-static GSList *
-nm_dhcp_dhclient_get_lease_ip_configs (NMDedupMultiIndex *multi_idx,
-                                       int addr_family,
-                                       const char *iface,
-                                       int ifindex,
-                                       const char *uuid,
-                                       guint32 route_table,
-                                       guint32 route_metric)
-{
-	gs_free char *contents = NULL;
-	gs_free char *leasefile = NULL;
-
-	leasefile = get_dhclient_leasefile (addr_family, iface, uuid, NULL);
-	if (!leasefile)
-		return NULL;
-
-	if (   g_file_test (leasefile, G_FILE_TEST_EXISTS)
-	    && g_file_get_contents (leasefile, &contents, NULL, NULL)
-	    && contents
-	    && contents[0]) {
-		return nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, addr_family, iface, ifindex,
-		                                               route_table, route_metric, contents, NULL);
-	}
-	return NULL;
-}
-
 static gboolean
 merge_dhclient_config (NMDhcpDhclient *self,
                        int addr_family,
@@ -338,7 +312,7 @@ create_dhclient_config (NMDhcpDhclient *self,
 static gboolean
 dhclient_start (NMDhcpClient *client,
                 const char *mode_opt,
-                const GByteArray *duid,
+                GBytes *duid,
                 gboolean release,
                 pid_t *out_pid,
                 int prefixes)
@@ -439,19 +413,19 @@ dhclient_start (NMDhcpClient *client,
 		while (prefixes--)
 			g_ptr_array_add (argv, (gpointer) "-P");
 	}
-	g_ptr_array_add (argv, (gpointer) "-sf");	/* Set script file */
+	g_ptr_array_add (argv, (gpointer) "-sf"); /* Set script file */
 	g_ptr_array_add (argv, (gpointer) nm_dhcp_helper_path);
 
 	if (pid_file) {
-		g_ptr_array_add (argv, (gpointer) "-pf");	/* Set pid file */
+		g_ptr_array_add (argv, (gpointer) "-pf"); /* Set pid file */
 		g_ptr_array_add (argv, (gpointer) pid_file);
 	}
 
-	g_ptr_array_add (argv, (gpointer) "-lf");	/* Set lease file */
+	g_ptr_array_add (argv, (gpointer) "-lf"); /* Set lease file */
 	g_ptr_array_add (argv, (gpointer) priv->lease_file);
 
 	if (priv->conf_file) {
-		g_ptr_array_add (argv, (gpointer) "-cf");	/* Set interface config file */
+		g_ptr_array_add (argv, (gpointer) "-cf"); /* Set interface config file */
 		g_ptr_array_add (argv, (gpointer) priv->conf_file);
 	}
 
@@ -518,8 +492,10 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	priv->conf_file = create_dhclient_config (self, AF_INET, iface, uuid, client_id, dhcp_anycast_addr,
 	                                          hostname, timeout, use_fqdn, &new_client_id);
 	if (priv->conf_file) {
-		if (new_client_id)
+		if (new_client_id) {
+			nm_assert (!client_id);
 			nm_dhcp_client_set_client_id (client, new_client_id);
+		}
 		success = dhclient_start (client, NULL, NULL, FALSE, NULL, 0);
 	} else
 		_LOGW ("error creating dhclient configuration file");
@@ -531,9 +507,8 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
-           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           const GByteArray *duid,
+           GBytes *duid,
            guint needed_prefixes)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
@@ -553,16 +528,19 @@ ip6_start (NMDhcpClient *client,
 		return FALSE;
 	}
 
-	return dhclient_start (client, info_only ? "-S" : "-N", duid, FALSE, NULL, needed_prefixes);
+	return dhclient_start (client,
+	                       nm_dhcp_client_get_info_only (NM_DHCP_CLIENT (self))
+	                         ? "-S"
+	                         : "-N",
+	                       duid, FALSE, NULL, needed_prefixes);
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
 
-	/* Chain up to parent */
 	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->stop (client, release, duid);
 
 	if (priv->conf_file)
@@ -603,12 +581,12 @@ state_changed (NMDhcpClient *client,
 	nm_dhcp_client_set_client_id (client, client_id);
 }
 
-static GByteArray *
+static GBytes *
 get_duid (NMDhcpClient *client)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
-	GByteArray *duid = NULL;
+	GBytes *duid = NULL;
 	char *leasefile;
 	GError *error = NULL;
 
@@ -642,7 +620,7 @@ get_duid (NMDhcpClient *client)
 	}
 
 	/* return our DUID, otherwise let the parent class make a default DUID */
-	return duid ? duid : NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->get_duid (client);
+	return duid ?: NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->get_duid (client);
 }
 
 /*****************************************************************************/
@@ -717,7 +695,6 @@ const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient = {
 	.name = "dhclient",
 	.get_type = nm_dhcp_dhclient_get_type,
 	.get_path = nm_dhcp_dhclient_get_path,
-	.get_lease_ip_configs = nm_dhcp_dhclient_get_lease_ip_configs,
 };
 
 #endif /* WITH_DHCLIENT */
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
index d7ddd194..82b3db4f 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -80,7 +80,7 @@ nm_dhcp_dhcpcanon_get_path (void)
 static gboolean
 dhcpcanon_start (NMDhcpClient *client,
                 const char *mode_opt,
-                const GByteArray *duid,
+                GBytes *duid,
                 gboolean release,
                 pid_t *out_pid,
                 int prefixes)
@@ -118,16 +118,16 @@ dhcpcanon_start (NMDhcpClient *client,
 	argv = g_ptr_array_new ();
 	g_ptr_array_add (argv, (gpointer) dhcpcanon_path);
 
-	g_ptr_array_add (argv, (gpointer) "-sf");	/* Set script file */
+	g_ptr_array_add (argv, (gpointer) "-sf"); /* Set script file */
 	g_ptr_array_add (argv, (gpointer) nm_dhcp_helper_path);
 
 	if (pid_file) {
-		g_ptr_array_add (argv, (gpointer) "-pf");	/* Set pid file */
+		g_ptr_array_add (argv, (gpointer) "-pf"); /* Set pid file */
 		g_ptr_array_add (argv, (gpointer) pid_file);
 	}
 
 	if (priv->conf_file) {
-		g_ptr_array_add (argv, (gpointer) "-cf");	/* Set interface config file */
+		g_ptr_array_add (argv, (gpointer) "-cf"); /* Set interface config file */
 		g_ptr_array_add (argv, (gpointer) priv->conf_file);
 	}
 
@@ -179,9 +179,8 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
-           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           const GByteArray *duid,
+           GBytes *duid,
            guint needed_prefixes)
 {
 	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
@@ -190,7 +189,7 @@ ip6_start (NMDhcpClient *client,
 	return FALSE;
 }
 static void
-stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 {
 	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
 	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
@@ -266,7 +265,6 @@ const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon = {
 	.name = "dhcpcanon",
 	.get_type = nm_dhcp_dhcpcanon_get_type,
 	.get_path = nm_dhcp_dhcpcanon_get_path,
-	.get_lease_ip_configs = NULL,
 };
 
 #endif /* WITH_DHCPCANON */
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index 66a31acf..c4bcb084 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -114,18 +114,18 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	argv = g_ptr_array_new ();
 	g_ptr_array_add (argv, (gpointer) dhcpcd_path);
 
-	g_ptr_array_add (argv, (gpointer) "-B");	/* Don't background on lease (disable fork()) */
+	g_ptr_array_add (argv, (gpointer) "-B");    /* Don't background on lease (disable fork()) */
 
-	g_ptr_array_add (argv, (gpointer) "-K");	/* Disable built-in carrier detection */
+	g_ptr_array_add (argv, (gpointer) "-K");    /* Disable built-in carrier detection */
 
-	g_ptr_array_add (argv, (gpointer) "-L");	/* Disable built-in IPv4LL */
+	g_ptr_array_add (argv, (gpointer) "-L");    /* Disable built-in IPv4LL */
 
 	/* --noarp. Don't request or claim the address by ARP; this also disables IPv4LL. */
 	g_ptr_array_add (argv, (gpointer) "-A");
 
-	g_ptr_array_add (argv, (gpointer) "-G");	/* Let NM handle routing */
+	g_ptr_array_add (argv, (gpointer) "-G");    /* Let NM handle routing */
 
-	g_ptr_array_add (argv, (gpointer) "-c");	/* Set script file */
+	g_ptr_array_add (argv, (gpointer) "-c");    /* Set script file */
 	g_ptr_array_add (argv, (gpointer) nm_dhcp_helper_path);
 
 #ifdef DHCPCD_SUPPORTS_IPV6
@@ -177,9 +177,8 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
-           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           const GByteArray *duid,
+           GBytes *duid,
            guint needed_prefixes)
 {
 	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
@@ -189,12 +188,11 @@ ip6_start (NMDhcpClient *client,
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 {
 	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
 	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (self);
 
-	/* Chain up to parent */
 	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcd_parent_class)->stop (client, release, duid);
 
 	if (priv->pid_file) {
@@ -253,7 +251,6 @@ const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcd = {
 	.name = "dhcpcd",
 	.get_type = nm_dhcp_dhcpcd_get_type,
 	.get_path = nm_dhcp_dhcpcd_get_path,
-	.get_lease_ip_configs = NULL,
 };
 
 #endif /* WITH_DHCPCD */
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c
index f50c5cec..8ea55061 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -134,8 +134,6 @@ main (int argc, char *argv[])
 	guint try_count = 0;
 	gint64 time_end;
 
-	nm_g_type_init ();
-
 	/* FIXME: g_dbus_connection_new_for_address_sync() tries to connect to the socket in
 	 * non-blocking mode, which can easily fail with EAGAIN, causing the creation of the
 	 * socket to fail with "Could not connect: Resource temporarily unavailable".
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index 1cce5a1c..d7d38e54 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -33,7 +33,7 @@
 #include "nm-dhcp-client.h"
 #include "nm-dhcp-manager.h"
 #include "nm-core-internal.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
 #include "NetworkManagerUtils.h"
 
 #define PRIV_SOCK_PATH            NMRUNDIR "/private-dhcp"
@@ -60,7 +60,7 @@ const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
 /*****************************************************************************/
 
 typedef struct {
-	NMBusManager *      dbus_mgr;
+	NMDBusManager *      dbus_mgr;
 	gulong              new_conn_id;
 	gulong              dis_conn_id;
 	GHashTable *        connections;
@@ -192,70 +192,52 @@ _method_call (GDBusConnection *connection,
 {
 	NMDhcpListener *self = NM_DHCP_LISTENER (user_data);
 
-	if (!nm_streq0 (interface_name, NM_DHCP_HELPER_SERVER_INTERFACE_NAME))
-		g_return_if_reached ();
-	if (!nm_streq0 (method_name, NM_DHCP_HELPER_SERVER_METHOD_NOTIFY))
-		g_return_if_reached ();
-	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})")))
-		g_return_if_reached ();
+	if (   !nm_streq (interface_name, NM_DHCP_HELPER_SERVER_INTERFACE_NAME)
+	    || !nm_streq (method_name, NM_DHCP_HELPER_SERVER_METHOD_NOTIFY)) {
+		g_dbus_method_invocation_return_error (invocation,
+		                                       G_DBUS_ERROR,
+		                                       G_DBUS_ERROR_UNKNOWN_METHOD,
+		                                       "Unknown method %s",
+		                                       method_name);
+		return;
+	}
 
 	_method_call_handle (self, parameters);
-
 	g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
+static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO (
+	NM_DHCP_HELPER_SERVER_INTERFACE_NAME,
+	.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+		NM_DEFINE_GDBUS_METHOD_INFO (
+			NM_DHCP_HELPER_SERVER_METHOD_NOTIFY,
+			.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+				NM_DEFINE_GDBUS_ARG_INFO ("data", "a{sv}"),
+			),
+		),
+	),
+);
+
 static guint
 _dbus_connection_register_object (NMDhcpListener *self,
                                   GDBusConnection *connection,
                                   GError **error)
 {
-	static GDBusArgInfo arg_info_notify_in = {
-		.ref_count = -1,
-		.name = "data",
-		.signature = "a{sv}",
-		.annotations = NULL,
-	};
-	static GDBusArgInfo *arg_infos_notify[] = {
-		&arg_info_notify_in,
-		NULL,
-	};
-	static GDBusMethodInfo method_info_notify = {
-		.ref_count = -1,
-		.name = NM_DHCP_HELPER_SERVER_METHOD_NOTIFY,
-		.in_args = arg_infos_notify,
-		.out_args = NULL,
-		.annotations = NULL,
-	};
-	static GDBusMethodInfo *method_infos[] = {
-		&method_info_notify,
-		NULL,
-	};
-	static GDBusInterfaceInfo interface_info = {
-		.ref_count = -1,
-		.name = NM_DHCP_HELPER_SERVER_INTERFACE_NAME,
-		.methods = method_infos,
-		.signals = NULL,
-		.properties = NULL,
-		.annotations = NULL,
-	};
-
-	static GDBusInterfaceVTable interface_vtable = {
+	static const GDBusInterfaceVTable interface_vtable = {
 		.method_call = _method_call,
-		.get_property = NULL,
-		.set_property = NULL,
 	};
 
 	return g_dbus_connection_register_object (connection,
 	                                          NM_DHCP_HELPER_SERVER_OBJECT_PATH,
-	                                          &interface_info,
-	                                          &interface_vtable,
+	                                          interface_info,
+	                                          NM_UNCONST_PTR (GDBusInterfaceVTable, &interface_vtable),
 	                                          self,
 	                                          NULL,
 	                                          error);
 }
 
 static void
-new_connection_cb (NMBusManager *mgr,
+new_connection_cb (NMDBusManager *mgr,
                    GDBusConnection *connection,
                    GDBusObjectManager *manager,
                    NMDhcpListener *self)
@@ -278,7 +260,7 @@ new_connection_cb (NMBusManager *mgr,
 }
 
 static void
-dis_connection_cb (NMBusManager *mgr,
+dis_connection_cb (NMDBusManager *mgr,
                    GDBusConnection *connection,
                    NMDhcpListener *self)
 {
@@ -300,18 +282,18 @@ nm_dhcp_listener_init (NMDhcpListener *self)
 	NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE (self);
 
 	/* Maps GDBusConnection :: signal-id */
-	priv->connections = g_hash_table_new (NULL, NULL);
+	priv->connections = g_hash_table_new (nm_direct_hash, NULL);
 
-	priv->dbus_mgr = nm_bus_manager_get ();
+	priv->dbus_mgr = nm_dbus_manager_get ();
 
 	/* Register the socket our DHCP clients will return lease info on */
-	nm_bus_manager_private_server_register (priv->dbus_mgr, PRIV_SOCK_PATH, PRIV_SOCK_TAG);
+	nm_dbus_manager_private_server_register (priv->dbus_mgr, PRIV_SOCK_PATH, PRIV_SOCK_TAG);
 	priv->new_conn_id = g_signal_connect (priv->dbus_mgr,
-	                                      NM_BUS_MANAGER_PRIVATE_CONNECTION_NEW "::" PRIV_SOCK_TAG,
+	                                      NM_DBUS_MANAGER_PRIVATE_CONNECTION_NEW "::" PRIV_SOCK_TAG,
 	                                      G_CALLBACK (new_connection_cb),
 	                                      self);
 	priv->dis_conn_id = g_signal_connect (priv->dbus_mgr,
-	                                      NM_BUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED "::" PRIV_SOCK_TAG,
+	                                      NM_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED "::" PRIV_SOCK_TAG,
 	                                      G_CALLBACK (dis_connection_cb),
 	                                      self);
 }
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index f5c7c84b..bf22872d 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -43,8 +43,8 @@
 
 typedef struct {
 	const NMDhcpClientFactory *client_factory;
-	GHashTable *        clients;
-	char *              default_hostname;
+	char *default_hostname;
+	CList dhcp_client_lst_head;
 } NMDhcpManagerPrivate;
 
 struct _NMDhcpManager {
@@ -98,21 +98,17 @@ static NMDhcpClient *
 get_client_for_ifindex (NMDhcpManager *manager, int addr_family, int ifindex)
 {
 	NMDhcpManagerPrivate *priv;
-	GHashTableIter iter;
-	gpointer value;
+	NMDhcpClient *client;
 
 	g_return_val_if_fail (NM_IS_DHCP_MANAGER (manager), NULL);
 	g_return_val_if_fail (ifindex > 0, NULL);
 
 	priv = NM_DHCP_MANAGER_GET_PRIVATE (manager);
 
-	g_hash_table_iter_init (&iter, priv->clients);
-	while (g_hash_table_iter_next (&iter, NULL, &value)) {
-		NMDhcpClient *candidate = NM_DHCP_CLIENT (value);
-
-		if (   nm_dhcp_client_get_ifindex (candidate) == ifindex
-		    && nm_dhcp_client_get_addr_family (candidate) == addr_family)
-			return candidate;
+	c_list_for_each_entry (client, &priv->dhcp_client_lst_head, dhcp_client_lst) {
+		if (   nm_dhcp_client_get_ifindex (client) == ifindex
+		    && nm_dhcp_client_get_addr_family (client) == addr_family)
+			return client;
 	}
 
 	return NULL;
@@ -129,13 +125,19 @@ static void
 remove_client (NMDhcpManager *self, NMDhcpClient *client)
 {
 	g_signal_handlers_disconnect_by_func (client, client_state_changed, self);
+	c_list_unlink (&client->dhcp_client_lst);
 
 	/* Stopping the client is left up to the controlling device
 	 * explicitly since we may want to quit NetworkManager but not terminate
 	 * the DHCP client.
 	 */
+}
 
-	g_hash_table_remove (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client);
+static void
+remove_client_unref (NMDhcpManager *self, NMDhcpClient *client)
+{
+	remove_client (self, client);
+	g_object_unref (client);
 }
 
 static void
@@ -147,7 +149,7 @@ client_state_changed (NMDhcpClient *client,
                       NMDhcpManager *self)
 {
 	if (state >= NM_DHCP_STATE_TIMEOUT)
-		remove_client (self, client);
+		remove_client_unref (self, client);
 }
 
 static NMDhcpClient *
@@ -156,12 +158,12 @@ client_start (NMDhcpManager *self,
               NMDedupMultiIndex *multi_idx,
               const char *iface,
               int ifindex,
-              const GByteArray *hwaddr,
+              GBytes *hwaddr,
               const char *uuid,
               guint32 route_table,
               guint32 route_metric,
               const struct in6_addr *ipv6_ll_addr,
-              const char *dhcp_client_id,
+              GBytes *dhcp_client_id,
               guint32 timeout,
               const char *dhcp_anycast_addr,
               const char *hostname,
@@ -179,23 +181,21 @@ client_start (NMDhcpManager *self,
 	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
 	g_return_val_if_fail (ifindex > 0, NULL);
 	g_return_val_if_fail (uuid != NULL, NULL);
+	g_return_val_if_fail (!dhcp_client_id || g_bytes_get_size (dhcp_client_id) >= 2, NULL);
 
 	priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
 
-	/* Ensure we have a usable DHCP client */
 	if (!priv->client_factory)
 		return NULL;
 
 	/* Kill any old client instance */
 	client = get_client_for_ifindex (self, addr_family, ifindex);
 	if (client) {
-		g_object_ref (client);
 		remove_client (self, client);
 		nm_dhcp_client_stop (client, FALSE);
 		g_object_unref (client);
 	}
 
-	/* And make a new one */
 	client = g_object_new (priv->client_factory->get_type (),
 	                       NM_DHCP_CLIENT_MULTI_IDX, multi_idx,
 	                       NM_DHCP_CLIENT_ADDR_FAMILY, addr_family,
@@ -206,21 +206,26 @@ client_start (NMDhcpManager *self,
 	                       NM_DHCP_CLIENT_ROUTE_TABLE, (guint) route_table,
 	                       NM_DHCP_CLIENT_ROUTE_METRIC, (guint) route_metric,
 	                       NM_DHCP_CLIENT_TIMEOUT, (guint) timeout,
+	                       NM_DHCP_CLIENT_FLAGS, (guint) (0
+	                           | (hostname_use_fqdn ? NM_DHCP_CLIENT_FLAGS_USE_FQDN  : 0)
+	                           | (info_only         ? NM_DHCP_CLIENT_FLAGS_INFO_ONLY : 0)
+	                       ),
 	                       NULL);
-	g_hash_table_insert (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client, g_object_ref (client));
+	nm_assert (client && c_list_is_empty (&client->dhcp_client_lst));
+	c_list_link_tail (&priv->dhcp_client_lst_head, &client->dhcp_client_lst);
 	g_signal_connect (client, NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, G_CALLBACK (client_state_changed), self);
 
 	if (addr_family == AF_INET)
-		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, hostname_use_fqdn, last_ip4_address);
+		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, last_ip4_address);
 	else
-		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, info_only, privacy, needed_prefixes);
+		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, privacy, needed_prefixes);
 
 	if (!success) {
-		remove_client (self, client);
-		client = NULL;
+		remove_client_unref (self, client);
+		return NULL;
 	}
 
-	return client;
+	return g_object_ref (client);
 }
 
 /* Caller owns a reference to the NMDhcpClient on return */
@@ -229,14 +234,14 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
                            NMDedupMultiIndex *multi_idx,
                            const char *iface,
                            int ifindex,
-                           const GByteArray *hwaddr,
+                           GBytes *hwaddr,
                            const char *uuid,
                            guint32 route_table,
                            guint32 route_metric,
                            gboolean send_hostname,
                            const char *dhcp_hostname,
                            const char *dhcp_fqdn,
-                           const char *dhcp_client_id,
+                           GBytes *dhcp_client_id,
                            guint32 timeout,
                            const char *dhcp_anycast_addr,
                            const char *last_ip_address)
@@ -285,7 +290,7 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
                            NMDedupMultiIndex *multi_idx,
                            const char *iface,
                            int ifindex,
-                           const GByteArray *hwaddr,
+                           GBytes *hwaddr,
                            const struct in6_addr *ll_addr,
                            const char *uuid,
                            guint32 route_table,
@@ -328,31 +333,6 @@ nm_dhcp_manager_set_default_hostname (NMDhcpManager *manager, const char *hostna
 	priv->default_hostname = g_strdup (hostname);
 }
 
-GSList *
-nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
-                                      NMDedupMultiIndex *multi_idx,
-                                      int addr_family,
-                                      const char *iface,
-                                      int ifindex,
-                                      const char *uuid,
-                                      guint32 route_table,
-                                      guint32 route_metric)
-{
-	NMDhcpManagerPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
-	g_return_val_if_fail (iface != NULL, NULL);
-	g_return_val_if_fail (ifindex >= -1, NULL);
-	g_return_val_if_fail (uuid != NULL, NULL);
-	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NULL);
-
-	priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
-	if (   priv->client_factory
-	    && priv->client_factory->get_lease_ip_configs)
-		return priv->client_factory->get_lease_ip_configs (multi_idx, addr_family, iface, ifindex, uuid, route_table, route_metric);
-	return NULL;
-}
-
 const char *
 nm_dhcp_manager_get_config (NMDhcpManager *self)
 {
@@ -378,6 +358,8 @@ nm_dhcp_manager_init (NMDhcpManager *self)
 	int i;
 	const NMDhcpClientFactory *client_factory = NULL;
 
+	c_list_init (&priv->dhcp_client_lst_head);
+
 	for (i = 0; i < G_N_ELEMENTS (_nm_dhcp_manager_factories); i++) {
 		const NMDhcpClientFactory *f = _nm_dhcp_manager_factories[i];
 
@@ -429,38 +411,21 @@ nm_dhcp_manager_init (NMDhcpManager *self)
 	nm_log_info (LOGD_DHCP, "dhcp-init: Using DHCP client '%s'", client_factory->name);
 
 	priv->client_factory = client_factory;
-	priv->clients = g_hash_table_new_full (g_direct_hash, g_direct_equal,
-	                                       NULL,
-	                                       (GDestroyNotify) g_object_unref);
 }
 
 static void
 dispose (GObject *object)
 {
-	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE ((NMDhcpManager *) object);
-	GList *values, *iter;
-
-	if (priv->clients) {
-		values = g_hash_table_get_values (priv->clients);
-		for (iter = values; iter; iter = g_list_next (iter))
-			remove_client (NM_DHCP_MANAGER (object), NM_DHCP_CLIENT (iter->data));
-		g_list_free (values);
-	}
-
-	G_OBJECT_CLASS (nm_dhcp_manager_parent_class)->dispose (object);
-}
-
-static void
-finalize (GObject *object)
-{
-	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE ((NMDhcpManager *) object);
+	NMDhcpManager *self = NM_DHCP_MANAGER (object);
+	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
+	NMDhcpClient *client, *client_safe;
 
-	g_free (priv->default_hostname);
+	c_list_for_each_entry_safe (client, client_safe, &priv->dhcp_client_lst_head, dhcp_client_lst)
+		remove_client_unref (self, client);
 
-	if (priv->clients)
-		g_hash_table_destroy (priv->clients);
+	G_OBJECT_CLASS (nm_dhcp_manager_parent_class)->dispose (object);
 
-	G_OBJECT_CLASS (nm_dhcp_manager_parent_class)->finalize (object);
+	nm_clear_g_free (&priv->default_hostname);
 }
 
 static void
@@ -468,6 +433,5 @@ nm_dhcp_manager_class_init (NMDhcpManagerClass *manager_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (manager_class);
 
-	object_class->finalize = finalize;
 	object_class->dispose = dispose;
 }
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index 078117ff..f8a7e31d 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -49,14 +49,14 @@ NMDhcpClient * nm_dhcp_manager_start_ip4     (NMDhcpManager *manager,
                                               struct _NMDedupMultiIndex *multi_idx,
                                               const char *iface,
                                               int ifindex,
-                                              const GByteArray *hwaddr,
+                                              GBytes *hwaddr,
                                               const char *uuid,
                                               guint32 route_table,
                                               guint32 route_metric,
                                               gboolean send_hostname,
                                               const char *dhcp_hostname,
                                               const char *dhcp_fqdn,
-                                              const char *dhcp_client_id,
+                                              GBytes *dhcp_client_id,
                                               guint32 timeout,
                                               const char *dhcp_anycast_addr,
                                               const char *last_ip_address);
@@ -65,7 +65,7 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               struct _NMDedupMultiIndex *multi_idx,
                                               const char *iface,
                                               int ifindex,
-                                              const GByteArray *hwaddr,
+                                              GBytes *hwaddr,
                                               const struct in6_addr *ll_addr,
                                               const char *uuid,
                                               guint32 route_table,
@@ -78,15 +78,6 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               NMSettingIP6ConfigPrivacy privacy,
                                               guint needed_prefixes);
 
-GSList *       nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
-                                                     struct _NMDedupMultiIndex *multi_idx,
-                                                     int addr_family,
-                                                     const char *iface,
-                                                     int ifindex,
-                                                     const char *uuid,
-                                                     guint32 route_table,
-                                                     guint32 route_metric);
-
 /* For testing only */
 extern const char* nm_dhcp_helper_path;
 
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index f79b7cb1..4f37f069 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -29,6 +29,7 @@
 #include <net/if_arp.h>
 
 #include "nm-utils/nm-dedup-multi.h"
+#include "nm-utils/unaligned.h"
 
 #include "nm-utils.h"
 #include "nm-dhcp-utils.h"
@@ -60,8 +61,7 @@ typedef struct {
 
 	guint request_count;
 
-	gboolean privacy;
-	gboolean info_only;
+	bool privacy:1;
 } NMDhcpSystemdPrivate;
 
 struct _NMDhcpSystemd {
@@ -451,36 +451,6 @@ get_leasefile_path (int addr_family, const char *iface, const char *uuid)
 	                        iface);
 }
 
-static GSList *
-nm_dhcp_systemd_get_lease_ip_configs (NMDedupMultiIndex *multi_idx,
-                                      int addr_family,
-                                      const char *iface,
-                                      int ifindex,
-                                      const char *uuid,
-                                      guint32 route_table,
-                                      guint32 route_metric)
-{
-	GSList *leases = NULL;
-	gs_free char *path = NULL;
-	sd_dhcp_lease *lease = NULL;
-	NMIP4Config *ip4_config;
-	int r;
-
-	if (addr_family != AF_INET)
-		return NULL;
-
-	path = get_leasefile_path (addr_family, iface, uuid);
-	r = dhcp_lease_load (&lease, path);
-	if (r == 0 && lease) {
-		ip4_config = lease_to_ip4_config (multi_idx, iface, ifindex, lease, NULL, route_table, route_metric, FALSE, NULL);
-		if (ip4_config)
-			leases = g_slist_append (leases, ip4_config);
-		sd_dhcp_lease_unref (lease);
-	}
-
-	return leases;
-}
-
 /*****************************************************************************/
 
 static void
@@ -584,14 +554,16 @@ dhcp_event_cb (sd_dhcp_client *client, int event, gpointer user_data)
 }
 
 static guint16
-get_arp_type (const GByteArray *hwaddr)
+get_arp_type (GBytes *hwaddr)
 {
-	if (hwaddr->len == ETH_ALEN)
+	switch (g_bytes_get_size (hwaddr)) {
+	case ETH_ALEN:
 		return ARPHRD_ETHER;
-	else if (hwaddr->len == INFINIBAND_ALEN)
+	case INFINIBAND_ALEN:
 		return ARPHRD_INFINIBAND;
-	else
+	default:
 		return ARPHRD_NONE;
+	}
 }
 
 static gboolean
@@ -600,7 +572,7 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 	const char *iface = nm_dhcp_client_get_iface (client);
-	const GByteArray *hwaddr;
+	GBytes *hwaddr;
 	sd_dhcp_lease *lease = NULL;
 	GBytes *override_client_id;
 	const uint8_t *client_id = NULL;
@@ -609,7 +581,6 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	const char *hostname;
 	int r, i;
 	gboolean success = FALSE;
-	guint16 arp_type;
 
 	g_assert (priv->client4 == NULL);
 	g_assert (priv->client6 == NULL);
@@ -633,16 +604,14 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 
 	hwaddr = nm_dhcp_client_get_hw_addr (client);
 	if (hwaddr) {
-		arp_type= get_arp_type (hwaddr);
-		if (arp_type == ARPHRD_NONE) {
-			_LOGW ("failed to determine ARP type");
-			goto error;
-		}
+		const uint8_t *data;
+		gsize len;
 
+		data = g_bytes_get_data (hwaddr, &len);
 		r = sd_dhcp_client_set_mac (priv->client4,
-		                            hwaddr->data,
-		                            hwaddr->len,
-		                            arp_type);
+		                            data,
+		                            len,
+		                            get_arp_type (hwaddr));
 		if (r < 0) {
 			_LOGW ("failed to set MAC address (%d)", r);
 			goto error;
@@ -661,12 +630,6 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 		goto error;
 	}
 
-	r = sd_dhcp_client_set_request_broadcast (priv->client4, true);
-	if (r < 0) {
-		_LOGW ("failed to enable broadcast mode (%d)", r);
-		goto error;
-	}
-
 	dhcp_lease_load (&lease, priv->lease_file);
 
 	if (last_ip4_address)
@@ -854,7 +817,7 @@ bound6_handle (NMDhcpSystemd *self)
 	                                  lease,
 	                                  options,
 	                                  TRUE,
-	                                  priv->info_only,
+	                                  nm_dhcp_client_get_info_only (NM_DHCP_CLIENT (self)),
 	                                  &error);
 
 	if (ip6_config) {
@@ -900,24 +863,29 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
-           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           const GByteArray *duid,
+           GBytes *duid,
            guint needed_prefixes)
 {
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 	const char *iface = nm_dhcp_client_get_iface (client);
-	const GByteArray *hwaddr;
+	GBytes *hwaddr;
+	const char *hostname;
 	int r, i;
+	const guint8 *duid_arr;
+	gsize duid_len;
 
 	g_assert (priv->client4 == NULL);
 	g_assert (priv->client6 == NULL);
 	g_return_val_if_fail (duid != NULL, FALSE);
 
+	duid_arr = g_bytes_get_data (duid, &duid_len);
+	if (!duid_arr || duid_len < 2)
+		g_return_val_if_reached (FALSE);
+
 	g_free (priv->lease_file);
 	priv->lease_file = get_leasefile_path (AF_INET6, iface, nm_dhcp_client_get_uuid (client));
-	priv->info_only = info_only;
 
 	r = sd_dhcp6_client_new (&priv->client6);
 	if (r < 0) {
@@ -932,16 +900,13 @@ ip6_start (NMDhcpClient *client,
 
 	_LOGT ("dhcp-client6: set %p", priv->client6);
 
-	if (info_only)
-	    sd_dhcp6_client_set_information_request (priv->client6, 1);
+	if (nm_dhcp_client_get_info_only (client))
+		sd_dhcp6_client_set_information_request (priv->client6, 1);
 
-	/* NM stores the entire DUID which includes the uint16 "type", while systemd
-	 * wants the type passed separately from the following data.
-	 */
 	r = sd_dhcp6_client_set_duid (priv->client6,
-	                              ntohs (((const guint16 *) duid->data)[0]),
-	                              duid->data + 2,
-	                              duid->len - 2);
+	                              unaligned_read_be16 (&duid_arr[0]),
+	                              &duid_arr[2],
+	                              duid_len - 2);
 	if (r < 0) {
 		_LOGW ("failed to set DUID (%d)", r);
 		return FALSE;
@@ -955,9 +920,13 @@ ip6_start (NMDhcpClient *client,
 
 	hwaddr = nm_dhcp_client_get_hw_addr (client);
 	if (hwaddr) {
+		const uint8_t *data;
+		gsize len;
+
+		data = g_bytes_get_data (hwaddr, &len);
 		r = sd_dhcp6_client_set_mac (priv->client6,
-		                             hwaddr->data,
-		                             hwaddr->len,
+		                             data,
+		                             len,
 		                             get_arp_type (hwaddr));
 		if (r < 0) {
 			_LOGW ("failed to set MAC address (%d)", r);
@@ -989,6 +958,13 @@ ip6_start (NMDhcpClient *client,
 		goto error;
 	}
 
+	hostname = nm_dhcp_client_get_hostname (client);
+	r = sd_dhcp6_client_set_fqdn (priv->client6, hostname);
+	if (r < 0) {
+		_LOGW ("failed to set DHCP hostname to '%s' (%d)", hostname, r);
+		goto error;
+	}
+
 	r = sd_dhcp6_client_start (priv->client6);
 	if (r < 0) {
 		_LOGW ("failed to start client (%d)", r);
@@ -1006,12 +982,14 @@ error:
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 {
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 	int r = 0;
 
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_systemd_parent_class)->stop (client, release, duid);
+
 	_LOGT ("dhcp-client%d: stop %p",
 	       priv->client4 ? '4' : '6',
 	       priv->client4 ? (gpointer) priv->client4 : (gpointer) priv->client6);
@@ -1074,5 +1052,4 @@ const NMDhcpClientFactory _nm_dhcp_client_factory_internal = {
 	.name = "internal",
 	.get_type = nm_dhcp_systemd_get_type,
 	.get_path = NULL,
-	.get_lease_ip_configs = nm_dhcp_systemd_get_lease_ip_configs,
 };
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index 50ca2abe..9185a135 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -721,11 +721,15 @@ error:
 }
 
 char *
-nm_dhcp_utils_duid_to_string (const GByteArray *duid)
+nm_dhcp_utils_duid_to_string (GBytes *duid)
 {
+	gconstpointer data;
+	gsize len;
+
 	g_return_val_if_fail (duid != NULL, NULL);
 
-	return _nm_utils_bin2str (duid->data, duid->len, FALSE);
+	data = g_bytes_get_data (duid, &len);
+	return _nm_utils_bin2str (data, len, FALSE);
 }
 
 /**
diff --git a/src/dhcp/nm-dhcp-utils.h b/src/dhcp/nm-dhcp-utils.h
index 32140f48..5c127bd1 100644
--- a/src/dhcp/nm-dhcp-utils.h
+++ b/src/dhcp/nm-dhcp-utils.h
@@ -39,7 +39,7 @@ NMIP6Config *nm_dhcp_utils_ip6_config_from_options (struct _NMDedupMultiIndex *m
 
 NMPlatformIP6Address nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options);
 
-char *       nm_dhcp_utils_duid_to_string          (const GByteArray *duid);
+char *nm_dhcp_utils_duid_to_string (GBytes *duid);
 
 GBytes *     nm_dhcp_utils_client_id_string_to_bytes (const char *client_id);
 
diff --git a/src/dhcp/tests/leases/basic.leases b/src/dhcp/tests/leases/basic.leases
deleted file mode 100644
index 703d9247..00000000
--- a/src/dhcp/tests/leases/basic.leases
+++ /dev/null
@@ -1,31 +0,0 @@
-lease {
-  interface "wlan0";
-  fixed-address 192.168.1.180;
-  option subnet-mask 255.255.255.0;
-  option routers 192.168.1.1;
-  option dhcp-lease-time 600;
-  option dhcp-message-type 5;
-  option domain-name-servers 192.168.1.1;
-  option dhcp-server-identifier 192.168.1.1;
-  option broadcast-address 192.168.1.255;
-  renew 5 2013/11/01 19:56:15;
-  rebind 5 2013/11/01 20:00:44;
-  expire 5 2013/11/01 20:01:59;
-}
-lease {
-  interface "wlan0";
-  fixed-address 10.77.52.141;
-  option subnet-mask 255.0.0.0;
-  option dhcp-lease-time 1200;
-  option routers 10.77.52.254;
-  option dhcp-message-type 5;
-  option dhcp-server-identifier 10.77.52.254;
-  option domain-name-servers 8.8.8.8,8.8.4.4;
-  option dhcp-renewal-time 600;
-  option dhcp-rebinding-time 1050;
-  option domain-name "morriesguest.local";
-  renew 5 2013/11/01 20:01:08;
-  rebind 5 2013/11/01 20:05:00;
-  expire 5 2013/11/01 20:06:15;
-}
-
diff --git a/src/dhcp/tests/leases/malformed1.leases b/src/dhcp/tests/leases/malformed1.leases
deleted file mode 100644
index 401d982a..00000000
--- a/src/dhcp/tests/leases/malformed1.leases
+++ /dev/null
@@ -1,15 +0,0 @@
-# missing fixed-address option
-lease {
-  interface "wlan0";
-  option subnet-mask 255.255.255.0;
-  option routers 192.168.1.1;
-  option dhcp-lease-time 600;
-  option dhcp-message-type 5;
-  option domain-name-servers 192.168.1.1;
-  option dhcp-server-identifier 192.168.1.1;
-  option broadcast-address 192.168.1.255;
-  renew 5 2013/11/01 19:56:15;
-  rebind 5 2013/11/01 20:00:44;
-  expire 5 2013/11/01 20:01:59;
-}
-
diff --git a/src/dhcp/tests/leases/malformed2.leases b/src/dhcp/tests/leases/malformed2.leases
deleted file mode 100644
index adf5f6de..00000000
--- a/src/dhcp/tests/leases/malformed2.leases
+++ /dev/null
@@ -1,15 +0,0 @@
-# missing routers option
-lease {
-  interface "wlan0";
-  fixed-address 192.168.1.180;
-  option subnet-mask 255.255.255.0;
-  option dhcp-lease-time 600;
-  option dhcp-message-type 5;
-  option domain-name-servers 192.168.1.1;
-  option dhcp-server-identifier 192.168.1.1;
-  option broadcast-address 192.168.1.255;
-  renew 5 2013/11/01 19:56:15;
-  rebind 5 2013/11/01 20:00:44;
-  expire 5 2013/11/01 20:01:59;
-}
-
diff --git a/src/dhcp/tests/leases/malformed3.leases b/src/dhcp/tests/leases/malformed3.leases
deleted file mode 100644
index a2afc8b6..00000000
--- a/src/dhcp/tests/leases/malformed3.leases
+++ /dev/null
@@ -1,15 +0,0 @@
-# missing expire time
-lease {
-  interface "wlan0";
-  fixed-address 192.168.1.180;
-  option subnet-mask 255.255.255.0;
-  option routers 192.168.1.1;
-  option dhcp-lease-time 600;
-  option dhcp-message-type 5;
-  option domain-name-servers 192.168.1.1;
-  option dhcp-server-identifier 192.168.1.1;
-  option broadcast-address 192.168.1.255;
-  renew 5 2013/11/01 19:56:15;
-  rebind 5 2013/11/01 20:00:44;
-}
-
diff --git a/src/dhcp/tests/meson.build b/src/dhcp/tests/meson.build
new file mode 100644
index 00000000..32badae8
--- /dev/null
+++ b/src/dhcp/tests/meson.build
@@ -0,0 +1,19 @@
+test_units = [
+  'test-dhcp-dhclient',
+  'test-dhcp-utils'
+]
+
+foreach test_unit: test_units
+  exe = executable(
+    test_unit,
+    test_unit + '.c',
+    dependencies: test_nm_dep,
+    c_args: '-DTESTDIR="@0@"'.format(meson.current_source_dir())
+  )
+
+  test(
+    'dhcp/' + test_unit,
+    test_script,
+    args: test_args + [exe.full_path()]
+  )
+endforeach
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index f2e1f321..25af51a1 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -36,12 +36,6 @@
 
 #include "nm-test-utils-core.h"
 
-#define DEBUG 1
-
-static const int IFINDEX = 5;
-static const guint32 ROUTE_TABLE = RT_TABLE_MAIN;
-static const guint32 ROUTE_METRIC = 100;
-
 static void
 test_config (const char *orig,
              const char *expected,
@@ -154,7 +148,7 @@ test_override_client_id (void)
 static const char *quote_client_id_expected = \
 	"# Created by NetworkManager\n"
 	"\n"
-	"send dhcp-client-identifier \"1234\"; # added by NetworkManager\n"
+	"send dhcp-client-identifier \"\\x00abcd\"; # added by NetworkManager\n"
 	"\n"
 	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
 	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
@@ -172,7 +166,65 @@ test_quote_client_id (void)
 {
 	test_config (NULL, quote_client_id_expected,
 	             AF_INET, NULL, 0, FALSE,
-	             "1234",
+	             "abcd",
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *quote_client_id_expected_2 = \
+	"# Created by NetworkManager\n"
+	"\n"
+	"send dhcp-client-identifier 00:61:5c:62:63; # added by NetworkManager\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_quote_client_id_2 (void)
+{
+	test_config (NULL, quote_client_id_expected_2,
+	             AF_INET, NULL, 0, FALSE,
+	             "a\\bc",
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *hex_zero_client_id_expected = \
+	"# Created by NetworkManager\n"
+	"\n"
+	"send dhcp-client-identifier 00:11:22:33; # added by NetworkManager\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_hex_zero_client_id (void)
+{
+	test_config (NULL, hex_zero_client_id_expected,
+	             AF_INET, NULL, 0, FALSE,
+	             "00:11:22:33",
 	             NULL,
 	             "eth0",
 	             NULL);
@@ -183,7 +235,7 @@ test_quote_client_id (void)
 static const char *ascii_client_id_expected = \
 	"# Created by NetworkManager\n"
 	"\n"
-	"send dhcp-client-identifier \"qb:cd:ef:12:34:56\"; # added by NetworkManager\n"
+	"send dhcp-client-identifier \"\\x00qb:cd:ef:12:34:56\"; # added by NetworkManager\n"
 	"\n"
 	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
 	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
@@ -239,13 +291,13 @@ test_hex_single_client_id (void)
 /*****************************************************************************/
 
 static const char *existing_hex_client_id_orig = \
-	"send dhcp-client-identifier 00:30:04:20:7A:08;\n";
+	"send dhcp-client-identifier 10:30:04:20:7A:08;\n";
 
 static const char *existing_hex_client_id_expected = \
 	"# Created by NetworkManager\n"
 	"# Merged from /path/to/dhclient.conf\n"
 	"\n"
-	"send dhcp-client-identifier 00:30:04:20:7A:08;\n"
+	"send dhcp-client-identifier 10:30:04:20:7A:08;\n"
 	"\n"
 	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
 	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
@@ -262,7 +314,7 @@ static void
 test_existing_hex_client_id (void)
 {
 	gs_unref_bytes GBytes *new_client_id = NULL;
-	const guint8 bytes[] = { 0x00, 0x30, 0x04,0x20, 0x7A, 0x08 };
+	const guint8 bytes[] = { 0x10, 0x30, 0x04, 0x20, 0x7A, 0x08 };
 
 	new_client_id = g_bytes_new (bytes, sizeof (bytes));
 	test_config (existing_hex_client_id_orig, existing_hex_client_id_expected,
@@ -275,16 +327,52 @@ test_existing_hex_client_id (void)
 
 /*****************************************************************************/
 
+static const char *existing_escaped_client_id_orig = \
+	"send dhcp-client-identifier \"\\044test\\xfe\";\n";
+
+static const char *existing_escaped_client_id_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n"
+	"send dhcp-client-identifier \"\\044test\\xfe\";\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_existing_escaped_client_id (void)
+{
+	gs_unref_bytes GBytes *new_client_id = NULL;
+
+	new_client_id = g_bytes_new ("$test\xfe", 6);
+	test_config (existing_escaped_client_id_orig, existing_escaped_client_id_expected,
+	             AF_INET, NULL, 0, FALSE,
+	             NULL,
+	             new_client_id,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
 #define EACID "qb:cd:ef:12:34:56"
 
 static const char *existing_ascii_client_id_orig = \
-	"send dhcp-client-identifier \"" EACID "\";\n";
+	"send dhcp-client-identifier \"\\x00" EACID "\";\n";
 
 static const char *existing_ascii_client_id_expected = \
 	"# Created by NetworkManager\n"
 	"# Merged from /path/to/dhclient.conf\n"
 	"\n"
-	"send dhcp-client-identifier \"" EACID "\";\n"
+	"send dhcp-client-identifier \"\\x00" EACID "\";\n"
 	"\n"
 	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
 	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
@@ -585,23 +673,26 @@ test_existing_multiline_alsoreq (void)
 static void
 test_one_duid (const char *escaped, const guint8 *unescaped, guint len)
 {
-	GByteArray *t;
+	GBytes *t;
 	char *w;
+	gsize t_len;
+	gconstpointer t_arr;
 
 	t = nm_dhcp_dhclient_unescape_duid (escaped);
 	g_assert (t);
-	g_assert_cmpint (t->len, ==, len);
-	g_assert_cmpint (memcmp (t->data, unescaped, len), ==, 0);
-	g_byte_array_free (t, TRUE);
+	t_arr = g_bytes_get_data (t, &t_len);
+	g_assert (t_arr);
+	g_assert_cmpint (t_len, ==, len);
+	g_assert_cmpint (memcmp (t_arr, unescaped, len), ==, 0);
+	g_bytes_unref (t);
 
-	t = g_byte_array_sized_new (len);
-	g_byte_array_append (t, unescaped, len);
+	t = g_bytes_new_static (unescaped, len);
 	w = nm_dhcp_dhclient_escape_duid (t);
 	g_assert (w);
 	g_assert_cmpint (strlen (escaped), ==, strlen (w));
 	g_assert_cmpstr (escaped, ==, w);
 
-	g_byte_array_free (t, TRUE);
+	g_bytes_unref (t);
 	g_free (w);
 }
 
@@ -640,22 +731,23 @@ test_read_duid_from_leasefile (void)
 {
 	const guint8 expected[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0x79, 0xa6,
 	                            0x13, 0x60, 0x67, 0x20, 0xec, 0x4c, 0x70 };
-	GByteArray *duid;
+	gs_unref_bytes GBytes *duid = NULL;
 	GError *error = NULL;
+	gconstpointer duid_arr;
+	gsize duid_len;
 
 	duid = nm_dhcp_dhclient_read_duid (TESTDIR "/test-dhclient-duid.leases", &error);
 	g_assert_no_error (error);
 	g_assert (duid);
-	g_assert_cmpint (duid->len, ==, sizeof (expected));
-	g_assert_cmpint (memcmp (duid->data, expected, duid->len), ==, 0);
-
-	g_byte_array_free (duid, TRUE);
+	duid_arr = g_bytes_get_data (duid, &duid_len);
+	g_assert_cmpint (duid_len, ==, sizeof (expected));
+	g_assert_cmpint (memcmp (duid_arr, expected, duid_len), ==, 0);
 }
 
 static void
 test_read_commented_duid_from_leasefile (void)
 {
-	GByteArray *duid;
+	GBytes *duid;
 	GError *error = NULL;
 
 	duid = nm_dhcp_dhclient_read_duid (TESTDIR "/test-dhclient-commented-duid.leases", &error);
@@ -754,12 +846,12 @@ test_write_existing_commented_duid (void)
 
 static const char *interface1_orig = \
 	"interface \"eth0\" {\n"
-	"	also request my-option;\n"
-	"	initial-delay 5;\n"
+	"\talso request my-option;\n"
+	"\tinitial-delay 5;\n"
 	"}\n"
 	"interface \"eth1\" {\n"
-	"	also request another-option;\n"
-	"	initial-delay 0;\n"
+	"\talso request another-option;\n"
+	"\tinitial-delay 0;\n"
 	"}\n"
 	"\n"
 	"also request yet-another-option;\n";
@@ -798,12 +890,12 @@ test_interface1 (void)
 
 static const char *interface2_orig = \
 	"interface eth0 {\n"
-	"	also request my-option;\n"
-	"	initial-delay 5;\n"
+	"\talso request my-option;\n"
+	"\tinitial-delay 5;\n"
 	" }\n"
 	"interface eth1 {\n"
-	"	initial-delay 0;\n"
-	"	request another-option;\n"
+	"\tinitial-delay 0;\n"
+	"\trequest another-option;\n"
 	" } \n"
 	"\n"
 	"also request yet-another-option;\n";
@@ -844,12 +936,12 @@ test_config_req_intf (void)
 {
 	static const char *const orig = \
 		"request subnet-mask, broadcast-address, routers,\n"
-		"	rfc3442-classless-static-routes,\n"
-		"	interface-mtu, host-name, domain-name, domain-search,\n"
-		"	domain-name-servers, nis-domain, nis-servers,\n"
-		"	nds-context, nds-servers, nds-tree-name,\n"
-		"	netbios-name-servers, netbios-dd-server,\n"
-		"	netbios-node-type, netbios-scope, ntp-servers;\n"
+		"\trfc3442-classless-static-routes,\n"
+		"\tinterface-mtu, host-name, domain-name, domain-search,\n"
+		"\tdomain-name-servers, nis-domain, nis-servers,\n"
+		"\tnds-context, nds-servers, nds-tree-name,\n"
+		"\tnetbios-name-servers, netbios-dd-server,\n"
+		"\tnetbios-node-type, netbios-scope, ntp-servers;\n"
 		"";
 	static const char *const expected = \
 		"# Created by NetworkManager\n"
@@ -895,133 +987,6 @@ test_config_req_intf (void)
 
 /*****************************************************************************/
 
-static void
-test_read_lease_ip4_config_basic (void)
-{
-	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
-	GError *error = NULL;
-	char *contents = NULL;
-	gboolean success;
-	const char *path = TESTDIR "/leases/basic.leases";
-	GSList *leases;
-	GDateTime *now;
-	NMIP4Config *config;
-	const NMPlatformIP4Address *addr;
-	guint32 expected_addr;
-
-	success = g_file_get_contents (path, &contents, NULL, &error);
-	g_assert_no_error (error);
-	g_assert (success);
-
-	/* Date from before the least expiration */
-	now = g_date_time_new_utc (2013, 11, 1, 19, 55, 32);
-	leases = nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, now);
-	g_assert_cmpint (g_slist_length (leases), ==, 2);
-
-	/* IP4Config #1 */
-	config = g_slist_nth_data (leases, 0);
-	g_assert (NM_IS_IP4_CONFIG (config));
-
-	/* Address */
-	g_assert_cmpint (nm_ip4_config_get_num_addresses (config), ==, 1);
-	expected_addr = nmtst_inet4_from_string ("192.168.1.180");
-	addr = _nmtst_ip4_config_get_address (config, 0);
-	g_assert_cmpint (addr->address, ==, expected_addr);
-	g_assert_cmpint (addr->peer_address, ==, expected_addr);
-	g_assert_cmpint (addr->plen, ==, 24);
-
-	/* Gateway */
-	expected_addr = nmtst_inet4_from_string ("192.168.1.1");
-	g_assert_cmpint (nmtst_ip4_config_get_gateway (config), ==, expected_addr);
-
-	/* DNS */
-	g_assert_cmpint (nm_ip4_config_get_num_nameservers (config), ==, 1);
-	expected_addr = nmtst_inet4_from_string ("192.168.1.1");
-	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 0), ==, expected_addr);
-
-	g_assert_cmpint (nm_ip4_config_get_num_domains (config), ==, 0);
-
-	/* IP4Config #2 */
-	config = g_slist_nth_data (leases, 1);
-	g_assert (NM_IS_IP4_CONFIG (config));
-
-	/* Address */
-	g_assert_cmpint (nm_ip4_config_get_num_addresses (config), ==, 1);
-	expected_addr = nmtst_inet4_from_string ("10.77.52.141");
-	addr = _nmtst_ip4_config_get_address (config, 0);
-	g_assert_cmpint (addr->address, ==, expected_addr);
-	g_assert_cmpint (addr->peer_address, ==, expected_addr);
-	g_assert_cmpint (addr->plen, ==, 8);
-
-	/* Gateway */
-	expected_addr = nmtst_inet4_from_string ("10.77.52.254");
-	g_assert_cmpint (nmtst_ip4_config_get_gateway (config), ==, expected_addr);
-
-	/* DNS */
-	g_assert_cmpint (nm_ip4_config_get_num_nameservers (config), ==, 2);
-	expected_addr = nmtst_inet4_from_string ("8.8.8.8");
-	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 0), ==, expected_addr);
-	expected_addr = nmtst_inet4_from_string ("8.8.4.4");
-	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 1), ==, expected_addr);
-
-	/* Domains */
-	g_assert_cmpint (nm_ip4_config_get_num_domains (config), ==, 1);
-	g_assert_cmpstr (nm_ip4_config_get_domain (config, 0), ==, "morriesguest.local");
-
-	g_slist_free_full (leases, g_object_unref);
-	g_date_time_unref (now);
-	g_free (contents);
-}
-
-static void
-test_read_lease_ip4_config_expired (void)
-{
-	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
-	GError *error = NULL;
-	char *contents = NULL;
-	gboolean success;
-	const char *path = TESTDIR "/leases/basic.leases";
-	GSList *leases;
-	GDateTime *now;
-
-	success = g_file_get_contents (path, &contents, NULL, &error);
-	g_assert_no_error (error);
-	g_assert (success);
-
-	/* Date from *after* the lease expiration */
-	now = g_date_time_new_utc (2013, 12, 1, 19, 55, 32);
-	leases = nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, now);
-	g_assert (leases == NULL);
-
-	g_date_time_unref (now);
-	g_free (contents);
-}
-
-static void
-test_read_lease_ip4_config_expect_failure (gconstpointer user_data)
-{
-	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
-	GError *error = NULL;
-	char *contents = NULL;
-	gboolean success;
-	GSList *leases;
-	GDateTime *now;
-
-	success = g_file_get_contents ((const char *) user_data, &contents, NULL, &error);
-	g_assert_no_error (error);
-	g_assert (success);
-
-	/* Date from before the least expiration */
-	now = g_date_time_new_utc (2013, 11, 1, 1, 1, 1);
-	leases = nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, now);
-	g_assert (leases == NULL);
-
-	g_date_time_unref (now);
-	g_free (contents);
-}
-
-/*****************************************************************************/
-
 NMTST_DEFINE ();
 
 int
@@ -1031,10 +996,13 @@ main (int argc, char **argv)
 
 	g_test_add_func ("/dhcp/dhclient/orig_missing", test_orig_missing);
 	g_test_add_func ("/dhcp/dhclient/override_client_id", test_override_client_id);
-	g_test_add_func ("/dhcp/dhclient/quote_client_id", test_quote_client_id);
+	g_test_add_func ("/dhcp/dhclient/quote_client_id/1", test_quote_client_id);
+	g_test_add_func ("/dhcp/dhclient/quote_client_id/2", test_quote_client_id_2);
+	g_test_add_func ("/dhcp/dhclient/hex_zero_client_id", test_hex_zero_client_id);
 	g_test_add_func ("/dhcp/dhclient/ascii_client_id", test_ascii_client_id);
 	g_test_add_func ("/dhcp/dhclient/hex_single_client_id", test_hex_single_client_id);
 	g_test_add_func ("/dhcp/dhclient/existing-hex-client-id", test_existing_hex_client_id);
+	g_test_add_func ("/dhcp/dhclient/existing-client-id", test_existing_escaped_client_id);
 	g_test_add_func ("/dhcp/dhclient/existing-ascii-client-id", test_existing_ascii_client_id);
 	g_test_add_func ("/dhcp/dhclient/fqdn", test_fqdn);
 	g_test_add_func ("/dhcp/dhclient/fqdn_options_override", test_fqdn_options_override);
@@ -1056,18 +1024,6 @@ main (int argc, char **argv)
 	g_test_add_func ("/dhcp/dhclient/write_existing_duid", test_write_existing_duid);
 	g_test_add_func ("/dhcp/dhclient/write_existing_commented_duid", test_write_existing_commented_duid);
 
-	g_test_add_func ("/dhcp/dhclient/leases/ip4-config/basic", test_read_lease_ip4_config_basic);
-	g_test_add_func ("/dhcp/dhclient/leases/ip4-config/expired", test_read_lease_ip4_config_expired);
-	g_test_add_data_func ("/dhcp/dhclient/leases/ip4-config/missing-address",
-	                      TESTDIR "/leases/malformed1.leases",
-	                      test_read_lease_ip4_config_expect_failure);
-	g_test_add_data_func ("/dhcp/dhclient/leases/ip4-config/missing-gateway",
-	                      TESTDIR "/leases/malformed2.leases",
-	                      test_read_lease_ip4_config_expect_failure);
-	g_test_add_data_func ("/dhcp/dhclient/leases/ip4-config/missing-expire",
-	                      TESTDIR "/leases/malformed3.leases",
-	                      test_read_lease_ip4_config_expect_failure);
-
 	return g_test_run ();
 }
 
diff --git a/src/dhcp/tests/test-dhcp-utils.c b/src/dhcp/tests/test-dhcp-utils.c
index 72f31191..617a3c6c 100644
--- a/src/dhcp/tests/test-dhcp-utils.c
+++ b/src/dhcp/tests/test-dhcp-utils.c
@@ -349,8 +349,7 @@ test_dhclient_invalid_classless_routes_1 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring invalid classless static routes*");
+	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -380,8 +379,7 @@ test_dhcpcd_invalid_classless_routes_1 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring invalid classless static routes*");
+	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -413,8 +411,7 @@ test_dhclient_invalid_classless_routes_2 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring invalid classless static routes*");
+	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -446,8 +443,7 @@ test_dhcpcd_invalid_classless_routes_2 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring invalid classless static routes*");
+	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -479,8 +475,7 @@ test_dhclient_invalid_classless_routes_3 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring invalid classless static routes*");
+	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -507,8 +502,7 @@ test_dhcpcd_invalid_classless_routes_3 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*DHCP provided invalid classless static route*");
+	NMTST_EXPECT_NM_WARN ("*DHCP provided invalid classless static route*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -615,8 +609,7 @@ test_invalid_escaped_domain_searches (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*invalid domain search*");
+	NMTST_EXPECT_NM_WARN ("*invalid domain search*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/dns/nm-dns-dnsmasq.c
index e6436c79..d0753078 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/dns/nm-dns-dnsmasq.c
@@ -35,7 +35,7 @@
 #include "nm-utils.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
 #include "NetworkManagerUtils.h"
 
 #define PIDFILE NMRUNDIR "/dnsmasq.pid"
@@ -76,54 +76,41 @@ G_DEFINE_TYPE (NMDnsDnsmasq, nm_dns_dnsmasq, NM_TYPE_DNS_PLUGIN)
 /*****************************************************************************/
 
 static char **
-get_ip4_rdns_domains (NMIP4Config *ip4)
+get_ip_rdns_domains (NMIPConfig *ip_config)
 {
+	int addr_family = nm_ip_config_get_addr_family (ip_config);
 	char **strv;
 	GPtrArray *domains = NULL;
 	NMDedupMultiIter ipconf_iter;
-	const NMPlatformIP4Address *address;
-	const NMPlatformIP4Route *route;
 
-	g_return_val_if_fail (ip4 != NULL, NULL);
+	nm_assert_addr_family (addr_family);
 
 	domains = g_ptr_array_sized_new (5);
 
-	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &address)
-		nm_utils_get_reverse_dns_domains_ip4 (address->address, address->plen, domains);
+	if (addr_family == AF_INET) {
+		NMIP4Config *ip4 = (gpointer) ip_config;
+		const NMPlatformIP4Address *address;
+		const NMPlatformIP4Route *route;
 
-	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route) {
-		if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
-			nm_utils_get_reverse_dns_domains_ip4 (route->network, route->plen, domains);
-	}
-
-	/* Terminating NULL so we can use g_strfreev() to free it */
-	g_ptr_array_add (domains, NULL);
-
-	/* Free the array and return NULL if the only element was the ending NULL */
-	strv = (char **) g_ptr_array_free (domains, (domains->len == 1));
-
-	return _nm_utils_strv_cleanup (strv, FALSE, FALSE, TRUE);
-}
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &address)
+			nm_utils_get_reverse_dns_domains_ip4 (address->address, address->plen, domains);
 
-static char **
-get_ip6_rdns_domains (NMIP6Config *ip6)
-{
-	char **strv;
-	GPtrArray *domains = NULL;
-	NMDedupMultiIter ipconf_iter;
-	const NMPlatformIP6Address *address;
-	const NMPlatformIP6Route *route;
-
-	g_return_val_if_fail (ip6 != NULL, NULL);
-
-	domains = g_ptr_array_sized_new (5);
+		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route) {
+			if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
+				nm_utils_get_reverse_dns_domains_ip4 (route->network, route->plen, domains);
+		}
+	} else {
+		NMIP6Config *ip6 = (gpointer) ip_config;
+		const NMPlatformIP6Address *address;
+		const NMPlatformIP6Route *route;
 
-	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &address)
-		nm_utils_get_reverse_dns_domains_ip6 (&address->address, address->plen, domains);
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &address)
+			nm_utils_get_reverse_dns_domains_ip6 (&address->address, address->plen, domains);
 
-	nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route) {
-		if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
-			nm_utils_get_reverse_dns_domains_ip6 (&route->network, route->plen, domains);
+		nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route) {
+			if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
+				nm_utils_get_reverse_dns_domains_ip6 (&route->network, route->plen, domains);
+		}
 	}
 
 	/* Terminating NULL so we can use g_strfreev() to free it */
@@ -155,96 +142,43 @@ add_dnsmasq_nameserver (NMDnsDnsmasq *self,
 	g_variant_builder_close (servers);
 }
 
-static gboolean
-add_ip4_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP4Config *ip4,
-                const char *iface, gboolean split)
-{
-	char buf[INET_ADDRSTRLEN + 1 + IFNAMSIZ];
-	char buf2[INET_ADDRSTRLEN];
-	in_addr_t addr;
-	int nnameservers, i_nameserver, n, i;
-	gboolean added = FALSE;
-
-	g_return_val_if_fail (iface, FALSE);
-	nnameservers = nm_ip4_config_get_num_nameservers (ip4);
-
-	if (split) {
-		char **domains, **iter;
+#define IP_ADDR_TO_STRING_BUFLEN (NM_UTILS_INET_ADDRSTRLEN + 1 + IFNAMSIZ)
 
-		if (nnameservers == 0)
-			return FALSE;
-
-		for (i_nameserver = 0; i_nameserver < nnameservers; i_nameserver++) {
-			addr = nm_ip4_config_get_nameserver (ip4, i_nameserver);
-			g_snprintf (buf, sizeof (buf), "%s@%s",
-			            nm_utils_inet4_ntop (addr, buf2), iface);
-
-			/* searches are preferred over domains */
-			n = nm_ip4_config_get_num_searches (ip4);
-			for (i = 0; i < n; i++) {
-				add_dnsmasq_nameserver (self,
-				                        servers,
-				                        buf,
-				                        nm_ip4_config_get_search (ip4, i));
-				added = TRUE;
-			}
-
-			if (n == 0) {
-				/* If not searches, use any domains */
-				n = nm_ip4_config_get_num_domains (ip4);
-				for (i = 0; i < n; i++) {
-					add_dnsmasq_nameserver (self,
-					                        servers,
-					                        buf,
-					                        nm_ip4_config_get_domain (ip4, i));
-					added = TRUE;
-				}
-			}
+static const char *
+ip_addr_to_string (int addr_family, gconstpointer addr, const char *iface, char *out_buf)
+{
+	int n_written;
+	char buf2[NM_UTILS_INET_ADDRSTRLEN];
+	const char *separator;
 
-			/* Ensure reverse-DNS works by directing queries for in-addr.arpa
-			 * domains to the split domain's nameserver.
-			 */
-			domains = get_ip4_rdns_domains (ip4);
-			if (domains) {
-				for (iter = domains; iter && *iter; iter++)
-					add_dnsmasq_nameserver (self, servers, buf, *iter);
-				g_strfreev (domains);
-			}
-		}
-	}
+	nm_assert_addr_family (addr_family);
+	nm_assert (addr);
+	nm_assert (out_buf);
 
-	/* If no searches or domains, just add the nameservers */
-	if (!added) {
-		for (i = 0; i < nnameservers; i++) {
-			addr = nm_ip4_config_get_nameserver (ip4, i);
-			g_snprintf (buf, sizeof (buf), "%s@%s",
-			            nm_utils_inet4_ntop (addr, buf2), iface);
-			add_dnsmasq_nameserver (self, servers, buf, NULL);
-		}
+	if (addr_family == AF_INET) {
+		nm_utils_inet_ntop (addr_family, addr, buf2);
+		separator = "@";
+	} else {
+		if (IN6_IS_ADDR_V4MAPPED (addr))
+			nm_utils_inet4_ntop (((const struct in6_addr *) addr)->s6_addr32[3], buf2);
+		else
+			nm_utils_inet6_ntop (addr, buf2);
+		/* Need to scope link-local addresses with %<zone-id>. Before dnsmasq 2.58,
+		 * only '@' was supported as delimiter. Since 2.58, '@' and '%' are
+		 * supported. Due to a bug, since 2.73 only '%' works properly as "server"
+		 * address.
+		 */
+		separator = IN6_IS_ADDR_LINKLOCAL (addr) ? "%" : "@";
 	}
 
-	return TRUE;
-}
-
-static char *
-ip6_addr_to_string (const struct in6_addr *addr, const char *iface)
-{
-	char buf[NM_UTILS_INET_ADDRSTRLEN];
-
-	if (IN6_IS_ADDR_V4MAPPED (addr))
-		nm_utils_inet4_ntop (addr->s6_addr32[3], buf);
-	else
-		nm_utils_inet6_ntop (addr, buf);
-
-	/* Need to scope link-local addresses with %<zone-id>. Before dnsmasq 2.58,
-	 * only '@' was supported as delimiter. Since 2.58, '@' and '%' are
-	 * supported. Due to a bug, since 2.73 only '%' works properly as "server"
-	 * address.
-	 */
-	return g_strdup_printf ("%s%c%s",
-	                        buf,
-	                        IN6_IS_ADDR_LINKLOCAL (addr) ? '%' : '@',
-	                        iface);
+	n_written = g_snprintf (out_buf,
+	                        IP_ADDR_TO_STRING_BUFLEN,
+	                        "%s%s%s",
+	                        buf2,
+	                        iface ? separator : "",
+	                        iface ?: "");
+	nm_assert (n_written < IP_ADDR_TO_STRING_BUFLEN);
+	return out_buf;
 }
 
 static void
@@ -271,103 +205,100 @@ add_global_config (NMDnsDnsmasq *self, GVariantBuilder *dnsmasq_servers, const N
 	}
 }
 
-static gboolean
-add_ip6_config (NMDnsDnsmasq *self, GVariantBuilder *servers, NMIP6Config *ip6,
-                const char *iface, gboolean split)
+static void
+add_ip_config (NMDnsDnsmasq *self,
+               GVariantBuilder *servers,
+               int ifindex,
+               NMIPConfig *ip_config,
+               gboolean split)
 {
-	const struct in6_addr *addr;
-	char *buf = NULL;
-	int nnameservers, i_nameserver, n, i;
+	int addr_family;
+	gconstpointer addr;
 	gboolean added = FALSE;
+	guint nnameservers, i_nameserver, n, i;
+	char ip_addr_to_string_buf[IP_ADDR_TO_STRING_BUFLEN];
+	char **domains, **iter;
+	gboolean iface_resolved = FALSE;
+	const char *iface = NULL, *domain;
 
-	g_return_val_if_fail (iface, FALSE);
-	nnameservers = nm_ip6_config_get_num_nameservers (ip6);
+	addr_family = nm_ip_config_get_addr_family (ip_config);
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	if (split) {
-		char **domains, **iter;
+	nm_assert (ifindex > 0);
+	nm_assert (ifindex == nm_ip_config_get_ifindex (ip_config));
+
+	nnameservers = nm_ip_config_get_num_nameservers (ip_config);
 
+	if (split) {
 		if (nnameservers == 0)
-			return FALSE;
-
-		for (i_nameserver = 0; i_nameserver < nnameservers; i_nameserver++) {
-			addr = nm_ip6_config_get_nameserver (ip6, i_nameserver);
-			buf = ip6_addr_to_string (addr, iface);
-
-			/* searches are preferred over domains */
-			n = nm_ip6_config_get_num_searches (ip6);
-			for (i = 0; i < n; i++) {
-				add_dnsmasq_nameserver (self,
-				                        servers,
-				                        buf,
-				                        nm_ip6_config_get_search (ip6, i));
-				added = TRUE;
-			}
+			return;
 
-			if (n == 0) {
-				/* If not searches, use any domains */
-				n = nm_ip6_config_get_num_domains (ip6);
+		if (!iface_resolved) {
+			iface = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+			iface_resolved = TRUE;
+		}
+
+		if (iface) {
+			for (i_nameserver = 0; i_nameserver < nnameservers; i_nameserver++) {
+				addr = nm_ip_config_get_nameserver (ip_config, i_nameserver);
+
+				ip_addr_to_string (addr_family, addr, iface, ip_addr_to_string_buf);
+
+				/* searches are preferred over domains */
+				n = nm_ip_config_get_num_searches (ip_config);
 				for (i = 0; i < n; i++) {
+					domain = nm_utils_parse_dns_domain (nm_ip_config_get_search (ip_config, i), NULL);
 					add_dnsmasq_nameserver (self,
 					                        servers,
-					                        buf,
-					                        nm_ip6_config_get_domain (ip6, i));
+					                        ip_addr_to_string_buf,
+					                        domain);
 					added = TRUE;
 				}
-			}
 
-			/* Ensure reverse-DNS works by directing queries for ip6.arpa
-			 * domains to the split domain's nameserver.
-			 */
-			domains = get_ip6_rdns_domains (ip6);
-			if (domains) {
-				for (iter = domains; iter && *iter; iter++)
-					add_dnsmasq_nameserver (self, servers, buf, *iter);
-				g_strfreev (domains);
-			}
+				if (n == 0) {
+					/* If not searches, use any domains */
+					n = nm_ip_config_get_num_domains (ip_config);
+					domain = nm_utils_parse_dns_domain (nm_ip_config_get_domain (ip_config, i), NULL);
+					for (i = 0; i < n; i++) {
+						add_dnsmasq_nameserver (self,
+						                        servers,
+						                        ip_addr_to_string_buf,
+						                        domain);
+						added = TRUE;
+					}
+				}
 
-			g_free (buf);
+				/* Ensure reverse-DNS works by directing queries for in-addr4.arpa/ip6.arpa
+				 * domains to the split domain's nameserver.
+				 */
+				domains = get_ip_rdns_domains (ip_config);
+				if (domains) {
+					for (iter = domains; *iter; iter++)
+						add_dnsmasq_nameserver (self, servers, ip_addr_to_string_buf, *iter);
+					g_strfreev (domains);
+				}
+			}
 		}
 	}
 
 	/* If no searches or domains, just add the nameservers */
 	if (!added) {
-		for (i = 0; i < nnameservers; i++) {
-			addr = nm_ip6_config_get_nameserver (ip6, i);
-			buf = ip6_addr_to_string (addr, iface);
-			if (buf) {
-				add_dnsmasq_nameserver (self, servers, buf, NULL);
-				g_free (buf);
+		if (!iface_resolved)
+			iface = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+		if (iface) {
+			for (i = 0; i < nnameservers; i++) {
+				addr = nm_ip_config_get_nameserver (ip_config, i);
+				ip_addr_to_string (addr_family, addr, iface, ip_addr_to_string_buf);
+				add_dnsmasq_nameserver (self, servers, ip_addr_to_string_buf, NULL);
 			}
 		}
 	}
-
-	return TRUE;
-}
-
-static gboolean
-add_ip_config_data (NMDnsDnsmasq *self, GVariantBuilder *servers, const NMDnsIPConfigData *data)
-{
-	if (NM_IS_IP4_CONFIG (data->config)) {
-		return add_ip4_config (self,
-		                       servers,
-		                       (NMIP4Config *) data->config,
-		                       data->iface,
-		                       data->type == NM_DNS_IP_CONFIG_TYPE_VPN);
-	} else if (NM_IS_IP6_CONFIG (data->config)) {
-		return add_ip6_config (self,
-		                       servers,
-		                       (NMIP6Config *) data->config,
-		                       data->iface,
-		                       data->type == NM_DNS_IP_CONFIG_TYPE_VPN);
-	} else
-		g_return_val_if_reached (FALSE);
 }
 
 static void
 dnsmasq_update_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
 {
 	NMDnsDnsmasq *self;
-	NMDnsDnsmasqPrivate *priv;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *response = NULL;
 
@@ -376,7 +307,6 @@ dnsmasq_update_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
 		return;
 
 	self = NM_DNS_DNSMASQ (user_data);
-	priv = NM_DNS_DNSMASQ_GET_PRIVATE (self);
 
 	if (!response)
 		_LOGW ("dnsmasq update failed: %s", error->message);
@@ -478,7 +408,7 @@ start_dnsmasq (NMDnsDnsmasq *self)
 	const char *argv[15];
 	GPid pid = 0;
 	guint idx = 0;
-	NMBusManager *dbus_mgr;
+	NMDBusManager *dbus_mgr;
 	GDBusConnection *connection;
 
 	if (priv->running) {
@@ -530,10 +460,10 @@ start_dnsmasq (NMDnsDnsmasq *self)
 		return;
 	}
 
-	dbus_mgr = nm_bus_manager_get ();
+	dbus_mgr = nm_dbus_manager_get ();
 	g_return_if_fail (dbus_mgr);
 
-	connection = nm_bus_manager_get_connection (dbus_mgr);
+	connection = nm_dbus_manager_get_connection (dbus_mgr);
 	g_return_if_fail (connection);
 
 	priv->dnsmasq_cancellable = g_cancellable_new ();
@@ -550,15 +480,16 @@ start_dnsmasq (NMDnsDnsmasq *self)
 
 static gboolean
 update (NMDnsPlugin *plugin,
-        const GPtrArray *configs,
         const NMGlobalDnsConfig *global_config,
+        const CList *ip_config_lst_head,
         const char *hostname)
 {
 	NMDnsDnsmasq *self = NM_DNS_DNSMASQ (plugin);
 	NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE (self);
 	GVariantBuilder servers;
-	guint i;
-	int prio, first_prio;
+	int prio, first_prio = 0;
+	const NMDnsIPConfigData *ip_data;
+	gboolean is_first = TRUE;
 
 	start_dnsmasq (self);
 
@@ -567,15 +498,18 @@ update (NMDnsPlugin *plugin,
 	if (global_config)
 		add_global_config (self, &servers, global_config);
 	else {
-		for (i = 0; i < configs->len; i++) {
-			const NMDnsIPConfigData *data = configs->pdata[i];
-
-			prio = nm_ip_config_get_dns_priority (data->config);
-			if (i == 0)
+		c_list_for_each_entry (ip_data, ip_config_lst_head, ip_config_lst) {
+			prio = nm_ip_config_get_dns_priority (ip_data->ip_config);
+			if (is_first) {
+				is_first = FALSE;
 				first_prio = prio;
-			else if (first_prio < 0 && first_prio != prio)
+			} else if (first_prio < 0 && first_prio != prio)
 				break;
-			add_ip_config_data (self, &servers, data);
+			add_ip_config (self,
+			               &servers,
+			               ip_data->data->ifindex,
+			               ip_data->ip_config,
+			               ip_data->ip_config_type == NM_DNS_IP_CONFIG_TYPE_VPN);
 		}
 	}
 
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index dc545470..10a16e57 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -46,6 +46,7 @@
 #include "nm-ip6-config.h"
 #include "NetworkManagerUtils.h"
 #include "nm-config.h"
+#include "nm-dbus-object.h"
 #include "devices/nm-device.h"
 #include "nm-manager.h"
 
@@ -54,8 +55,6 @@
 #include "nm-dns-systemd-resolved.h"
 #include "nm-dns-unbound.h"
 
-#include "introspection/org.freedesktop.NetworkManager.DnsManager.h"
-
 #define HASH_LEN 20
 
 #ifndef RESOLVCONF_PATH
@@ -70,6 +69,24 @@
 #define PLUGIN_RATELIMIT_BURST       5
 #define PLUGIN_RATELIMIT_DELAY       300
 
+/*****************************************************************************/
+
+typedef enum {
+	SR_SUCCESS,
+	SR_NOTFOUND,
+	SR_ERROR
+} SpawnResult;
+
+typedef struct {
+	GPtrArray *nameservers;
+	GPtrArray *searches;
+	GPtrArray *options;
+	const char *nis_domain;
+	GPtrArray *nis_servers;
+} NMResolvConfData;
+
+/*****************************************************************************/
+
 enum {
 	CONFIG_CHANGED,
 
@@ -84,44 +101,16 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDnsManager,
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
-typedef enum {
-	SR_SUCCESS,
-	SR_NOTFOUND,
-	SR_ERROR
-} SpawnResult;
-
-NM_DEFINE_SINGLETON_GETTER (NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER);
-
-/*****************************************************************************/
-
-#define _NMLOG_PREFIX_NAME                "dns-mgr"
-#define _NMLOG_DOMAIN                     LOGD_DNS
-#define _NMLOG(level, ...) \
-    G_STMT_START { \
-        const NMLogLevel __level = (level); \
-        \
-        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
-            char __prefix[20]; \
-            const NMDnsManager *const __self = (self); \
-            \
-            _nm_log (__level, _NMLOG_DOMAIN, 0, NULL, NULL, \
-                     "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                     _NMLOG_PREFIX_NAME, \
-                     ((!__self || __self == singleton_instance) \
-                        ? "" \
-                        : nm_sprintf_buf (__prefix, "[%p]", __self)) \
-                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
-        } \
-    } G_STMT_END
-
-/*****************************************************************************/
-
 typedef struct {
-	GPtrArray *configs;
+	GHashTable *configs;
+	CList ip_config_lst_head;
 	GVariant *config_variant;
-	NMDnsIPConfigData *best_conf4, *best_conf6;
 
-	bool need_sort:1;
+	NMDnsIPConfigData *best_ip_config_4;
+	NMDnsIPConfigData *best_ip_config_6;
+
+	bool ip_config_lst_need_sort:1;
+
 	bool dns_touched:1;
 	bool is_stopped:1;
 
@@ -145,20 +134,52 @@ typedef struct {
 } NMDnsManagerPrivate;
 
 struct _NMDnsManager {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	NMDnsManagerPrivate _priv;
 };
 
 struct _NMDnsManagerClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 };
 
-G_DEFINE_TYPE (NMDnsManager, nm_dns_manager, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMDnsManager, nm_dns_manager, NM_TYPE_DBUS_OBJECT)
 
 #define NM_DNS_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDnsManager, NM_IS_DNS_MANAGER)
 
+NM_DEFINE_SINGLETON_GETTER (NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER);
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME                "dns-mgr"
+#define _NMLOG_DOMAIN                     LOGD_DNS
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        \
+        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
+            char __prefix[20]; \
+            const NMDnsManager *const __self = (self); \
+            \
+            _nm_log (__level, _NMLOG_DOMAIN, 0, NULL, NULL, \
+                     "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     ((!__self || __self == singleton_instance) \
+                        ? "" \
+                        : nm_sprintf_buf (__prefix, "[%p]", __self)) \
+                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static void _ip_config_dns_priority_changed (gpointer config,
+                                             GParamSpec *pspec,
+                                             NMDnsIPConfigData *ip_data);
+
+/*****************************************************************************/
+
 static gboolean
-domain_is_valid (const gchar *domain, gboolean check_public_suffix)
+domain_is_valid (const char *domain, gboolean check_public_suffix)
 {
 	if (*domain == '\0')
 		return FALSE;
@@ -169,15 +190,13 @@ domain_is_valid (const gchar *domain, gboolean check_public_suffix)
 	return TRUE;
 }
 
-/*****************************************************************************/
+static gboolean
+domain_is_routing (const char *domain)
+{
+	return domain[0] == '~';
+}
 
-typedef struct {
-	GPtrArray *nameservers;
-	GPtrArray *searches;
-	GPtrArray *options;
-	const char *nis_domain;
-	GPtrArray *nis_servers;
-} NMResolvConfData;
+/*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_rc_manager_to_string, NMDnsManagerResolvConfManager,
 	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
@@ -192,44 +211,109 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_rc_manager_to_string, NMDnsManagerResolvConf
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_config_type_to_string, NMDnsIPConfigType,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("<unknown>"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_REMOVED, "removed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_DEFAULT, "default"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE, "best"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_VPN, "vpn"),
 );
 
-static NMDnsIPConfigData *
-ip_config_data_new (gpointer config, NMDnsIPConfigType type, const char *iface)
+/*****************************************************************************/
+
+static void
+_ASSERT_config_data (const NMDnsConfigData *data)
 {
-	NMDnsIPConfigData *data;
+	nm_assert (data);
+	nm_assert (NM_IS_DNS_MANAGER (data->self));
+	nm_assert (data->ifindex > 0);
+}
 
-	data = g_slice_new0 (NMDnsIPConfigData);
-	data->config = g_object_ref (config);
-	data->iface = g_strdup (iface);
-	data->type = type;
+static void
+_ASSERT_ip_config_data (const NMDnsIPConfigData *ip_data)
+{
+	nm_assert (ip_data);
+	_ASSERT_config_data (ip_data->data);
+	nm_assert (NM_IS_IP_CONFIG (ip_data->ip_config));
+	nm_assert (c_list_contains (&ip_data->data->data_lst_head, &ip_data->data_lst));
+	nm_assert (ip_data->data->ifindex == nm_ip_config_get_ifindex (ip_data->ip_config));
+}
 
-	return data;
+static NMDnsIPConfigData *
+_ip_config_data_new (NMDnsConfigData *data,
+                     NMIPConfig *ip_config,
+                     NMDnsIPConfigType ip_config_type)
+{
+	NMDnsIPConfigData *ip_data;
+
+	_ASSERT_config_data (data);
+	nm_assert (NM_IS_IP_CONFIG (ip_config));
+	nm_assert (ip_config_type != NM_DNS_IP_CONFIG_TYPE_REMOVED);
+
+	ip_data = g_slice_new0 (NMDnsIPConfigData);
+	ip_data->data = data;
+	ip_data->ip_config = g_object_ref (ip_config);
+	ip_data->ip_config_type = ip_config_type;
+	c_list_link_tail (&data->data_lst_head, &ip_data->data_lst);
+	c_list_link_tail (&NM_DNS_MANAGER_GET_PRIVATE (data->self)->ip_config_lst_head, &ip_data->ip_config_lst);
+
+	g_signal_connect (ip_config,
+	                  NM_IS_IP4_CONFIG (ip_config)
+	                    ? "notify::" NM_IP4_CONFIG_DNS_PRIORITY
+	                    : "notify::" NM_IP6_CONFIG_DNS_PRIORITY,
+	                  (GCallback) _ip_config_dns_priority_changed, ip_data);
+
+	_ASSERT_ip_config_data (ip_data);
+	return ip_data;
 }
 
 static void
-ip_config_data_destroy (gpointer ptr)
+_ip_config_data_free (NMDnsIPConfigData *ip_data)
 {
-	NMDnsIPConfigData *data = ptr;
+	_ASSERT_ip_config_data (ip_data);
 
-	if (!data)
-		return;
+	c_list_unlink_stale (&ip_data->data_lst);
+	c_list_unlink_stale (&ip_data->ip_config_lst);
+
+	g_signal_handlers_disconnect_by_func (ip_data->ip_config,
+	                                      _ip_config_dns_priority_changed,
+	                                      ip_data);
+
+	g_object_unref (ip_data->ip_config);
+	g_slice_free (NMDnsIPConfigData, ip_data);
+}
+
+static NMDnsIPConfigData *
+_config_data_find_ip_config (NMDnsConfigData *data,
+                             NMIPConfig *ip_config)
+{
+	NMDnsIPConfigData *ip_data;
+
+	_ASSERT_config_data (data);
+
+	c_list_for_each_entry (ip_data, &data->data_lst_head, data_lst) {
+		_ASSERT_ip_config_data (ip_data);
+
+		if (ip_data->ip_config == ip_config)
+			return ip_data;
+	}
+	return NULL;
+}
+
+static void
+_config_data_free (NMDnsConfigData *data)
+{
+	_ASSERT_config_data (data);
 
-	g_object_unref (data->config);
-	g_free (data->iface);
-	g_slice_free (NMDnsIPConfigData, data);
+	nm_assert (c_list_is_empty (&data->data_lst_head));
+	g_slice_free (NMDnsConfigData, data);
 }
 
 static gint
-ip_config_data_compare (const NMDnsIPConfigData *a, const NMDnsIPConfigData *b)
+_ip_config_data_cmp (const NMDnsIPConfigData *a, const NMDnsIPConfigData *b)
 {
 	int a_prio, b_prio;
 
-	a_prio = nm_ip_config_get_dns_priority (a->config);
-	b_prio = nm_ip_config_get_dns_priority (b->config);
+	a_prio = nm_ip_config_get_dns_priority (a->ip_config);
+	b_prio = nm_ip_config_get_dns_priority (b->ip_config);
 
 	/* Configurations with lower priority value first */
 	if (a_prio < b_prio)
@@ -238,24 +322,40 @@ ip_config_data_compare (const NMDnsIPConfigData *a, const NMDnsIPConfigData *b)
 		return 1;
 
 	/* Sort also according to type */
-	if (a->type > b->type)
+	if (a->ip_config_type > b->ip_config_type)
 		return -1;
-	else if (a->type < b->type)
+	else if (a->ip_config_type < b->ip_config_type)
 		return 1;
 
 	return 0;
 }
 
 static gint
-ip_config_data_ptr_compare (gconstpointer a, gconstpointer b)
+_ip_config_lst_cmp (const CList *a,
+                    const CList *b,
+                    const void *user_data)
 {
-	const NMDnsIPConfigData *const *ptr_a = a, *const *ptr_b = b;
+	return _ip_config_data_cmp (c_list_entry (a, NMDnsIPConfigData, ip_config_lst),
+	                            c_list_entry (b, NMDnsIPConfigData, ip_config_lst));
+}
 
-	return ip_config_data_compare (*ptr_a, *ptr_b);
+static CList *
+_ip_config_lst_head (NMDnsManager *self)
+{
+	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+
+	if (priv->ip_config_lst_need_sort) {
+		priv->ip_config_lst_need_sort = FALSE;
+		c_list_sort (&priv->ip_config_lst_head, _ip_config_lst_cmp, NULL);
+	}
+
+	return &priv->ip_config_lst_head;
 }
 
+/*****************************************************************************/
+
 static void
-add_string_item (GPtrArray *array, const char *str)
+add_string_item (GPtrArray *array, const char *str, gboolean dup)
 {
 	int i;
 
@@ -271,7 +371,7 @@ add_string_item (GPtrArray *array, const char *str)
 	}
 
 	/* No dupes, add the new item */
-	g_ptr_array_add (array, g_strdup (str));
+	g_ptr_array_add (array, dup ? g_strdup (str): (gpointer) str);
 }
 
 static void
@@ -282,24 +382,55 @@ add_dns_option_item (GPtrArray *array, const char *str)
 }
 
 static void
+add_dns_domains (GPtrArray *array, const NMIPConfig *ip_config,
+                 gboolean include_routing, gboolean dup)
+{
+	guint num_domains, num_searches, i;
+	const char *str;
+
+	num_domains = nm_ip_config_get_num_domains (ip_config);
+	num_searches = nm_ip_config_get_num_searches (ip_config);
+
+	for (i = 0; i < num_searches; i++) {
+		str = nm_ip_config_get_search (ip_config, i);
+		if (!include_routing && domain_is_routing (str))
+			continue;
+		if (!domain_is_valid (nm_utils_parse_dns_domain (str, NULL), FALSE))
+			continue;
+		add_string_item (array, str, dup);
+	}
+	if (num_domains > 1 || !num_searches) {
+		for (i = 0; i < num_domains; i++) {
+			str = nm_ip_config_get_domain (ip_config, i);
+			if (!include_routing && domain_is_routing (str))
+				continue;
+			if (!domain_is_valid (nm_utils_parse_dns_domain (str, NULL), FALSE))
+				continue;
+			add_string_item (array, str, dup);
+		}
+	}
+}
+
+static void
 merge_one_ip_config (NMResolvConfData *rc,
-                     const NMIPConfig *config,
-                     const char *iface)
+                     int ifindex,
+                     const NMIPConfig *ip_config)
 {
 	int addr_family;
-	guint num, num_domains, num_searches, i;
+	guint num, i;
 	char buf[NM_UTILS_INET_ADDRSTRLEN + 50];
-	const char *str;
 
-	addr_family = nm_ip_config_get_addr_family (config);
+	addr_family = nm_ip_config_get_addr_family (ip_config);
 
 	nm_assert_addr_family (addr_family);
+	nm_assert (ifindex > 0);
+	nm_assert (ifindex == nm_ip_config_get_ifindex (ip_config));
 
-	num = nm_ip_config_get_num_nameservers (config);
+	num = nm_ip_config_get_num_nameservers (ip_config);
 	for (i = 0; i < num; i++) {
 		const NMIPAddr *addr;
 
-		addr = nm_ip_config_get_nameserver (config, i);
+		addr = nm_ip_config_get_nameserver (ip_config, i);
 		if (addr_family == AF_INET)
 			nm_utils_inet_ntop (addr_family, addr, buf);
 		else if (IN6_IS_ADDR_V4MAPPED (addr))
@@ -307,49 +438,42 @@ merge_one_ip_config (NMResolvConfData *rc,
 		else {
 			nm_utils_inet6_ntop (&addr->addr6, buf);
 			if (IN6_IS_ADDR_LINKLOCAL (addr)) {
-				g_strlcat (buf, "%", sizeof (buf));
-				g_strlcat (buf, iface, sizeof (buf));
+				const char *ifname;
+
+				ifname = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
+				if (ifname) {
+					g_strlcat (buf, "%", sizeof (buf));
+					g_strlcat (buf, ifname, sizeof (buf));
+				}
 			}
 		}
 
-		add_string_item (rc->nameservers, buf);
+		add_string_item (rc->nameservers, buf, TRUE);
 	}
 
-	num_domains = nm_ip_config_get_num_domains (config);
-	num_searches = nm_ip_config_get_num_searches (config);
-	for (i = 0; i < num_searches; i++) {
-		str = nm_ip_config_get_search (config, i);
-		if (domain_is_valid (str, FALSE))
-			add_string_item (rc->searches, str);
-	}
-	if (num_domains > 1 || !num_searches) {
-		for (i = 0; i < num_domains; i++) {
-			str = nm_ip_config_get_domain (config, i);
-			if (domain_is_valid (str, FALSE))
-				add_string_item (rc->searches, str);
-		}
-	}
+	add_dns_domains (rc->searches, ip_config, FALSE, TRUE);
 
-	num = nm_ip_config_get_num_dns_options (config);
+	num = nm_ip_config_get_num_dns_options (ip_config);
 	for (i = 0; i < num; i++) {
 		add_dns_option_item (rc->options,
-		                     nm_ip_config_get_dns_option (config, i));
+		                     nm_ip_config_get_dns_option (ip_config, i));
 	}
 
 	if (addr_family == AF_INET) {
-		const NMIP4Config *config4 = (const NMIP4Config *) config;
+		const NMIP4Config *ip4_config = (const NMIP4Config *) ip_config;
 
 		/* NIS stuff */
-		num = nm_ip4_config_get_num_nis_servers (config4);
+		num = nm_ip4_config_get_num_nis_servers (ip4_config);
 		for (i = 0; i < num; i++) {
 			add_string_item (rc->nis_servers,
-			                 nm_utils_inet4_ntop (nm_ip4_config_get_nis_server (config4, i), buf));
+			                 nm_utils_inet4_ntop (nm_ip4_config_get_nis_server (ip4_config, i), buf),
+			                 TRUE);
 		}
 
-		if (nm_ip4_config_get_nis_domain (config4)) {
+		if (nm_ip4_config_get_nis_domain (ip4_config)) {
 			/* FIXME: handle multiple domains */
 			if (!rc->nis_domain)
-				rc->nis_domain = nm_ip4_config_get_nis_domain (config4);
+				rc->nis_domain = nm_ip4_config_get_nis_domain (ip4_config);
 		}
 	}
 }
@@ -378,58 +502,59 @@ run_netconfig (NMDnsManager *self, GError **error, gint *stdin_fd)
 }
 
 static void
-write_to_netconfig (NMDnsManager *self, gint fd, const char *key, const char *value)
+netconfig_construct_str (NMDnsManager *self, GString *str, const char *key, const char *value)
 {
-	char *str;
-	int x;
+	if (value) {
+		_LOGD ("writing to netconfig: %s='%s'", key, value);
+		g_string_append_printf (str, "%s='%s'\n", key, value);
+	}
+}
 
-	str = g_strdup_printf ("%s='%s'\n", key, value);
-	_LOGD ("writing to netconfig: %s", str);
-	x = write (fd, str, strlen (str));
-	g_free (str);
+static void
+netconfig_construct_strv (NMDnsManager *self, GString *str, const char *key, const char *const*values)
+{
+	if (values) {
+		gs_free char *value = NULL;
+
+		value = g_strjoinv (" ", (char **) values);
+		netconfig_construct_str (self, str, key, value);
+	}
 }
 
 static SpawnResult
 dispatch_netconfig (NMDnsManager *self,
-                    char **searches,
-                    char **nameservers,
+                    const char *const*searches,
+                    const char *const*nameservers,
                     const char *nis_domain,
-                    char **nis_servers,
+                    const char *const*nis_servers,
                     GError **error)
 {
-	char *str;
 	GPid pid;
 	gint fd;
 	int status;
+	gssize l;
+	nm_auto_free_gstring GString *str = NULL;
 
 	pid = run_netconfig (self, error, &fd);
 	if (pid <= 0)
 		return SR_NOTFOUND;
 
+	str = g_string_new ("");
+
 	/* NM is writing already-merged DNS information to netconfig, so it
 	 * does not apply to a specific network interface.
 	 */
-	write_to_netconfig (self, fd, "INTERFACE", "NetworkManager");
+	netconfig_construct_str (self, str, "INTERFACE", "NetworkManager");
+	netconfig_construct_strv (self, str, "DNSSEARCH", searches);
+	netconfig_construct_strv (self, str, "DNSSERVERS", nameservers);
+	netconfig_construct_str (self, str, "NISDOMAIN", nis_domain);
+	netconfig_construct_strv (self, str, "NISSERVERS", nis_servers);
 
-	if (searches) {
-		str = g_strjoinv (" ", searches);
-		write_to_netconfig (self, fd, "DNSSEARCH", str);
-		g_free (str);
-	}
-
-	if (nameservers) {
-		str = g_strjoinv (" ", nameservers);
-		write_to_netconfig (self, fd, "DNSSERVERS", str);
-		g_free (str);
-	}
-
-	if (nis_domain)
-		write_to_netconfig (self, fd, "NISDOMAIN", nis_domain);
-
-	if (nis_servers) {
-		str = g_strjoinv (" ", nis_servers);
-		write_to_netconfig (self, fd, "NISSERVERS", str);
-		g_free (str);
+again:
+	l = write (fd, str->str, str->len);
+	if (l == -1)  {
+		if (errno == EINTR)
+			goto again;
 	}
 
 	nm_close (fd);
@@ -743,7 +868,7 @@ update_resolv_conf (NMDnsManager *self,
 
 	if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) {
 		_LOGT ("update-resolv-conf: write internal file %s succeeded (rc-manager=%s)",
-		       rc_path, _rc_manager_to_string (rc_manager));
+		       MY_RESOLV_CONF, _rc_manager_to_string (rc_manager));
 		return write_file_result;
 	}
 
@@ -817,25 +942,21 @@ update_resolv_conf (NMDnsManager *self,
 static void
 compute_hash (NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[HASH_LEN])
 {
-	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 	GChecksum *sum;
 	gsize len = HASH_LEN;
-	guint i;
+	NMDnsIPConfigData *ip_data;
 
 	sum = g_checksum_new (G_CHECKSUM_SHA1);
-	g_assert (len == g_checksum_type_get_length (G_CHECKSUM_SHA1));
+	nm_assert (len == g_checksum_type_get_length (G_CHECKSUM_SHA1));
 
 	if (global)
 		nm_global_dns_config_update_checksum (global, sum);
 	else {
-		for (i = 0; i < priv->configs->len; i++) {
-			NMDnsIPConfigData *data = priv->configs->pdata[i];
+		const CList *head;
 
-			if (NM_IS_IP4_CONFIG (data->config))
-				nm_ip4_config_hash ((NMIP4Config *) data->config, sum, TRUE);
-			else if (NM_IS_IP6_CONFIG (data->config))
-				nm_ip6_config_hash ((NMIP6Config *) data->config, sum, TRUE);
-		}
+		head = _ip_config_lst_head (self);
+		c_list_for_each_entry (ip_data, head, ip_config_lst)
+			nm_ip_config_hash (ip_data->ip_config, sum, TRUE);
 	}
 
 	g_checksum_get_digest (sum, buffer, &len);
@@ -849,27 +970,36 @@ merge_global_dns_config (NMResolvConfData *rc, NMGlobalDnsConfig *global_conf)
 	const char *const *searches;
 	const char *const *options;
 	const char *const *servers;
-	gint i;
+	guint i;
 
 	if (!global_conf)
 		return FALSE;
 
 	searches = nm_global_dns_config_get_searches (global_conf);
-	options = nm_global_dns_config_get_options (global_conf);
-
-	for (i = 0; searches && searches[i]; i++) {
-		if (domain_is_valid (searches[i], FALSE))
-			add_string_item (rc->searches, searches[i]);
+	if (searches) {
+		for (i = 0; searches[i]; i++) {
+			if (domain_is_routing (searches[i]))
+				continue;
+			if (!domain_is_valid (searches[i], FALSE))
+				continue;
+			add_string_item (rc->searches, searches[i], TRUE);
+		}
 	}
 
-	for (i = 0; options && options[i]; i++)
-		add_string_item (rc->options, options[i]);
+	options = nm_global_dns_config_get_options (global_conf);
+	if (options) {
+		for (i = 0; options[i]; i++)
+			add_string_item (rc->options, options[i], TRUE);
+	}
 
 	default_domain = nm_global_dns_config_lookup_domain (global_conf, "*");
-	g_assert (default_domain);
+	nm_assert (default_domain);
+
 	servers = nm_global_dns_domain_get_servers (default_domain);
-	for (i = 0; servers && servers[i]; i++)
-		add_string_item (rc->nameservers, servers[i]);
+	if (servers) {
+		for (i = 0; servers[i]; i++)
+			add_string_item (rc->nameservers, servers[i], TRUE);
+	}
 
 	return TRUE;
 }
@@ -909,17 +1039,16 @@ _ptrarray_to_strv (GPtrArray *parray)
 }
 
 static void
-_collect_resolv_conf_data (NMDnsManager *self, /* only for logging context, no other side-effects */
+_collect_resolv_conf_data (NMDnsManager *self,
                            NMGlobalDnsConfig *global_config,
-                           const GPtrArray *configs,
-                           const char *hostname,
                            char ***out_searches,
                            char ***out_options,
                            char ***out_nameservers,
                            char ***out_nis_servers,
                            const char **out_nis_domain)
 {
-	guint i, j, num, len;
+	NMDnsManagerPrivate *priv;
+	guint i, num, len;
 	NMResolvConfData rc = {
 		.nameservers = g_ptr_array_new (),
 		.searches = g_ptr_array_new (),
@@ -928,37 +1057,44 @@ _collect_resolv_conf_data (NMDnsManager *self, /* only for logging context, no o
 		.nis_servers = g_ptr_array_new (),
 	};
 
+	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+
 	if (global_config)
 		merge_global_dns_config (&rc, global_config);
 	else {
 		nm_auto_free_gstring GString *tmp_gstring = NULL;
 		int prio, first_prio = 0;
-		NMDnsIPConfigData *current;
+		const NMDnsIPConfigData *ip_data;
+		const CList *head;
+		gboolean is_first = TRUE;
 
-		for (i = 0, j = 0; i < configs->len; i++) {
+		head = _ip_config_lst_head (self);
+		c_list_for_each_entry (ip_data, head, ip_config_lst) {
 			gboolean skip = FALSE;
 
-			current = configs->pdata[i];
+			_ASSERT_ip_config_data (ip_data);
 
-			prio = nm_ip_config_get_dns_priority (current->config);
+			prio = nm_ip_config_get_dns_priority (ip_data->ip_config);
 
-			if (i == 0)
+			if (is_first) {
+				is_first = FALSE;
 				first_prio = prio;
-			else if (first_prio < 0 && first_prio != prio)
+			} else if (   first_prio < 0
+			           && first_prio != prio)
 				skip = TRUE;
 
-			if (nm_ip_config_get_num_nameservers (current->config)) {
-				_LOGT ("config: %8d %-7s v%c %-16s %s: %s",
+			if (nm_ip_config_get_num_nameservers (ip_data->ip_config)) {
+				_LOGT ("config: %8d %-7s v%c %-5d %s: %s",
 				       prio,
-				       _config_type_to_string (current->type),
-				       nm_utils_addr_family_to_char (nm_ip_config_get_addr_family (current->config)),
-				       current->iface,
+				       _config_type_to_string (ip_data->ip_config_type),
+				       nm_utils_addr_family_to_char (nm_ip_config_get_addr_family (ip_data->ip_config)),
+				       ip_data->data->ifindex,
 				       skip ? "<SKIP>" : "",
-				       get_nameserver_list (current->config, &tmp_gstring));
+				       get_nameserver_list (ip_data->ip_config, &tmp_gstring));
 			}
 
 			if (!skip)
-				merge_one_ip_config (&rc, current->config, current->iface);
+				merge_one_ip_config (&rc, ip_data->data->ifindex, ip_data->ip_config);
 		}
 	}
 
@@ -969,16 +1105,16 @@ _collect_resolv_conf_data (NMDnsManager *self, /* only for logging context, no o
 	 * (eg, "example.com"), then use the hostname itself as the search (since the user is
 	 * unlikely to want "com" as a search domain).
 	 */
-	if (hostname) {
-		const char *hostdomain = strchr (hostname, '.');
+	if (priv->hostname) {
+		const char *hostdomain = strchr (priv->hostname, '.');
 
 		if (   hostdomain
-		    && !nm_utils_ipaddr_valid (AF_UNSPEC, hostname)) {
+		    && !nm_utils_ipaddr_valid (AF_UNSPEC, priv->hostname)) {
 			hostdomain++;
 			if (domain_is_valid (hostdomain, TRUE))
-				add_string_item (rc.searches, hostdomain);
-			else if (domain_is_valid (hostname, TRUE))
-				add_string_item (rc.searches, hostname);
+				add_string_item (rc.searches, hostdomain, TRUE);
+			else if (domain_is_valid (priv->hostname, TRUE))
+				add_string_item (rc.searches, priv->hostname, TRUE);
 		}
 	}
 
@@ -1040,16 +1176,12 @@ update_dns (NMDnsManager *self,
 	data = nm_config_get_data (priv->config);
 	global_config = nm_config_data_get_global_dns_config (data);
 
-	if (priv->need_sort) {
-		g_ptr_array_sort (priv->configs, ip_config_data_ptr_compare);
-		priv->need_sort = FALSE;
-	}
-
 	/* Update hash with config we're applying */
 	compute_hash (self, global_config, priv->hash);
 
-	_collect_resolv_conf_data (self, global_config, priv->configs, priv->hostname,
-	                           &searches, &options, &nameservers, &nis_servers, &nis_domain);
+	_collect_resolv_conf_data (self, global_config,
+	                           &searches, &options, &nameservers,
+	                           &nis_servers, &nis_domain);
 
 	/* Let any plugins do their thing first */
 	if (priv->plugin) {
@@ -1067,8 +1199,8 @@ update_dns (NMDnsManager *self,
 
 		_LOGD ("update-dns: updating plugin %s", plugin_name);
 		if (!nm_dns_plugin_update (plugin,
-		                           priv->configs,
 		                           global_config,
+		                           _ip_config_lst_head (self),
 		                           priv->hostname)) {
 			_LOGW ("update-dns: plugin %s update failed", plugin_name);
 
@@ -1114,8 +1246,12 @@ update_dns (NMDnsManager *self,
 			result = dispatch_resolvconf (self, searches, nameservers, options, error);
 			break;
 		case NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG:
-			result = dispatch_netconfig (self, searches, nameservers, nis_domain,
-			                             nis_servers, error);
+			result = dispatch_netconfig (self,
+			                             (const char *const*) searches,
+			                             (const char *const*) nameservers,
+			                             nis_domain,
+			                             (const char *const*) nis_servers,
+			                             error);
 			break;
 		default:
 			g_assert_not_reached ();
@@ -1205,82 +1341,96 @@ plugin_child_quit (NMDnsPlugin *plugin, int exit_status, gpointer user_data)
 }
 
 static void
-ip_config_dns_priority_changed (gpointer config,
-                                GParamSpec *pspec,
-                                NMDnsManager *self)
+_ip_config_dns_priority_changed (gpointer config,
+                                 GParamSpec *pspec,
+                                 NMDnsIPConfigData *ip_data)
 {
-	NM_DNS_MANAGER_GET_PRIVATE (self)->need_sort = TRUE;
-}
+	_ASSERT_ip_config_data (ip_data);
 
-static void
-forget_data (NMDnsManager *self, NMDnsIPConfigData *data)
-{
-	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-
-	if (data == priv->best_conf4)
-		priv->best_conf4 = NULL;
-	else if (data == priv->best_conf6)
-		priv->best_conf6 = NULL;
-
-	g_signal_handlers_disconnect_by_func (data->config, ip_config_dns_priority_changed, self);
+	NM_DNS_MANAGER_GET_PRIVATE (ip_data->data->self)->ip_config_lst_need_sort = TRUE;
 }
 
 gboolean
-nm_dns_manager_add_ip_config (NMDnsManager *self,
-                              const char *iface,
-                              gpointer config,
-                              NMDnsIPConfigType cfg_type)
+nm_dns_manager_set_ip_config (NMDnsManager *self,
+                              NMIPConfig *ip_config,
+                              NMDnsIPConfigType ip_config_type)
 {
 	NMDnsManagerPrivate *priv;
 	GError *error = NULL;
-	NMDnsIPConfigData *data;
-	gboolean v4 = NM_IS_IP4_CONFIG (config);
-	guint i;
+	NMDnsIPConfigData *ip_data;
+	NMDnsConfigData *data;
+	int ifindex;
+	NMDnsIPConfigData **p_best;
 
 	g_return_val_if_fail (NM_IS_DNS_MANAGER (self), FALSE);
-	g_return_val_if_fail (config, FALSE);
-	g_return_val_if_fail (iface && iface[0], FALSE);
-	nm_assert (NM_IS_IP_CONFIG (config));
+	g_return_val_if_fail (NM_IS_IP_CONFIG (ip_config), FALSE);
+
+	ifindex = nm_ip_config_get_ifindex (ip_config);
+	g_return_val_if_fail (ifindex > 0, FALSE);
 
 	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 
-	for (i = 0; i < priv->configs->len; i++) {
-		data = priv->configs->pdata[i];
-		if (data->config == config) {
-			if (   nm_streq (data->iface, iface)
-			    && data->type == cfg_type)
-				return FALSE;
-			else {
-				forget_data (self, data);
-				g_ptr_array_remove_index_fast (priv->configs, i);
-				break;
-			}
-		}
+	data = g_hash_table_lookup (priv->configs, GINT_TO_POINTER (ifindex));
+	if (!data)
+		ip_data = NULL;
+	else
+		ip_data = _config_data_find_ip_config (data, ip_config);
+
+	if (ip_config_type == NM_DNS_IP_CONFIG_TYPE_REMOVED) {
+		if (!ip_data)
+			return FALSE;
+		if (priv->best_ip_config_4 == ip_data)
+			priv->best_ip_config_4 = NULL;
+		if (priv->best_ip_config_6 == ip_data)
+			priv->best_ip_config_6 = NULL;
+		/* deleting a config doesn't invalidate the configs' sort order. */
+		_ip_config_data_free (ip_data);
+		if (c_list_is_empty (&data->data_lst_head))
+			g_hash_table_remove (priv->configs, GINT_TO_POINTER (ifindex));
+		goto changed;
+	}
+
+	if (   ip_data
+	    && ip_data->ip_config_type == ip_config_type) {
+		/* nothing to do. */
+		return FALSE;
 	}
 
-	data = ip_config_data_new (config, cfg_type, iface);
-	g_ptr_array_add (priv->configs, data);
-	g_signal_connect (config,
-	                  v4 ?
-	                    "notify::" NM_IP4_CONFIG_DNS_PRIORITY :
-	                    "notify::" NM_IP6_CONFIG_DNS_PRIORITY,
-	                  (GCallback) ip_config_dns_priority_changed, self);
-	priv->need_sort = TRUE;
+	if (!data) {
+		data = g_slice_new0 (NMDnsConfigData);
+		data->ifindex = ifindex;
+		data->self = self;
+		c_list_init (&data->data_lst_head);
+		_ASSERT_config_data (data);
+		g_hash_table_insert (priv->configs, GINT_TO_POINTER (ifindex), data);
+	}
+
+	if (!ip_data)
+		ip_data = _ip_config_data_new (data, ip_config, ip_config_type);
+	else
+		ip_data->ip_config_type = ip_config_type;
+
+	priv->ip_config_lst_need_sort = TRUE;
+
+	p_best = NM_IS_IP4_CONFIG (ip_config)
+	         ? &priv->best_ip_config_4
+	         : &priv->best_ip_config_6;
 
-	if (cfg_type == NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE) {
+	if (ip_config_type == NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE) {
 		/* Only one best-device per IP version is allowed */
-		if (v4) {
-			if (priv->best_conf4)
-				priv->best_conf4->type = NM_DNS_IP_CONFIG_TYPE_DEFAULT;
-			priv->best_conf4 = data;
-		} else {
-			if (priv->best_conf6)
-				priv->best_conf6->type = NM_DNS_IP_CONFIG_TYPE_DEFAULT;
-			priv->best_conf6 = data;
+		if (*p_best != ip_data) {
+			if (*p_best)
+				(*p_best)->ip_config_type = NM_DNS_IP_CONFIG_TYPE_DEFAULT;
+			*p_best = ip_data;
 		}
+	} else {
+		if (*p_best == ip_data)
+			*p_best = NULL;
 	}
 
-	if (!priv->updates_queue && !update_dns (self, FALSE, &error)) {
+changed:
+	if (   !priv->updates_queue
+	    && !update_dns (self, FALSE, &error)) {
 		_LOGW ("could not commit DNS changes: %s", error->message);
 		g_clear_error (&error);
 	}
@@ -1288,38 +1438,6 @@ nm_dns_manager_add_ip_config (NMDnsManager *self,
 	return TRUE;
 }
 
-gboolean
-nm_dns_manager_remove_ip_config (NMDnsManager *self, gpointer config)
-{
-	NMDnsManagerPrivate *priv;
-	GError *error = NULL;
-	NMDnsIPConfigData *data;
-	guint i;
-
-	g_return_val_if_fail (NM_IS_DNS_MANAGER (self), FALSE);
-	g_return_val_if_fail (config, FALSE);
-	nm_assert (NM_IS_IP_CONFIG (config));
-
-	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-
-	for (i = 0; i < priv->configs->len; i++) {
-		data = priv->configs->pdata[i];
-
-		if (data->config == config) {
-			forget_data (self, data);
-			g_ptr_array_remove_index (priv->configs, i);
-
-			if (!priv->updates_queue && !update_dns (self, FALSE, &error)) {
-				_LOGW ("could not commit DNS changes: %s", error->message);
-				g_clear_error (&error);
-			}
-
-			return TRUE;
-		}
-	}
-	return FALSE;
-}
-
 void
 nm_dns_manager_set_initial_hostname (NMDnsManager *self,
                                      const char *hostname)
@@ -1362,23 +1480,6 @@ nm_dns_manager_set_hostname (NMDnsManager *self,
 	}
 }
 
-gboolean
-nm_dns_manager_get_resolv_conf_explicit (NMDnsManager *self)
-{
-	NMDnsManagerPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_DNS_MANAGER (self), FALSE);
-
-	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-
-	if (   NM_IN_SET (priv->rc_manager, NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED,
-	                                    NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE)
-	    || priv->plugin)
-		return FALSE;
-
-	return TRUE;
-}
-
 void
 nm_dns_manager_begin_updates (NMDnsManager *self, const char *func)
 {
@@ -1409,11 +1510,6 @@ nm_dns_manager_end_updates (NMDnsManager *self, const char *func)
 	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 	g_return_if_fail (priv->updates_queue > 0);
 
-	if (priv->need_sort) {
-		g_ptr_array_sort (priv->configs, ip_config_data_ptr_compare);
-		priv->need_sort = FALSE;
-	}
-
 	compute_hash (self, nm_config_data_get_global_dns_config (nm_config_get_data (priv->config)), new);
 	changed = (memcmp (new, priv->prev_hash, sizeof (new)) != 0) ? TRUE : FALSE;
 	_LOGD ("(%s): DNS configuration %s", func, changed ? "changed" : "did not change");
@@ -1810,14 +1906,14 @@ _get_config_variant (NMDnsManager *self)
 	NMGlobalDnsConfig *global_config;
 	gs_free char *str = NULL;
 	GVariantBuilder builder;
-	NMConfigData *data;
-	guint i, j;
+	NMDnsIPConfigData *ip_data;
+	const CList *head;
+	gs_unref_ptrarray GPtrArray *array_domains = NULL;
 
 	if (priv->config_variant)
 		return priv->config_variant;
 
-	data = nm_config_get_data (priv->config);
-	global_config = nm_config_data_get_global_dns_config (data);
+	global_config = nm_config_data_get_global_dns_config (nm_config_get_data (priv->config));
 	if (global_config) {
 		priv->config_variant = _get_global_config_variant (global_config);
 		_LOGT ("current configuration: %s", (str = g_variant_print (priv->config_variant, TRUE)));
@@ -1826,25 +1922,26 @@ _get_config_variant (NMDnsManager *self)
 
 	g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
 
-	for (i = 0; i < priv->configs->len; i++) {
-		NMDnsIPConfigData *current = priv->configs->pdata[i];
-		const NMIPConfig *config = current->config;
+	head = _ip_config_lst_head (self);
+	c_list_for_each_entry (ip_data, head, ip_config_lst) {
+		const NMIPConfig *ip_config = ip_data->ip_config;
 		GVariantBuilder entry_builder;
 		GVariantBuilder strv_builder;
-		guint num;
-		const int addr_family = nm_ip_config_get_addr_family (config);
+		guint i, num;
+		const int addr_family = nm_ip_config_get_addr_family (ip_config);
 		char buf[NM_UTILS_INET_ADDRSTRLEN];
 		const NMIPAddr *addr;
+		const char *ifname;
 
-		num = nm_ip_config_get_num_nameservers (config);
+		num = nm_ip_config_get_num_nameservers (ip_config);
 		if (!num)
 			continue;
 
 		g_variant_builder_init (&entry_builder, G_VARIANT_TYPE ("a{sv}"));
 
 		g_variant_builder_init (&strv_builder, G_VARIANT_TYPE ("as"));
-		for (j = 0; j < num; j++) {
-			addr = nm_ip_config_get_nameserver (config, j);
+		for (i = 0; i < num; i++) {
+			addr = nm_ip_config_get_nameserver (ip_config, i);
 			g_variant_builder_add (&strv_builder,
 			                       "s",
 			                       nm_utils_inet_ntop (addr_family, addr, buf));
@@ -1854,36 +1951,47 @@ _get_config_variant (NMDnsManager *self)
 		                       "nameservers",
 		                       g_variant_builder_end (&strv_builder));
 
-		num = nm_ip_config_get_num_domains (config);
+
+		num = nm_ip_config_get_num_domains (ip_config);
+		num += nm_ip_config_get_num_searches (ip_config);
 		if (num > 0) {
-			g_variant_builder_init (&strv_builder, G_VARIANT_TYPE ("as"));
-			for (j = 0; j < num; j++) {
-				g_variant_builder_add (&strv_builder,
-				                       "s",
-				                       nm_ip_config_get_domain (config, j));
+			if (!array_domains)
+				array_domains = g_ptr_array_sized_new (num);
+			else
+				g_ptr_array_set_size (array_domains, 0);
+
+			add_dns_domains (array_domains, ip_config, TRUE, FALSE);
+			if (array_domains->len) {
+				g_variant_builder_init (&strv_builder, G_VARIANT_TYPE ("as"));
+				for (i = 0; i < array_domains->len; i++) {
+					g_variant_builder_add (&strv_builder,
+					                       "s",
+					                       array_domains->pdata[i]);
+				}
+				g_variant_builder_add (&entry_builder,
+				                       "{sv}",
+				                       "domains",
+				                       g_variant_builder_end (&strv_builder));
 			}
-			g_variant_builder_add (&entry_builder,
-			                       "{sv}",
-			                       "domains",
-			                       g_variant_builder_end (&strv_builder));
 		}
 
-		if (current->iface) {
+		ifname = nm_platform_link_get_name (NM_PLATFORM_GET, ip_data->data->ifindex);
+		if (ifname) {
 			g_variant_builder_add (&entry_builder,
 			                       "{sv}",
 			                       "interface",
-			                       g_variant_new_string (current->iface));
+			                       g_variant_new_string (ifname));
 		}
 
 		g_variant_builder_add (&entry_builder,
 		                       "{sv}",
 		                       "priority",
-		                       g_variant_new_int32 (nm_ip_config_get_dns_priority (config)));
+		                       g_variant_new_int32 (nm_ip_config_get_dns_priority (ip_config)));
 
 		g_variant_builder_add (&entry_builder,
 		                       "{sv}",
 		                       "vpn",
-		                       g_variant_new_boolean (current->type == NM_DNS_IP_CONFIG_TYPE_VPN));
+		                       g_variant_new_boolean (ip_data->ip_config_type == NM_DNS_IP_CONFIG_TYPE_VPN));
 
 		g_variant_builder_add (&builder, "a{sv}", &entry_builder);
 	}
@@ -1924,8 +2032,12 @@ nm_dns_manager_init (NMDnsManager *self)
 
 	_LOGT ("creating...");
 
+	c_list_init (&priv->ip_config_lst_head);
+
 	priv->config = g_object_ref (nm_config_get ());
-	priv->configs = g_ptr_array_new_full (8, ip_config_data_destroy);
+
+	priv->configs = g_hash_table_new_full (nm_direct_hash, NULL,
+	                                       NULL, (GDestroyNotify) _config_data_free);
 
 	/* Set the initial hash */
 	compute_hash (self, NULL, NM_DNS_MANAGER_GET_PRIVATE (self)->hash);
@@ -1942,33 +2054,33 @@ dispose (GObject *object)
 {
 	NMDnsManager *self = NM_DNS_MANAGER (object);
 	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-	NMDnsIPConfigData *data;
-	guint i;
+	NMDnsIPConfigData *ip_data, *ip_data_safe;
 
 	_LOGT ("disposing");
 
 	if (!priv->is_stopped)
 		nm_dns_manager_stop (self);
 
+	if (priv->config)
+		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
+
 	_clear_plugin (self);
 
-	if (priv->config) {
-		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
-		g_clear_object (&priv->config);
-	}
+	priv->best_ip_config_4 = NULL;
+	priv->best_ip_config_6 = NULL;
 
-	if (priv->configs) {
-		for (i = 0; i < priv->configs->len; i++) {
-			data = priv->configs->pdata[i];
-			forget_data (self, data);
-		}
-		g_ptr_array_free (priv->configs, TRUE);
-		priv->configs = NULL;
-	}
+	c_list_for_each_entry_safe (ip_data, ip_data_safe, &priv->ip_config_lst_head, ip_config_lst)
+		_ip_config_data_free (ip_data);
+
+	g_clear_pointer (&priv->configs, g_hash_table_destroy);
 
 	nm_clear_g_source (&priv->plugin_ratelimit.timer);
 
+	g_clear_object (&priv->config);
+
 	G_OBJECT_CLASS (nm_dns_manager_parent_class)->dispose (object);
+
+	g_clear_pointer (&priv->config_variant, g_variant_unref);
 }
 
 static void
@@ -1983,18 +2095,30 @@ finalize (GObject *object)
 	G_OBJECT_CLASS (nm_dns_manager_parent_class)->finalize (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_dns_manager = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DNS_MANAGER,
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",          "s",      NM_DNS_MANAGER_MODE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("RcManager",     "s",      NM_DNS_MANAGER_RC_MANAGER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Configuration", "aa{sv}", NM_DNS_MANAGER_CONFIGURATION),
+		),
+	),
+};
+
 static void
 nm_dns_manager_class_init (NMDnsManagerClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 	object_class->get_property = get_property;
 
-	exported_object_class->export_path = NM_DBUS_PATH "/DnsManager";
-	exported_object_class->export_on_construction = TRUE;
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC (NM_DBUS_PATH "/DnsManager");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_dns_manager);
+	dbus_object_class->export_on_construction = TRUE;
 
 	obj_properties[PROP_MODE] =
 	    g_param_spec_string (NM_DNS_MANAGER_MODE, "", "",
@@ -2024,9 +2148,4 @@ nm_dns_manager_class_init (NMDnsManagerClass *klass)
 	                  0, NULL, NULL,
 	                  g_cclosure_marshal_VOID__VOID,
 	                  G_TYPE_NONE, 0);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DNS_MANAGER_SKELETON,
-	                                        NULL);
 }
-
diff --git a/src/dns/nm-dns-manager.h b/src/dns/nm-dns-manager.h
index b38ea701..56889367 100644
--- a/src/dns/nm-dns-manager.h
+++ b/src/dns/nm-dns-manager.h
@@ -26,11 +26,14 @@
 
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
+#include "nm-setting-connection.h"
 
 typedef enum {
+	NM_DNS_IP_CONFIG_TYPE_REMOVED = -1,
+
 	NM_DNS_IP_CONFIG_TYPE_DEFAULT = 0,
 	NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE,
-	NM_DNS_IP_CONFIG_TYPE_VPN
+	NM_DNS_IP_CONFIG_TYPE_VPN,
 } NMDnsIPConfigType;
 
 enum {
@@ -38,12 +41,23 @@ enum {
 	NM_DNS_PRIORITY_DEFAULT_VPN     = 50,
 };
 
+struct _NMDnsConfigData;
+struct _NMDnsManager;
+
 typedef struct {
-	gpointer config;
-	NMDnsIPConfigType type;
-	char *iface;
+	struct _NMDnsConfigData *data;
+	NMIPConfig *ip_config;
+	CList data_lst;
+	CList ip_config_lst;
+	NMDnsIPConfigType ip_config_type;
 } NMDnsIPConfigData;
 
+typedef struct _NMDnsConfigData {
+	struct _NMDnsManager *self;
+	CList data_lst_head;
+	int ifindex;
+} NMDnsConfigData;
+
 #define NM_TYPE_DNS_MANAGER (nm_dns_manager_get_type ())
 #define NM_DNS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NM_TYPE_DNS_MANAGER, NMDnsManager))
 #define NM_DNS_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NM_TYPE_DNS_MANAGER, NMDnsManagerClass))
@@ -70,12 +84,9 @@ NMDnsManager * nm_dns_manager_get (void);
 void nm_dns_manager_begin_updates (NMDnsManager *self, const char *func);
 void nm_dns_manager_end_updates (NMDnsManager *self, const char *func);
 
-gboolean nm_dns_manager_add_ip_config (NMDnsManager *self,
-                                       const char *iface,
-                                       gpointer config,
-                                       NMDnsIPConfigType cfg_type);
-
-gboolean nm_dns_manager_remove_ip_config (NMDnsManager *self, gpointer config);
+gboolean nm_dns_manager_set_ip_config (NMDnsManager *self,
+                                       NMIPConfig *ip_config,
+                                       NMDnsIPConfigType ip_config_type);
 
 void nm_dns_manager_set_initial_hostname (NMDnsManager *self,
                                           const char *hostname);
@@ -112,8 +123,6 @@ typedef enum {
 	NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG,
 } NMDnsManagerResolvConfManager;
 
-gboolean nm_dns_manager_get_resolv_conf_explicit (NMDnsManager *self);
-
 void nm_dns_manager_stop (NMDnsManager *self);
 
 #endif /* __NETWORKMANAGER_DNS_MANAGER_H__ */
diff --git a/src/dns/nm-dns-plugin.c b/src/dns/nm-dns-plugin.c
index 5805b7d8..d9400e3e 100644
--- a/src/dns/nm-dns-plugin.c
+++ b/src/dns/nm-dns-plugin.c
@@ -77,15 +77,15 @@ G_DEFINE_TYPE_EXTENDED (NMDnsPlugin, nm_dns_plugin, G_TYPE_OBJECT, G_TYPE_FLAG_A
 
 gboolean
 nm_dns_plugin_update (NMDnsPlugin *self,
-                      const GPtrArray *configs,
                       const NMGlobalDnsConfig *global_config,
+                      const CList *ip_config_lst_head,
                       const char *hostname)
 {
 	g_return_val_if_fail (NM_DNS_PLUGIN_GET_CLASS (self)->update != NULL, FALSE);
 
 	return NM_DNS_PLUGIN_GET_CLASS (self)->update (self,
-	                                               configs,
 	                                               global_config,
+	                                               ip_config_lst_head,
 	                                               hostname);
 }
 
diff --git a/src/dns/nm-dns-plugin.h b/src/dns/nm-dns-plugin.h
index 996695c0..80b77d95 100644
--- a/src/dns/nm-dns-plugin.h
+++ b/src/dns/nm-dns-plugin.h
@@ -50,8 +50,8 @@ typedef struct {
 	 * configuration.
 	 */
 	gboolean (*update) (NMDnsPlugin *self,
-	                    const GPtrArray *configs,
 	                    const NMGlobalDnsConfig *global_config,
+	                    const CList *ip_config_lst_head,
 	                    const char *hostname);
 
 	/* Subclasses should override and return TRUE if they start a local
@@ -80,8 +80,8 @@ gboolean nm_dns_plugin_is_caching (NMDnsPlugin *self);
 const char *nm_dns_plugin_get_name (NMDnsPlugin *self);
 
 gboolean nm_dns_plugin_update (NMDnsPlugin *self,
-                               const GPtrArray *configs,
                                const NMGlobalDnsConfig *global_config,
+                               const CList *ip_config_lst_head,
                                const char *hostname);
 
 void nm_dns_plugin_stop (NMDnsPlugin *self);
diff --git a/src/dns/nm-dns-systemd-resolved.c b/src/dns/nm-dns-systemd-resolved.c
index 6ab2ea18..7da27e5f 100644
--- a/src/dns/nm-dns-systemd-resolved.c
+++ b/src/dns/nm-dns-systemd-resolved.c
@@ -31,13 +31,15 @@
 #include <sys/stat.h>
 #include <linux/if.h>
 
+#include "nm-utils/nm-c-list.h"
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
 #include "nm-utils.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
 #include "nm-manager.h"
+#include "nm-setting-connection.h"
 #include "devices/nm-device.h"
 #include "NetworkManagerUtils.h"
 
@@ -48,17 +50,23 @@
 
 typedef struct {
 	int ifindex;
-	GList *configs;
+	CList configs_lst_head;
 } InterfaceConfig;
 
+typedef struct {
+	CList request_queue_lst;
+	const char *operation;
+	GVariant *argument;
+} RequestItem;
+
 /*****************************************************************************/
 
 typedef struct {
 	GDBusProxy *resolve;
 	GCancellable *init_cancellable;
 	GCancellable *update_cancellable;
-	GQueue dns_updates;
-	GQueue domain_updates;
+	GCancellable *mdns_cancellable;
+	CList request_queue_lst_head;
 } NMDnsSystemdResolvedPrivate;
 
 struct _NMDnsSystemdResolved {
@@ -82,129 +90,95 @@ G_DEFINE_TYPE (NMDnsSystemdResolved, nm_dns_systemd_resolved, NM_TYPE_DNS_PLUGIN
 /*****************************************************************************/
 
 static void
-call_done (GObject *source, GAsyncResult *r, gpointer user_data)
+_request_item_free (RequestItem *request_item)
 {
-	GVariant *v;
-	GError *error = NULL;
-	NMDnsSystemdResolved *self = (NMDnsSystemdResolved *) user_data;
-
-	v = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), r, &error);
+	c_list_unlink_stale (&request_item->request_queue_lst);
+	g_variant_unref (request_item->argument);
+	g_slice_free (RequestItem, request_item);
+}
 
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
+static void
+_request_item_append (CList *request_queue_lst_head,
+                      const char *operation,
+                      GVariant *argument)
+{
+	RequestItem *request_item;
 
-	if (error != NULL) {
-		_LOGW ("Failed: %s\n", error->message);
-		g_error_free (error);
-	}
+	request_item = g_slice_new (RequestItem);
+	request_item->operation = operation;
+	request_item->argument = g_variant_ref_sink (argument);
+	c_list_link_tail (request_queue_lst_head, &request_item->request_queue_lst);
 }
 
+/*****************************************************************************/
+
 static void
-add_interface_configuration (NMDnsSystemdResolved *self,
-                             GArray *interfaces,
-                             const NMDnsIPConfigData *data,
-                             gboolean skip)
+_interface_config_free (InterfaceConfig *config)
 {
-	int i;
-	InterfaceConfig *ic = NULL;
-	int ifindex;
+	nm_c_list_elem_free_all (&config->configs_lst_head, NULL);
+	g_slice_free (InterfaceConfig, config);
+}
 
-	if (NM_IS_IP4_CONFIG (data->config))
-		ifindex = nm_ip4_config_get_ifindex (data->config);
-	else if (NM_IS_IP6_CONFIG  (data->config))
-		ifindex = nm_ip6_config_get_ifindex (data->config);
-	else
-		g_return_if_reached ();
-
-	for (i = 0; i < interfaces->len; i++) {
-		InterfaceConfig *tic = &g_array_index (interfaces, InterfaceConfig, i);
-		if (ifindex == tic->ifindex) {
-			ic = tic;
-			break;
-		}
-	}
+static void
+call_done (GObject *source, GAsyncResult *r, gpointer user_data)
+{
+	GVariant *v;
+	GError *error = NULL;
+	NMDnsSystemdResolved *self = (NMDnsSystemdResolved *) user_data;
 
-	if (!ic) {
-		g_array_set_size (interfaces, interfaces->len + 1);
-		ic = &g_array_index (interfaces, InterfaceConfig,
-		                     interfaces->len - 1);
-		ic->ifindex = ifindex;
+	v = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), r, &error);
+	if (!v) {
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			return;
+		_LOGW ("Failed: %s\n", error->message);
+		g_error_free (error);
 	}
-
-	if (!skip)
-		ic->configs = g_list_append (ic->configs, data->config);
 }
 
 static void
 update_add_ip_config (NMDnsSystemdResolved *self,
                       GVariantBuilder *dns,
                       GVariantBuilder *domains,
-                      gpointer config)
+                      NMIPConfig *config)
 {
 	int addr_family;
 	gsize addr_size;
 	guint i, n;
-	gboolean route_only;
-
-	if (NM_IS_IP4_CONFIG (config))
-		addr_family = AF_INET;
-	else if (NM_IS_IP6_CONFIG (config))
-		addr_family = AF_INET6;
-	else
-		g_return_if_reached ();
+	gboolean is_routing;
+	const char *domain;
 
+	addr_family = nm_ip_config_get_addr_family (config);
 	addr_size = nm_utils_addr_family_to_size (addr_family);
 
-	n =   addr_family == AF_INET
-	    ? nm_ip4_config_get_num_nameservers (config)
-	    : nm_ip6_config_get_num_nameservers (config);
+	n = nm_ip_config_get_num_nameservers (config);
 	for (i = 0 ; i < n; i++) {
-		in_addr_t ns4;
-		gconstpointer ns;
-
-		if (addr_family == AF_INET) {
-			ns4 = nm_ip4_config_get_nameserver (config, i);
-			ns = &ns4;
-		} else
-			ns = nm_ip6_config_get_nameserver (config, i);
-
 		g_variant_builder_open (dns, G_VARIANT_TYPE ("(iay)"));
 		g_variant_builder_add (dns, "i", addr_family);
 		g_variant_builder_add_value (dns,
 		                             g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-		                                                        ns,
+		                                                        nm_ip_config_get_nameserver (config, i),
 		                                                        addr_size,
 		                                                        1));
 		g_variant_builder_close (dns);
 	}
 
-	/* If this link is never the default (e.g. only used for resources on this
-	 * network) add a routing domain. */
-	route_only =   addr_family == AF_INET
-	             ? !nm_ip4_config_best_default_route_get (config)
-	             : !nm_ip6_config_best_default_route_get (config);
-
-	n =   addr_family == AF_INET
-	    ? nm_ip4_config_get_num_searches (config)
-	    : nm_ip6_config_get_num_searches (config);
+	n = nm_ip_config_get_num_searches (config);
 	if (n  > 0) {
 		for (i = 0; i < n; i++) {
+			domain = nm_utils_parse_dns_domain (nm_ip_config_get_search (config, i),
+			                                    &is_routing);
 			g_variant_builder_add (domains, "(sb)",
-			                       addr_family == AF_INET
-			                         ? nm_ip4_config_get_search (config, i)
-			                         : nm_ip6_config_get_search (config, i),
-			                       route_only);
+			                       domain,
+			                       is_routing);
 		}
 	} else {
-		n =   addr_family == AF_INET
-		    ? nm_ip4_config_get_num_domains (config)
-		    : nm_ip6_config_get_num_domains (config);
+		n = nm_ip_config_get_num_domains (config);
 		for (i = 0; i < n; i++) {
+			domain = nm_utils_parse_dns_domain (nm_ip_config_get_domain (config, i),
+			                                    &is_routing);
 			g_variant_builder_add (domains, "(sb)",
-			                       addr_family == AF_INET
-			                         ? nm_ip4_config_get_domain (config, i)
-			                         : nm_ip6_config_get_domain (config, i),
-			                       route_only);
+			                       domain,
+			                       is_routing);
 		}
 	}
 }
@@ -213,13 +187,13 @@ static void
 free_pending_updates (NMDnsSystemdResolved *self)
 {
 	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
-	GVariant *v;
+	RequestItem *request_item, *request_item_safe;
 
-	while ((v = g_queue_pop_head (&priv->dns_updates)) != NULL)
-		g_variant_unref (v);
-
-	while ((v = g_queue_pop_head (&priv->domain_updates)) != NULL)
-		g_variant_unref (v);
+	c_list_for_each_entry_safe (request_item,
+	                            request_item_safe,
+	                            &priv->request_queue_lst_head,
+	                            request_queue_lst)
+		_request_item_free (request_item);
 }
 
 static void
@@ -227,7 +201,9 @@ prepare_one_interface (NMDnsSystemdResolved *self, InterfaceConfig *ic)
 {
 	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
 	GVariantBuilder dns, domains;
-	GList *l;
+	NMCListElem *elem;
+	NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+	const char *mdns_arg = NULL;
 
 	g_variant_builder_init (&dns, G_VARIANT_TYPE ("(ia(iay))"));
 	g_variant_builder_add (&dns, "i", ic->ifindex);
@@ -237,23 +213,50 @@ prepare_one_interface (NMDnsSystemdResolved *self, InterfaceConfig *ic)
 	g_variant_builder_add (&domains, "i", ic->ifindex);
 	g_variant_builder_open (&domains, G_VARIANT_TYPE ("a(sb)"));
 
-	for (l = ic->configs; l; l = l->next)
-		update_add_ip_config (self, &dns, &domains, l->data);
+	c_list_for_each_entry (elem, &ic->configs_lst_head, lst) {
+		NMIPConfig *ip_config = elem->data;
+
+		update_add_ip_config (self, &dns, &domains, ip_config);
+
+		if (NM_IS_IP4_CONFIG (ip_config))
+			mdns = NM_MAX (mdns, nm_ip4_config_mdns_get (NM_IP4_CONFIG (ip_config)));
+	}
 
 	g_variant_builder_close (&dns);
 	g_variant_builder_close (&domains);
 
-	g_queue_push_tail (&priv->dns_updates,
-	                   g_variant_ref_sink (g_variant_builder_end (&dns)));
-	g_queue_push_tail (&priv->domain_updates,
-	                   g_variant_ref_sink (g_variant_builder_end (&domains)));
+	switch (mdns) {
+	case NM_SETTING_CONNECTION_MDNS_NO:
+		mdns_arg = "no";
+		break;
+	case NM_SETTING_CONNECTION_MDNS_RESOLVE:
+		mdns_arg = "resolve";
+		break;
+	case NM_SETTING_CONNECTION_MDNS_YES:
+		mdns_arg = "yes";
+		break;
+	case NM_SETTING_CONNECTION_MDNS_DEFAULT:
+		mdns_arg = "";
+		break;
+	}
+	nm_assert (mdns_arg);
+
+	_request_item_append (&priv->request_queue_lst_head,
+	                      "SetLinkDNS",
+	                      g_variant_builder_end (&dns));
+	_request_item_append (&priv->request_queue_lst_head,
+	                      "SetLinkDomains",
+	                      g_variant_builder_end (&domains));
+	_request_item_append (&priv->request_queue_lst_head,
+	                      "SetLinkMulticastDNS",
+	                      g_variant_new ("(is)", ic->ifindex, mdns_arg ?: ""));
 }
 
 static void
 send_updates (NMDnsSystemdResolved *self)
 {
 	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
-	GVariant *v;
+	RequestItem *request_item, *request_item_safe;
 
 	nm_clear_g_cancellable (&priv->update_cancellable);
 
@@ -262,55 +265,81 @@ send_updates (NMDnsSystemdResolved *self)
 
 	priv->update_cancellable = g_cancellable_new ();
 
-	while ((v = g_queue_pop_head (&priv->dns_updates)) != NULL) {
-		g_dbus_proxy_call (priv->resolve, "SetLinkDNS", v,
+	c_list_for_each_entry_safe (request_item,
+	                            request_item_safe,
+	                            &priv->request_queue_lst_head,
+	                            request_queue_lst) {
+		g_dbus_proxy_call (priv->resolve,
+		                   request_item->operation,
+		                   request_item->argument,
 		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1, priv->update_cancellable, call_done, self);
-		g_variant_unref (v);
-	}
-
-	while ((v = g_queue_pop_head (&priv->domain_updates)) != NULL) {
-		g_dbus_proxy_call (priv->resolve, "SetLinkDomains", v,
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1, priv->update_cancellable, call_done, self);
-		g_variant_unref (v);
+		                   -1,
+		                   priv->update_cancellable,
+		                   call_done,
+		                   self);
+		_request_item_free (request_item);
 	}
 }
 
 static gboolean
 update (NMDnsPlugin *plugin,
-        const GPtrArray *configs,
         const NMGlobalDnsConfig *global_config,
+        const CList *ip_config_lst_head,
         const char *hostname)
 {
 	NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED (plugin);
-	GArray *interfaces = g_array_new (TRUE, TRUE, sizeof (InterfaceConfig));
+	gs_unref_hashtable GHashTable *interfaces = NULL;
+	gs_free gpointer *interfaces_keys = NULL;
+	guint interfaces_len;
 	guint i;
 	int prio, first_prio = 0;
+	NMDnsIPConfigData *ip_data;
+	gboolean is_first = TRUE;
+
+	interfaces = g_hash_table_new_full (nm_direct_hash, NULL,
+	                                    NULL, (GDestroyNotify) _interface_config_free);
 
-	for (i = 0; i < configs->len; i++) {
-		const NMDnsIPConfigData *data = configs->pdata[i];
+	c_list_for_each_entry (ip_data, ip_config_lst_head, ip_config_lst) {
 		gboolean skip = FALSE;
+		InterfaceConfig *ic = NULL;
+		int ifindex;
 
-		prio = nm_ip_config_get_dns_priority (data->config);
-		if (i == 0)
+		prio = nm_ip_config_get_dns_priority (ip_data->ip_config);
+		if (is_first) {
+			is_first = FALSE;
 			first_prio = prio;
-		else if (first_prio < 0 && first_prio != prio)
+		} else if (first_prio < 0 && first_prio != prio)
 			skip = TRUE;
-		add_interface_configuration (self, interfaces, data, skip);
+
+		ifindex = ip_data->data->ifindex;
+		nm_assert (ifindex == nm_ip_config_get_ifindex (ip_data->ip_config));
+
+		ic = g_hash_table_lookup (interfaces, GINT_TO_POINTER (ifindex));
+		if (!ic) {
+			ic = g_slice_new (InterfaceConfig);
+			ic->ifindex = ifindex;
+			c_list_init (&ic->configs_lst_head);
+			g_hash_table_insert (interfaces, GINT_TO_POINTER (ifindex), ic);
+		}
+
+		if (!skip) {
+			c_list_link_tail (&ic->configs_lst_head,
+			                  &nm_c_list_elem_new_stale (ip_data->ip_config)->lst);
+		}
 	}
 
 	free_pending_updates (self);
 
-	for (i = 0; i < interfaces->len; i++) {
-		InterfaceConfig *ic = &g_array_index (interfaces, InterfaceConfig, i);
+	interfaces_keys = nm_utils_hash_keys_to_array (interfaces,
+	                                               nm_cmp_int2ptr_p_with_data,
+	                                               NULL,
+	                                               &interfaces_len);
+	for (i = 0; i < interfaces_len; i++) {
+		InterfaceConfig *ic = g_hash_table_lookup (interfaces, GINT_TO_POINTER (interfaces_keys[i]));
 
 		prepare_one_interface (self, ic);
-		g_list_free (ic->configs);
 	}
 
-	g_array_free (interfaces, TRUE);
-
 	send_updates (self);
 
 	return TRUE;
@@ -363,16 +392,15 @@ static void
 nm_dns_systemd_resolved_init (NMDnsSystemdResolved *self)
 {
 	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
-	NMBusManager *dbus_mgr;
+	NMDBusManager *dbus_mgr;
 	GDBusConnection *connection;
 
-	g_queue_init (&priv->dns_updates);
-	g_queue_init (&priv->domain_updates);
+	c_list_init (&priv->request_queue_lst_head);
 
-	dbus_mgr = nm_bus_manager_get ();
+	dbus_mgr = nm_dbus_manager_get ();
 	g_return_if_fail (dbus_mgr);
 
-	connection = nm_bus_manager_get_connection (dbus_mgr);
+	connection = nm_dbus_manager_get_connection (dbus_mgr);
 	g_return_if_fail (connection);
 
 	priv->init_cancellable = g_cancellable_new ();
@@ -404,6 +432,7 @@ dispose (GObject *object)
 	g_clear_object (&priv->resolve);
 	nm_clear_g_cancellable (&priv->init_cancellable);
 	nm_clear_g_cancellable (&priv->update_cancellable);
+	nm_clear_g_cancellable (&priv->mdns_cancellable);
 
 	G_OBJECT_CLASS (nm_dns_systemd_resolved_parent_class)->dispose (object);
 }
diff --git a/src/dns/nm-dns-unbound.c b/src/dns/nm-dns-unbound.c
index 0b80055f..e06128aa 100644
--- a/src/dns/nm-dns-unbound.c
+++ b/src/dns/nm-dns-unbound.c
@@ -39,8 +39,8 @@ G_DEFINE_TYPE (NMDnsUnbound, nm_dns_unbound, NM_TYPE_DNS_PLUGIN)
 
 static gboolean
 update (NMDnsPlugin *plugin,
-        const GPtrArray *configs,
         const NMGlobalDnsConfig *global_config,
+        const CList *ip_config_lst_head,
         const char *hostname)
 {
 	char *argv[] = { DNSSEC_TRIGGER_SCRIPT, "--async", "--update", NULL };
diff --git a/src/dnsmasq/nm-dnsmasq-manager.c b/src/dnsmasq/nm-dnsmasq-manager.c
index 323ef781..041e75ea 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.c
+++ b/src/dnsmasq/nm-dnsmasq-manager.c
@@ -181,7 +181,7 @@ create_dm_cmd_line (const char *iface,
 		nm_cmd_line_add_string (cmd, "--log-queries");
 	}
 
-	/* dnsmasq may read from it's default config file location, which if that
+	/* dnsmasq may read from its default config file location, which if that
 	 * location is a valid config file, it will combine with the options here
 	 * and cause undesirable side-effects.  Like sending bogus IP addresses
 	 * as the gateway or whatever.  So tell dnsmasq not to use any config file
diff --git a/src/dnsmasq/tests/meson.build b/src/dnsmasq/tests/meson.build
new file mode 100644
index 00000000..40f42f6f
--- /dev/null
+++ b/src/dnsmasq/tests/meson.build
@@ -0,0 +1,14 @@
+test_unit = 'test-dnsmasq-utils'
+
+exe = executable(
+  test_unit,
+  test_unit + '.c',
+  dependencies: test_nm_dep,
+  c_args: '-DTESTDIR="@0@"'.format(meson.source_root())
+)
+
+test(
+  'dnsmasq/' + test_unit,
+  test_script,
+  args: test_args + [exe.full_path()]
+)
diff --git a/src/main.c b/src/main.c
index d59da052..bd8e26a5 100644
--- a/src/main.c
+++ b/src/main.c
@@ -39,7 +39,7 @@
 #include "NetworkManagerUtils.h"
 #include "nm-manager.h"
 #include "platform/nm-linux-platform.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
 #include "devices/nm-device.h"
 #include "dhcp/nm-dhcp-manager.h"
 #include "nm-config.h"
@@ -48,7 +48,7 @@
 #include "settings/nm-settings.h"
 #include "nm-auth-manager.h"
 #include "nm-core-internal.h"
-#include "nm-exported-object.h"
+#include "nm-dbus-object.h"
 #include "nm-connectivity.h"
 #include "dns/nm-dns-manager.h"
 #include "systemd/nm-sd.h"
@@ -93,8 +93,10 @@ static void
 _init_nm_debug (NMConfig *config)
 {
 	gs_free char *debug = NULL;
-	const guint D_RLIMIT_CORE = 1;
-	const guint D_FATAL_WARNINGS = 2;
+	enum {
+		D_RLIMIT_CORE =    (1 << 0),
+		D_FATAL_WARNINGS = (1 << 1),
+	};
 	GDebugKey keys[] = {
 		{ "RLIMIT_CORE", D_RLIMIT_CORE },
 		{ "fatal-warnings", D_FATAL_WARNINGS },
@@ -223,6 +225,7 @@ int
 main (int argc, char *argv[])
 {
 	gboolean success = FALSE;
+	NMManager *manager;
 	NMConfig *config;
 	GError *error = NULL;
 	gboolean wrote_pidfile = FALSE;
@@ -230,13 +233,11 @@ main (int argc, char *argv[])
 	NMConfigCmdLineOptions *config_cli;
 	guint sd_id = 0;
 
-	nm_g_type_init ();
-
 	/* Known to cause a possible deadlock upon GDBus initialization:
 	 * https://bugzilla.gnome.org/show_bug.cgi?id=674885 */
 	g_type_ensure (G_TYPE_SOCKET);
 	g_type_ensure (G_TYPE_DBUS_CONNECTION);
-	g_type_ensure (NM_TYPE_BUS_MANAGER);
+	g_type_ensure (NM_TYPE_DBUS_MANAGER);
 
 	_nm_utils_is_manager_process = TRUE;
 
@@ -394,27 +395,18 @@ main (int argc, char *argv[])
 	                                                         NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT,
 	                                                         NM_CONFIG_DEFAULT_MAIN_AUTH_POLKIT_BOOL));
 
-	nm_manager_setup ();
+	manager = nm_manager_setup ();
 
-	if (!nm_bus_manager_get_connection (nm_bus_manager_get ())) {
-		nm_log_warn (LOGD_CORE, "Failed to connect to D-Bus; only private bus is available");
-	} else {
-		/* Start our DBus service */
-		if (!nm_bus_manager_start_service (nm_bus_manager_get ())) {
-			nm_log_err (LOGD_CORE, "failed to start the dbus service.");
-			goto done;
-		}
-	}
-
-#if WITH_CONCHECK
-	NM_UTILS_KEEP_ALIVE (nm_manager_get (), nm_connectivity_get (), "NMManager-depends-on-NMConnectivity");
-#endif
+	if (!nm_dbus_manager_start (nm_dbus_manager_get (),
+	                            nm_manager_dbus_set_property_handle,
+	                            manager))
+		goto done;
 
 	nm_dispatcher_init ();
 
-	g_signal_connect (nm_manager_get (), NM_MANAGER_CONFIGURE_QUIT, G_CALLBACK (manager_configure_quit), config);
+	g_signal_connect (manager, NM_MANAGER_CONFIGURE_QUIT, G_CALLBACK (manager_configure_quit), config);
 
-	if (!nm_manager_start (nm_manager_get (), &error)) {
+	if (!nm_manager_start (manager, &error)) {
 		nm_log_err (LOGD_CORE, "failed to initialize: %s", error->message);
 		goto done;
 	}
@@ -448,11 +440,14 @@ done:
 	 * state here. We don't bother updating the state as devices
 	 * change during regular operation. If NM is killed with SIGKILL,
 	 * it misses to update the state. */
-	nm_manager_write_device_state (nm_manager_get ());
+	nm_manager_write_device_state (manager);
 
-	nm_exported_object_class_set_quitting ();
+	/* FIXME: we don't properly shut down on exit. That is a bug.
+	 * NMDBusObject have an assertion that they get unexported before disposing.
+	 * We need this workaround and disable the assertion during our leaky shutdown. */
+	nm_dbus_object_set_quitting ();
 
-	nm_manager_stop (nm_manager_get ());
+	nm_manager_stop (manager);
 
 	nm_config_state_set (config, TRUE, TRUE);
 
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 00000000..591c9c79
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,319 @@
+src_inc = include_directories('.')
+
+install_data(
+  'org.freedesktop.NetworkManager.conf',
+  install_dir: dbus_conf_dir
+)
+
+subdir('systemd')
+
+core_plugins = []
+
+nm_cflags = ['-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_DAEMON']
+
+nm_dep = declare_dependency(
+  include_directories: src_inc,
+  dependencies: nm_core_dep,
+  compile_args: nm_cflags
+)
+
+cflags = nm_cflags + [
+  '-DPREFIX="@0@"'.format(nm_prefix),
+  '-DBINDIR="@0@"'.format(nm_bindir),
+  '-DDATADIR="@0@"'.format(nm_datadir),
+  '-DLIBEXECDIR="@0@"'.format(nm_libexecdir),
+  '-DLOCALSTATEDIR="@0@"'.format(nm_localstatedir),
+  '-DRUNSTATEDIR="@0@"'.format(nm_runstatedir),
+  '-DSBINDIR="@0@"'.format(nm_sbindir),
+  '-DSYSCONFDIR="@0@"'.format(nm_sysconfdir),
+  '-DRUNDIR="@0@"'.format(nm_pkgrundir),
+  '-DNMCONFDIR="@0@"'.format(nm_pkgconfdir),
+  '-DNMLOCALEDIR="@0@"'.format(nm_localedir),
+  '-DNMPLUGINDIR="@0@"'.format(nm_pkglibdir),
+  '-DNMRUNDIR="@0@"'.format(nm_pkgrundir),
+  '-DNMSTATEDIR="@0@"'.format(nm_pkgstatedir),
+  '-DNMLIBDIR="@0@"'.format(nm_pkglibdir)
+]
+
+if enable_dhcpcanon
+  cflags += '-DDHCPCANON_PATH="@0@"'.format(dhcpcanon.path())
+endif
+
+if enable_dhclient
+  cflags += '-DDHCLIENT_PATH="@0@"'.format(dhclient.path())
+endif
+
+if enable_dhcpcd
+  cflags += '-DDHCPCD_PATH="@0@"'.format(dhcpcd.path())
+endif
+
+sources = files(
+  'dhcp/nm-dhcp-client.c',
+  'dhcp/nm-dhcp-manager.c',
+  'dhcp/nm-dhcp-systemd.c',
+  'dhcp/nm-dhcp-utils.c',
+  'ndisc/nm-lndp-ndisc.c',
+  'ndisc/nm-ndisc.c',
+  'platform/nm-netlink.c',
+  'platform/wifi/wifi-utils-nl80211.c',
+  'platform/wifi/wifi-utils.c',
+  'platform/nm-linux-platform.c',
+  'platform/nm-platform.c',
+  'platform/nm-platform-utils.c',
+  'platform/nmp-netns.c',
+  'platform/nmp-object.c',
+  'main-utils.c',
+  'NetworkManagerUtils.c',
+  'nm-core-utils.c',
+  'nm-dbus-object.c',
+  'nm-dbus-utils.c',
+  'nm-ip4-config.c',
+  'nm-ip6-config.c',
+  'nm-logging.c'
+)
+
+deps = [
+  libsystemd_dep,
+  libudev_dep,
+  nm_core_dep
+]
+
+if enable_wext
+  sources += files('platform/wifi/wifi-utils-wext.c')
+endif
+
+libnetwork_manager_base = static_library(
+  nm_name + 'Base',
+  sources: sources,
+  dependencies: deps,
+  c_args: cflags,
+  link_with: libnm_core
+)
+
+sources = files(
+  'devices/nm-acd-manager.c',
+  'devices/nm-device-bond.c',
+  'devices/nm-device-bridge.c',
+  'devices/nm-device.c',
+  'devices/nm-device-dummy.c',
+  'devices/nm-device-ethernet.c',
+  'devices/nm-device-ethernet-utils.c',
+  'devices/nm-device-factory.c',
+  'devices/nm-device-generic.c',
+  'devices/nm-device-infiniband.c',
+  'devices/nm-device-ip-tunnel.c',
+  'devices/nm-device-macsec.c',
+  'devices/nm-device-macvlan.c',
+  'devices/nm-device-ppp.c',
+  'devices/nm-device-tun.c',
+  'devices/nm-device-veth.c',
+  'devices/nm-device-vlan.c',
+  'devices/nm-device-vxlan.c',
+  'devices/nm-lldp-listener.c',
+  'dhcp/nm-dhcp-dhclient.c',
+  'dhcp/nm-dhcp-dhclient-utils.c',
+  'dhcp/nm-dhcp-dhcpcanon.c',
+  'dhcp/nm-dhcp-dhcpcd.c',
+  'dhcp/nm-dhcp-listener.c',
+  'dns/nm-dns-dnsmasq.c',
+  'dns/nm-dns-manager.c',
+  'dns/nm-dns-plugin.c',
+  'dns/nm-dns-systemd-resolved.c',
+  'dns/nm-dns-unbound.c',
+  '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-plugin.c',
+  'settings/plugins/keyfile/nms-keyfile-reader.c',
+  'settings/plugins/keyfile/nms-keyfile-utils.c',
+  'settings/plugins/keyfile/nms-keyfile-writer.c',
+  'settings/nm-agent-manager.c',
+  'settings/nm-secret-agent.c',
+  'settings/nm-settings.c',
+  'settings/nm-settings-connection.c',
+  'settings/nm-settings-plugin.c',
+  'supplicant/nm-supplicant-config.c',
+  'supplicant/nm-supplicant-interface.c',
+  'supplicant/nm-supplicant-manager.c',
+  'supplicant/nm-supplicant-settings-verify.c',
+  'vpn/nm-vpn-connection.c',
+  'vpn/nm-vpn-manager.c',
+  'nm-active-connection.c',
+  'nm-act-request.c',
+  'nm-audit-manager.c',
+  'nm-auth-manager.c',
+  'nm-auth-subject.c',
+  'nm-auth-utils.c',
+  'nm-dbus-manager.c',
+  'nm-checkpoint.c',
+  'nm-checkpoint-manager.c',
+  'nm-config.c',
+  'nm-config-data.c',
+  'nm-connectivity.c',
+  'nm-dcb.c',
+  'nm-dhcp4-config.c',
+  'nm-dhcp6-config.c',
+  'nm-dispatcher.c',
+  'nm-firewall-manager.c',
+  'nm-hostname-manager.c',
+  'nm-manager.c',
+  'nm-netns.c',
+  'nm-pacrunner-manager.c',
+  'nm-policy.c',
+  'nm-proxy-config.c',
+  'nm-rfkill-manager.c',
+  'nm-session-monitor.c',
+  'nm-sleep-monitor.c'
+)
+
+deps = [
+  dl_dep,
+  libndp_dep,
+  # FIXME: Some files use introspection/dbus* headers, so
+  #        this dependency might be needed
+  #libnmdbus_dep,
+  libudev_dep,
+  nm_core_dep,
+  shared_n_acd_dep
+]
+
+if enable_concheck
+  deps += libcurl_dep
+endif
+
+if enable_libaudit
+  deps += libaudit_dep
+endif
+
+if enable_libpsl
+  deps += libpsl_dep
+endif
+
+if enable_selinux
+  deps += selinux_dep
+endif
+
+if enable_session_tracking
+  deps += logind_dep
+endif
+
+libnetwork_manager = static_library(
+  nm_name,
+  sources: sources,
+  dependencies: deps,
+  c_args: cflags,
+  link_with: [libnetwork_manager_base, libsystemd_nm]
+)
+
+ldflags = ['-rdynamic']
+
+# FIXME: this doesn't work and it depends on libtool
+'''
+src/NetworkManager.ver: src/libNetworkManager.la $(core_plugins)
+  $(AM_V_GEN) NM="$(NM)" "$(srcdir)/tools/create-exports-NetworkManager.sh" --called-from-make "$(srcdir)"
+
+src_NetworkManager_LDFLAGS = \
+  -rdynamic \
+  -Wl,--version-script="src/NetworkManager.ver"
+
+nm = find_program('gcc-nm', 'nm')
+create_exports_networkmanager = join_paths(meson.source_root(), 'tools', 'create-exports-NetworkManager.sh')
+
+symbol_map_name = 'NetworkManager.ver'
+
+linker_script = custom_target(
+  symbol_map_name,
+  input: meson.source_root(),
+  output: symbol_map_name,
+  capture: true,
+  #command: ['NM=' + nm.path(), create_exports_networkmanager, '--called-from-make', '@INPUT@']
+  command: [create_exports_networkmanager, '--called-from-make', '@INPUT@']
+)
+
+ldflags += '-Wl,--version-script,@0@'.format(linker_script)
+'''
+
+network_manager = executable(
+  nm_name,
+  'main.c',
+  dependencies: deps,
+  c_args: cflags,
+  link_with: libnetwork_manager,
+  link_args: ldflags,
+  #FIXME
+  #link_depends: linker_script,
+  install: true,
+  install_dir: nm_sbindir
+)
+
+deps = [
+  dl_dep,
+  libndp_dep,
+  libudev_dep,
+  nm_core_dep
+]
+
+name = 'nm-iface-helper'
+
+executable(
+  name,
+  name + '.c',
+  dependencies: deps,
+  c_args: cflags,
+  link_with: [libnetwork_manager_base, libsystemd_nm],
+  link_args: ldflags_linker_script_binary,
+  link_depends: linker_script_binary,
+  install: true,
+  install_dir: nm_libexecdir
+)
+
+if enable_tests
+  sources = files(
+    'ndisc/nm-fake-ndisc.c',
+    'platform/tests/test-common.c',
+    'platform/nm-fake-platform.c'
+  )
+
+  deps = [
+    libudev_dep,
+    nm_core_dep
+  ]
+
+  test_cflags = ['-DNETWORKMANAGER_COMPILATION_TEST']
+  if require_root_tests
+    test_cflags += ['-DREQUIRE_ROOT_TESTS=1']
+  endif
+
+  platform = (host_machine.system().contains('linux') ? 'linux' : 'fake')
+  test_cflags_platform = '-DSETUP=nm_' + platform + '_platform_setup'
+
+  libnetwork_manager_test = static_library(
+    nm_name + 'Test',
+    sources: sources,
+    dependencies: deps,
+    c_args: cflags + test_cflags,
+    link_with: libnetwork_manager
+  )
+
+  test_nm_dep = declare_dependency(
+    dependencies: nm_dep,
+    compile_args: test_cflags,
+    link_with: libnetwork_manager_test
+  )
+
+  subdir('dnsmasq/tests')
+  subdir('ndisc/tests')
+  subdir('platform/tests')
+  subdir('supplicant/tests')
+  subdir('tests')
+endif
+
+subdir('dhcp')
+
+if enable_ppp
+  subdir('ppp')
+endif
+
+subdir('devices')
+subdir('settings/plugins')
diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/ndisc/nm-lndp-ndisc.c
index 70200ed3..c0a0cd40 100644
--- a/src/ndisc/nm-lndp-ndisc.c
+++ b/src/ndisc/nm-lndp-ndisc.c
@@ -173,10 +173,10 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 	 */
 	{
 		const NMNDiscGateway gateway = {
-		    .address = gateway_addr,
-		    .timestamp = now,
-		    .lifetime = ndp_msgra_router_lifetime (msgra),
-		    .preference = _route_preference_coerce (ndp_msgra_route_preference (msgra)),
+			.address = gateway_addr,
+			.timestamp = now,
+			.lifetime = ndp_msgra_router_lifetime (msgra),
+			.preference = _route_preference_coerce (ndp_msgra_route_preference (msgra)),
 		};
 
 		if (nm_ndisc_add_gateway (ndisc, &gateway))
@@ -195,12 +195,16 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 			continue;
 		nm_utils_ip6_address_clear_host_address (&r_network, ndp_msg_opt_prefix (msg, offset), r_plen);
 
+		if (   IN6_IS_ADDR_UNSPECIFIED (&r_network)
+		    || IN6_IS_ADDR_LINKLOCAL (&r_network))
+			continue;
+
 		if (ndp_msg_opt_prefix_flag_on_link (msg, offset)) {
-			NMNDiscRoute route = {
-			    .network = r_network,
-			    .plen = r_plen,
-			    .timestamp = now,
-			    .lifetime = ndp_msg_opt_prefix_valid_time (msg, offset),
+			const NMNDiscRoute route = {
+				.network = r_network,
+				.plen = r_plen,
+				.timestamp = now,
+				.lifetime = ndp_msg_opt_prefix_valid_time (msg, offset),
 			};
 
 			if (nm_ndisc_add_route (ndisc, &route))
@@ -211,10 +215,10 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 		if (   r_plen == 64
 		    && ndp_msg_opt_prefix_flag_auto_addr_conf (msg, offset)) {
 			NMNDiscAddress address = {
-			    .address = r_network,
-			    .timestamp = now,
-			    .lifetime = ndp_msg_opt_prefix_valid_time (msg, offset),
-			    .preferred = ndp_msg_opt_prefix_preferred_time (msg, offset),
+				.address = r_network,
+				.timestamp = now,
+				.lifetime = ndp_msg_opt_prefix_valid_time (msg, offset),
+				.preferred = ndp_msg_opt_prefix_preferred_time (msg, offset),
 			};
 
 			if (address.preferred > address.lifetime)
@@ -225,11 +229,11 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 	}
 	ndp_msg_opt_for_each_offset(offset, msg, NDP_MSG_OPT_ROUTE) {
 		NMNDiscRoute route = {
-		    .gateway = gateway_addr,
-		    .plen = ndp_msg_opt_route_prefix_len (msg, offset),
-		    .timestamp = now,
-		    .lifetime = ndp_msg_opt_route_lifetime (msg, offset),
-		    .preference = _route_preference_coerce (ndp_msg_opt_route_preference (msg, offset)),
+			.gateway = gateway_addr,
+			.plen = ndp_msg_opt_route_prefix_len (msg, offset),
+			.timestamp = now,
+			.lifetime = ndp_msg_opt_route_lifetime (msg, offset),
+			.preference = _route_preference_coerce (ndp_msg_opt_route_preference (msg, offset)),
 		};
 
 		if (route.plen == 0 || route.plen > 128)
@@ -248,9 +252,9 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 
 		ndp_msg_opt_rdnss_for_each_addr (addr, addr_index, msg, offset) {
 			NMNDiscDNSServer dns_server = {
-			    .address = *addr,
-			    .timestamp = now,
-			    .lifetime = ndp_msg_opt_rdnss_lifetime (msg, offset),
+				.address = *addr,
+				.timestamp = now,
+				.lifetime = ndp_msg_opt_rdnss_lifetime (msg, offset),
 			};
 
 			/* Pad the lifetime somewhat to give a bit of slack in cases
@@ -270,9 +274,9 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 
 		ndp_msg_opt_dnssl_for_each_domain (domain, domain_index, msg, offset) {
 			NMNDiscDNSDomain dns_domain = {
-			    .domain = domain,
-			    .timestamp = now,
-			    .lifetime = ndp_msg_opt_rdnss_lifetime (msg, offset),
+				.domain = domain,
+				.timestamp = now,
+				.lifetime = ndp_msg_opt_rdnss_lifetime (msg, offset),
 			};
 
 			/* Pad the lifetime somewhat to give a bit of slack in cases
diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c
index 6b44a96c..ba61cb11 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/ndisc/nm-ndisc.c
@@ -89,7 +89,7 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 );
 
 enum {
-	CONFIG_CHANGED,
+	CONFIG_RECEIVED,
 	RA_TIMEOUT,
 	LAST_SIGNAL
 };
@@ -235,7 +235,7 @@ static void
 _emit_config_change (NMNDisc *self, NMNDiscConfigMap changed)
 {
 	_config_changed_log (self, changed);
-	g_signal_emit (self, signals[CONFIG_CHANGED], 0,
+	g_signal_emit (self, signals[CONFIG_RECEIVED], 0,
 	               _data_complete (&NM_NDISC_GET_PRIVATE (self)->rdata),
 	               (guint) changed);
 }
@@ -349,6 +349,11 @@ nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new)
 	NMNDiscDataInternal *rdata = &priv->rdata;
 	guint i;
 
+	nm_assert (new);
+	nm_assert (new->timestamp > 0 && new->timestamp < G_MAXINT32);
+	nm_assert (!IN6_IS_ADDR_UNSPECIFIED (&new->address));
+	nm_assert (!IN6_IS_ADDR_LINKLOCAL (&new->address));
+
 	for (i = 0; i < rdata->addresses->len; i++) {
 		NMNDiscAddress *item = &g_array_index (rdata->addresses, NMNDiscAddress, i);
 
@@ -792,7 +797,7 @@ nm_ndisc_start (NMNDisc *ndisc)
 }
 
 void
-nm_ndisc_dad_failed (NMNDisc *ndisc, struct in6_addr *address)
+nm_ndisc_dad_failed (NMNDisc *ndisc, const struct in6_addr *address)
 {
 	NMNDiscDataInternal *rdata;
 	guint i;
@@ -887,6 +892,23 @@ get_expiry_time (guint32 timestamp, guint32 lifetime)
 		                   : (_item->lifetime) / 2); \
 	})
 
+static const char *
+_get_exp (char *buf, gsize buf_size, gint64 now_ns, gint32 expiry_time)
+{
+	int l;
+
+	if (expiry_time == G_MAXINT32)
+		return "permanent";
+	l = g_snprintf (buf, buf_size,
+	                "%.4f",
+	                ((double) ((expiry_time * NM_UTILS_NS_PER_SECOND) - now_ns)) / ((double) NM_UTILS_NS_PER_SECOND));
+	nm_assert (l < buf_size);
+	return buf;
+}
+
+#define get_exp(buf, now_ns, item) \
+	_get_exp ((buf), G_N_ELEMENTS (buf), (now_ns), (get_expiry (item)))
+
 static void
 _config_changed_log (NMNDisc *ndisc, NMNDiscConfigMap changed)
 {
@@ -896,10 +918,14 @@ _config_changed_log (NMNDisc *ndisc, NMNDiscConfigMap changed)
 	char changedstr[CONFIG_MAP_MAX_STR];
 	char addrstr[INET6_ADDRSTRLEN];
 	char str_pref[35];
+	char str_exp[100];
+	gint64 now_ns;
 
 	if (!_LOGD_ENABLED ())
 		return;
 
+	now_ns = nm_utils_get_monotonic_timestamp_ns ();
+
 	priv = NM_NDISC_GET_PRIVATE (ndisc);
 	rdata = &priv->rdata;
 
@@ -910,35 +936,38 @@ _config_changed_log (NMNDisc *ndisc, NMNDiscConfigMap changed)
 		NMNDiscGateway *gateway = &g_array_index (rdata->gateways, NMNDiscGateway, i);
 
 		inet_ntop (AF_INET6, &gateway->address, addrstr, sizeof (addrstr));
-		_LOGD ("  gateway %s pref %s exp %d", addrstr,
+		_LOGD ("  gateway %s pref %s exp %s", addrstr,
 		       nm_icmpv6_router_pref_to_string (gateway->preference, str_pref, sizeof (str_pref)),
-		       get_expiry (gateway));
+		       get_exp (str_exp, now_ns, gateway));
 	}
 	for (i = 0; i < rdata->addresses->len; i++) {
-		NMNDiscAddress *address = &g_array_index (rdata->addresses, NMNDiscAddress, i);
+		const NMNDiscAddress *address = &g_array_index (rdata->addresses, NMNDiscAddress, i);
 
 		inet_ntop (AF_INET6, &address->address, addrstr, sizeof (addrstr));
-		_LOGD ("  address %s exp %d", addrstr, get_expiry (address));
+		_LOGD ("  address %s exp %s", addrstr,
+		       get_exp (str_exp, now_ns, address));
 	}
 	for (i = 0; i < rdata->routes->len; i++) {
 		NMNDiscRoute *route = &g_array_index (rdata->routes, NMNDiscRoute, i);
 
 		inet_ntop (AF_INET6, &route->network, addrstr, sizeof (addrstr));
-		_LOGD ("  route %s/%u via %s pref %s exp %d", addrstr, (guint) route->plen,
+		_LOGD ("  route %s/%u via %s pref %s exp %s", addrstr, (guint) route->plen,
 		       nm_utils_inet6_ntop (&route->gateway, NULL),
 		       nm_icmpv6_router_pref_to_string (route->preference, str_pref, sizeof (str_pref)),
-		       get_expiry (route));
+		       get_exp (str_exp, now_ns, route));
 	}
 	for (i = 0; i < rdata->dns_servers->len; i++) {
 		NMNDiscDNSServer *dns_server = &g_array_index (rdata->dns_servers, NMNDiscDNSServer, i);
 
 		inet_ntop (AF_INET6, &dns_server->address, addrstr, sizeof (addrstr));
-		_LOGD ("  dns_server %s exp %d", addrstr, get_expiry (dns_server));
+		_LOGD ("  dns_server %s exp %s", addrstr,
+		       get_exp (str_exp, now_ns, dns_server));
 	}
 	for (i = 0; i < rdata->dns_domains->len; i++) {
 		NMNDiscDNSDomain *dns_domain = &g_array_index (rdata->dns_domains, NMNDiscDNSDomain, i);
 
-		_LOGD ("  dns_domain %s exp %d", dns_domain->domain, get_expiry (dns_domain));
+		_LOGD ("  dns_domain %s exp %s", dns_domain->domain,
+		       get_exp (str_exp, now_ns, dns_domain));
 	}
 }
 
@@ -979,7 +1008,7 @@ clean_addresses (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *
 	rdata = &NM_NDISC_GET_PRIVATE (ndisc)->rdata;
 
 	for (i = 0; i < rdata->addresses->len; ) {
-		NMNDiscAddress *item = &g_array_index (rdata->addresses, NMNDiscAddress, i);
+		const NMNDiscAddress *item = &g_array_index (rdata->addresses, NMNDiscAddress, i);
 
 		if (item->lifetime != NM_NDISC_INFINITY) {
 			gint32 expiry = get_expiry (item);
@@ -1354,7 +1383,7 @@ nm_ndisc_class_init (NMNDiscClass *klass)
 	                      G_PARAM_STATIC_STRINGS);
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-	signals[CONFIG_CHANGED] =
+	signals[CONFIG_RECEIVED] =
 	    g_signal_new (NM_NDISC_CONFIG_RECEIVED,
 	                  G_OBJECT_CLASS_TYPE (klass),
 	                  G_SIGNAL_RUN_FIRST,
diff --git a/src/ndisc/nm-ndisc.h b/src/ndisc/nm-ndisc.h
index b66c2289..9a8a27d7 100644
--- a/src/ndisc/nm-ndisc.h
+++ b/src/ndisc/nm-ndisc.h
@@ -27,6 +27,9 @@
 #include "nm-setting-ip6-config.h"
 #include "NetworkManagerUtils.h"
 
+#include "platform/nm-platform.h"
+#include "platform/nmp-object.h"
+
 #define NM_TYPE_NDISC            (nm_ndisc_get_type ())
 #define NM_NDISC(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_NDISC, NMNDisc))
 #define NM_NDISC_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_NDISC, NMNDiscClass))
@@ -174,7 +177,7 @@ NMNDiscNodeType nm_ndisc_get_node_type (NMNDisc *self);
 
 gboolean nm_ndisc_set_iid (NMNDisc *ndisc, const NMUtilsIPv6IfaceId iid);
 void nm_ndisc_start (NMNDisc *ndisc);
-void nm_ndisc_dad_failed (NMNDisc *ndisc, struct in6_addr *address);
+void nm_ndisc_dad_failed (NMNDisc *ndisc, const struct in6_addr *address);
 void nm_ndisc_set_config (NMNDisc *ndisc,
                           const GArray *addresses,
                           const GArray *dns_servers,
@@ -184,4 +187,32 @@ NMPlatform *nm_ndisc_get_platform (NMNDisc *self);
 NMPNetns *nm_ndisc_netns_get (NMNDisc *self);
 gboolean nm_ndisc_netns_push (NMNDisc *self, NMPNetns **netns);
 
+static inline gboolean
+nm_ndisc_dad_addr_is_fail_candidate_event (NMPlatformSignalChangeType change_type,
+                                           const NMPlatformIP6Address *addr)
+{
+	return    !NM_FLAGS_HAS (addr->n_ifa_flags, IFA_F_TEMPORARY)
+	       && (   (change_type == NM_PLATFORM_SIGNAL_CHANGED && addr->n_ifa_flags & IFA_F_DADFAILED)
+	           || (change_type == NM_PLATFORM_SIGNAL_REMOVED && addr->n_ifa_flags & IFA_F_TENTATIVE));
+}
+
+static inline gboolean
+nm_ndisc_dad_addr_is_fail_candidate (NMPlatform *platform,
+                                     const NMPObject *obj)
+{
+	const NMPlatformIP6Address *addr;
+
+	addr = NMP_OBJECT_CAST_IP6_ADDRESS (nm_platform_lookup_obj (platform,
+	                                                            NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+	                                                            obj));
+	if (   addr
+	    && (   NM_FLAGS_HAS (addr->n_ifa_flags, IFA_F_TEMPORARY)
+	        || !NM_FLAGS_HAS (addr->n_ifa_flags, IFA_F_DADFAILED))) {
+		/* the address still/again exists and is not in DADFAILED state. Skip it. */
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 #endif /* __NETWORKMANAGER_NDISC_H__ */
diff --git a/src/ndisc/tests/meson.build b/src/ndisc/tests/meson.build
new file mode 100644
index 00000000..2f479c2d
--- /dev/null
+++ b/src/ndisc/tests/meson.build
@@ -0,0 +1,23 @@
+test_unit = 'test-ndisc-fake'
+
+exe = executable(
+  test_unit,
+  test_unit + '.c',
+  dependencies: test_nm_dep,
+  c_args: test_cflags_platform
+)
+
+test(
+  'ndisc/' + test_unit,
+  test_script,
+  args: test_args + [exe.full_path()]
+)
+
+test = 'test-ndisc-linux'
+
+exe = executable(
+  test,
+  test + '.c',
+  dependencies: test_nm_dep,
+  c_args: test_cflags_platform
+)
diff --git a/src/nm-act-request.c b/src/nm-act-request.c
index 87070794..0ac20b85 100644
--- a/src/nm-act-request.c
+++ b/src/nm-act-request.c
@@ -28,7 +28,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
-#include "nm-utils/c-list.h"
+#include "c-list/src/c-list.h"
 
 #include "nm-setting-wireless-security.h"
 #include "nm-setting-8021x.h"
@@ -242,8 +242,6 @@ _do_cancel_secrets (NMActRequest *self, NMActRequestGetSecretsCallId *call_id, g
 void
 nm_act_request_cancel_secrets (NMActRequest *self, NMActRequestGetSecretsCallId *call_id)
 {
-	NMActRequestPrivate *priv;
-
 	g_return_if_fail (call_id);
 
 	if (self) {
@@ -255,8 +253,6 @@ nm_act_request_cancel_secrets (NMActRequest *self, NMActRequestGetSecretsCallId
 		self = call_id->self;
 	}
 
-	priv = NM_ACT_REQUEST_GET_PRIVATE (self);
-
 	if (!c_list_is_linked (&call_id->call_ids_lst))
 		g_return_if_reached ();
 
@@ -518,7 +514,7 @@ get_property (GObject *object, guint prop_id,
 	    || !NM_IN_SET (nm_active_connection_get_state (active),
 	                   NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
 	                   NM_ACTIVE_CONNECTION_STATE_DEACTIVATING)) {
-		g_value_set_string (value, "/");
+		g_value_set_string (value, NULL);
 		return;
 	}
 
@@ -541,7 +537,8 @@ nm_act_request_init (NMActRequest *req)
  * @specific_object: the object path of the specific object (ie, WiFi access point,
  *    etc) that will be used to activate @connection and @device
  * @subject: the #NMAuthSubject representing the requestor of the activation
- * @activation_type: the #NMActivationType.
+ * @activation_type: the #NMActivationType
+ * @activation_reason: the reason for activation
  * @device: the device/interface to configure according to @connection
  *
  * Creates a new device-based activation request. If an applied connection is
@@ -555,6 +552,7 @@ nm_act_request_new (NMSettingsConnection *settings_connection,
                     const char *specific_object,
                     NMAuthSubject *subject,
                     NMActivationType activation_type,
+                    NMActivationReason activation_reason,
                     NMDevice *device)
 {
 	g_return_val_if_fail (!settings_connection || NM_IS_SETTINGS_CONNECTION (settings_connection), NULL);
@@ -568,6 +566,7 @@ nm_act_request_new (NMSettingsConnection *settings_connection,
 	                                      NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT, specific_object,
 	                                      NM_ACTIVE_CONNECTION_INT_SUBJECT, subject,
 	                                      NM_ACTIVE_CONNECTION_INT_ACTIVATION_TYPE, (int) activation_type,
+	                                      NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON, (int) activation_reason,
 	                                      NULL);
 }
 
diff --git a/src/nm-act-request.h b/src/nm-act-request.h
index 5d1c9dfd..a8f09271 100644
--- a/src/nm-act-request.h
+++ b/src/nm-act-request.h
@@ -41,6 +41,7 @@ NMActRequest *nm_act_request_new          (NMSettingsConnection *settings_connec
                                            const char *specific_object,
                                            NMAuthSubject *subject,
                                            NMActivationType activation_type,
+                                           NMActivationReason activation_reason,
                                            NMDevice *device);
 
 NMSettingsConnection *nm_act_request_get_settings_connection (NMActRequest *req);
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index 65f57565..c6d6645d 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -28,14 +28,15 @@
 #include "settings/nm-settings-connection.h"
 #include "nm-simple-connection.h"
 #include "nm-auth-utils.h"
+#include "nm-auth-manager.h"
 #include "nm-auth-subject.h"
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Connection.Active.h"
+#define AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED  ((NMAuthManagerCallId *) GINT_TO_POINTER (1))
 
 typedef struct _NMActiveConnectionPrivate {
-	NMSettingsConnection *settings_connection;
+	NMDBusTrackObjPath settings_connection;
 	NMConnection *applied_connection;
 	char *specific_object;
 	NMDevice *device;
@@ -54,17 +55,22 @@ typedef struct _NMActiveConnectionPrivate {
 	bool master_ready:1;
 
 	NMActivationType activation_type:3;
+	NMActivationReason activation_reason:3;
 
 	NMAuthSubject *subject;
 	NMActiveConnection *master;
 
 	NMActiveConnection *parent;
 
-	NMAuthChain *chain;
-	const char *wifi_shared_permission;
-	NMActiveConnectionAuthResultFunc result_func;
-	gpointer user_data1;
-	gpointer user_data2;
+	struct {
+		NMAuthManagerCallId *call_id_network_control;
+		NMAuthManagerCallId *call_id_wifi_shared_permission;
+
+		NMActiveConnectionAuthResultFunc result_func;
+		gpointer user_data1;
+		gpointer user_data2;
+	} auth;
+
 } NMActiveConnectionPrivate;
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMActiveConnection,
@@ -92,6 +98,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMActiveConnection,
 	PROP_INT_MASTER,
 	PROP_INT_MASTER_READY,
 	PROP_INT_ACTIVATION_TYPE,
+	PROP_INT_ACTIVATION_REASON,
 );
 
 enum {
@@ -103,17 +110,19 @@ enum {
 };
 static guint signals[LAST_SIGNAL] = { 0 };
 
-G_DEFINE_ABSTRACT_TYPE (NMActiveConnection, nm_active_connection, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_ABSTRACT_TYPE (NMActiveConnection, nm_active_connection, NM_TYPE_DBUS_OBJECT)
 
 #define NM_ACTIVE_CONNECTION_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMActiveConnection, NM_IS_ACTIVE_CONNECTION)
 
 /*****************************************************************************/
 
+static const NMDBusInterfaceInfoExtended interface_info_active_connection;
+static const GDBusSignalInfo signal_info_state_changed;
+
 static void check_master_ready (NMActiveConnection *self);
 static void _device_cleanup (NMActiveConnection *self);
-static void _settings_connection_notify_flags (NMSettingsConnection *settings_connection,
-                                               GParamSpec *param,
-                                               NMActiveConnection *self);
+static void _settings_connection_flags_changed (NMSettingsConnection *settings_connection,
+                                                NMActiveConnection *self);
 static void _set_activation_type_managed (NMActiveConnection *self);
 
 /*****************************************************************************/
@@ -181,40 +190,24 @@ _settings_connection_updated (NMSettingsConnection *connection,
 }
 
 static void
-_settings_connection_removed (NMSettingsConnection *connection,
-                              gpointer user_data)
-{
-	NMActiveConnection *self = user_data;
-
-	/* Our settings connection is about to drop off. The next active connection
-	 * cleanup is going to tear us down (at least until we grow the capability to
-	 * re-link; in that case we'd just clean the references to the old connection here).
-	 * Let's remove ourselves from the bus so that we're not exposed with a dangling
-	 * reference to the setting connection once it's gone. */
-	if (nm_exported_object_is_exported (NM_EXPORTED_OBJECT (self)))
-		nm_exported_object_unexport (NM_EXPORTED_OBJECT (self));
-}
-
-static void
 _set_settings_connection (NMActiveConnection *self, NMSettingsConnection *connection)
 {
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 
-	if (priv->settings_connection == connection)
+	if (priv->settings_connection.obj == connection)
 		return;
-	if (priv->settings_connection) {
-		g_signal_handlers_disconnect_by_func (priv->settings_connection, _settings_connection_updated, self);
-		g_signal_handlers_disconnect_by_func (priv->settings_connection, _settings_connection_removed, self);
-		g_signal_handlers_disconnect_by_func (priv->settings_connection, _settings_connection_notify_flags, self);
-		g_clear_object (&priv->settings_connection);
+
+	if (priv->settings_connection.obj) {
+		g_signal_handlers_disconnect_by_func (priv->settings_connection.obj, _settings_connection_updated, self);
+		g_signal_handlers_disconnect_by_func (priv->settings_connection.obj, _settings_connection_flags_changed, self);
 	}
 	if (connection) {
-		priv->settings_connection = g_object_ref (connection);
 		g_signal_connect (connection, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL, (GCallback) _settings_connection_updated, self);
-		g_signal_connect (connection, NM_SETTINGS_CONNECTION_REMOVED, (GCallback) _settings_connection_removed, self);
 		if (nm_active_connection_get_activation_type (self) == NM_ACTIVATION_TYPE_EXTERNAL)
-			g_signal_connect (connection, "notify::"NM_SETTINGS_CONNECTION_FLAGS, (GCallback) _settings_connection_notify_flags, self);
+			g_signal_connect (connection, NM_SETTINGS_CONNECTION_FLAGS_CHANGED, (GCallback) _settings_connection_flags_changed, self);
 	}
+
+	nm_dbus_track_obj_path_set (&priv->settings_connection, connection, TRUE);
 }
 
 NMActiveConnectionState
@@ -223,6 +216,18 @@ nm_active_connection_get_state (NMActiveConnection *self)
 	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->state;
 }
 
+static void
+emit_state_changed (NMActiveConnection *self, guint state, guint reason)
+{
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
+	                            &interface_info_active_connection,
+	                            &signal_info_state_changed,
+	                            "(uu)",
+	                            (guint32) state,
+	                            (guint32) reason);
+	g_signal_emit (self, signals[STATE_CHANGED], 0, state, reason);
+}
+
 void
 nm_active_connection_set_state (NMActiveConnection *self,
                                 NMActiveConnectionState new_state,
@@ -253,14 +258,14 @@ nm_active_connection_set_state (NMActiveConnection *self,
 	old_state = priv->state;
 	priv->state = new_state;
 	priv->state_set = TRUE;
-	g_signal_emit (self, signals[STATE_CHANGED], 0, (guint) new_state, (guint) reason);
+	emit_state_changed (self, new_state, reason);
 	_notify (self, PROP_STATE);
 
 	check_master_ready (self);
 
 	if (   new_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED
 	    || old_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
-		nm_settings_connection_update_timestamp (priv->settings_connection,
+		nm_settings_connection_update_timestamp (priv->settings_connection.obj,
 		                                         (guint64) time (NULL), TRUE);
 	}
 
@@ -356,7 +361,7 @@ nm_active_connection_get_settings_connection_id (NMActiveConnection *self)
 
 	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);
 
-	con = NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection;
+	con = NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection.obj;
 	return con
 	       ? nm_connection_get_id (NM_CONNECTION (con))
 	       : NULL;
@@ -367,7 +372,7 @@ _nm_active_connection_get_settings_connection (NMActiveConnection *self)
 {
 	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);
 
-	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection;
+	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection.obj;
 }
 
 NMSettingsConnection *
@@ -421,10 +426,7 @@ _set_applied_connection_take (NMActiveConnection *self,
 	if (nm_setting_connection_get_master (s_con))
 		flags_val |= NM_ACTIVATION_STATE_FLAG_IS_SLAVE;
 
-	if (NM_IN_STRSET (nm_setting_connection_get_connection_type (s_con),
-	                  NM_SETTING_BOND_SETTING_NAME,
-	                  NM_SETTING_BRIDGE_SETTING_NAME,
-	                  NM_SETTING_TEAM_SETTING_NAME))
+	if (+_nm_connection_type_is_master (nm_setting_connection_get_connection_type (s_con)))
 		flags_val |= NM_ACTIVATION_STATE_FLAG_IS_MASTER;
 
 	nm_active_connection_set_state_flags_full (self,
@@ -444,7 +446,7 @@ nm_active_connection_set_settings_connection (NMActiveConnection *self,
 	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 
 	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (connection));
-	g_return_if_fail (!priv->settings_connection);
+	g_return_if_fail (!priv->settings_connection.obj);
 	g_return_if_fail (!priv->applied_connection);
 
 	/* Can't change connection after the ActiveConnection is exported over D-Bus.
@@ -454,12 +456,12 @@ nm_active_connection_set_settings_connection (NMActiveConnection *self,
 	 * never changes (once it's set). That has effects for NMVpnConnection and
 	 * NMActivationRequest.
 	 * For example, we'd have to cancel all pending seret requests. */
-	g_return_if_fail (!nm_exported_object_is_exported (NM_EXPORTED_OBJECT (self)));
+	g_return_if_fail (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (self)));
 
 	_set_settings_connection (self, connection);
 
 	_set_applied_connection_take (self,
-	                              nm_simple_connection_new_clone (NM_CONNECTION (priv->settings_connection)));
+	                              nm_simple_connection_new_clone (NM_CONNECTION (priv->settings_connection.obj)));
 }
 
 gboolean
@@ -471,9 +473,9 @@ nm_active_connection_has_unmodified_applied_connection (NMActiveConnection *self
 
 	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 
-	g_return_val_if_fail (priv->settings_connection, FALSE);
+	g_return_val_if_fail (priv->settings_connection.obj, FALSE);
 
-	return nm_settings_connection_has_unmodified_applied_connection (priv->settings_connection,
+	return nm_settings_connection_has_unmodified_applied_connection (priv->settings_connection.obj,
 	                                                                 priv->applied_connection,
 	                                                                 compare_flags);
 }
@@ -489,10 +491,10 @@ nm_active_connection_clear_secrets (NMActiveConnection *self)
 
 	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 
-	if (nm_settings_connection_has_unmodified_applied_connection (priv->settings_connection,
+	if (nm_settings_connection_has_unmodified_applied_connection (priv->settings_connection.obj,
 	                                                              priv->applied_connection,
 	                                                              NM_SETTING_COMPARE_FLAG_NONE))
-		nm_connection_clear_secrets ((NMConnection *) priv->settings_connection);
+		nm_connection_clear_secrets ((NMConnection *) priv->settings_connection.obj);
 	nm_connection_clear_secrets (priv->applied_connection);
 }
 
@@ -513,9 +515,9 @@ nm_active_connection_set_specific_object (NMActiveConnection *self,
 	/* Nothing that calls this function should be using paths from D-Bus,
 	 * where NM uses "/" to mean NULL.
 	 */
-	g_assert (g_strcmp0 (specific_object, "/") != 0);
+	nm_assert (!nm_streq0 (specific_object, "/"));
 
-	if (g_strcmp0 (priv->specific_object, specific_object) == 0)
+	if (nm_streq0 (priv->specific_object, specific_object))
 		return;
 
 	g_free (priv->specific_object);
@@ -813,7 +815,7 @@ nm_active_connection_set_master (NMActiveConnection *self, NMActiveConnection *m
 
 	/* Master is write-once, and must be set before exporting the object */
 	g_return_if_fail (priv->master == NULL);
-	g_return_if_fail (!nm_exported_object_is_exported (NM_EXPORTED_OBJECT (self)));
+	g_return_if_fail (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (self)));
 	if (priv->device) {
 		/* Note, the master ActiveConnection may not yet have a device */
 		g_return_if_fail (priv->device != nm_active_connection_get_device (master));
@@ -852,11 +854,11 @@ _set_activation_type (NMActiveConnection *self,
 
 	priv->activation_type = activation_type;
 
-	if (priv->settings_connection) {
+	if (priv->settings_connection.obj) {
 		if (activation_type == NM_ACTIVATION_TYPE_EXTERNAL)
-			g_signal_connect (priv->settings_connection, "notify::"NM_SETTINGS_CONNECTION_FLAGS, (GCallback) _settings_connection_notify_flags, self);
+			g_signal_connect (priv->settings_connection.obj, NM_SETTINGS_CONNECTION_FLAGS_CHANGED, (GCallback) _settings_connection_flags_changed, self);
 		else
-			g_signal_handlers_disconnect_by_func (priv->settings_connection, _settings_connection_notify_flags, self);
+			g_signal_handlers_disconnect_by_func (priv->settings_connection.obj, _settings_connection_flags_changed, self);
 	}
 }
 
@@ -882,22 +884,29 @@ _set_activation_type_managed (NMActiveConnection *self)
 		nm_device_sys_iface_state_set (priv->device, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
 }
 
+NMActivationReason
+nm_active_connection_get_activation_reason (NMActiveConnection *self)
+{
+	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NM_ACTIVATION_REASON_UNSET);
+
+	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->activation_reason;
+}
+
 /*****************************************************************************/
 
 static void
-_settings_connection_notify_flags (NMSettingsConnection *settings_connection,
-                                   GParamSpec *param,
-                                   NMActiveConnection *self)
+_settings_connection_flags_changed (NMSettingsConnection *settings_connection,
+                                    NMActiveConnection *self)
 {
 	GError *error = NULL;
 
 	nm_assert (NM_IS_ACTIVE_CONNECTION (self));
 	nm_assert (NM_IS_SETTINGS_CONNECTION (settings_connection));
 	nm_assert (nm_active_connection_get_activation_type (self) == NM_ACTIVATION_TYPE_EXTERNAL);
-	nm_assert (NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection == settings_connection);
+	nm_assert (NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection.obj == settings_connection);
 
 	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (settings_connection),
-	                  NM_SETTINGS_CONNECTION_FLAGS_NM_GENERATED))
+	                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
 		return;
 
 	_set_activation_type_managed (self);
@@ -977,62 +986,93 @@ nm_active_connection_set_parent (NMActiveConnection *self, NMActiveConnection *p
 /*****************************************************************************/
 
 static void
-auth_done (NMAuthChain *chain,
+auth_cancel (NMActiveConnection *self)
+{
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+	if (priv->auth.call_id_network_control)
+		nm_auth_manager_check_authorization_cancel (priv->auth.call_id_network_control);
+	if (priv->auth.call_id_wifi_shared_permission) {
+		if (priv->auth.call_id_wifi_shared_permission == AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED)
+			priv->auth.call_id_wifi_shared_permission = NULL;
+		else
+			nm_auth_manager_check_authorization_cancel (priv->auth.call_id_wifi_shared_permission);
+	}
+	priv->auth.result_func = NULL;
+	priv->auth.user_data1 = NULL;
+	priv->auth.user_data2 = NULL;
+}
+
+static void
+auth_complete (NMActiveConnection *self, gboolean result, const char *message)
+{
+	_nm_unused gs_unref_object NMActiveConnection *self_keep_alive = g_object_ref (self);
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+	priv->auth.result_func (self,
+	                        result,
+	                        message,
+	                        priv->auth.user_data1,
+	                        priv->auth.user_data2);
+	auth_cancel (self);
+}
+
+static void
+auth_done (NMAuthManager *auth_mgr,
+           NMAuthManagerCallId *auth_call_id,
+           gboolean is_authorized,
+           gboolean is_challenge,
            GError *error,
-           GDBusMethodInvocation *unused,
            gpointer user_data)
+
 {
 	NMActiveConnection *self = NM_ACTIVE_CONNECTION (user_data);
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 	NMAuthCallResult result;
 
-	g_assert (priv->chain == chain);
-	g_assert (priv->result_func != NULL);
+	nm_assert (auth_call_id);
+	nm_assert (priv->auth.result_func);
 
-	/* Must stay alive over the callback */
-	g_object_ref (self);
-
-	if (error) {
-		priv->result_func (self, FALSE, error->message, priv->user_data1, priv->user_data2);
-		goto done;
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		if (auth_call_id == priv->auth.call_id_network_control)
+			priv->auth.call_id_network_control = NULL;
+		else {
+			nm_assert (auth_call_id == priv->auth.call_id_wifi_shared_permission);
+			priv->auth.call_id_wifi_shared_permission = NULL;
+		}
+		return;
 	}
 
-	/* Caller has had a chance to obtain authorization, so we only need to
-	 * check for 'yes' here.
-	 */
-	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL);
-	if (result != NM_AUTH_CALL_RESULT_YES) {
-		priv->result_func (self,
-		                   FALSE,
-		                   "Not authorized to control networking.",
-		                   priv->user_data1,
-		                   priv->user_data2);
-		goto done;
-	}
+	result = nm_auth_call_result_eval (is_authorized, is_challenge, error);
 
-	if (priv->wifi_shared_permission) {
-		result = nm_auth_chain_get_result (chain, priv->wifi_shared_permission);
+	if (auth_call_id == priv->auth.call_id_network_control) {
+		priv->auth.call_id_network_control = NULL;
 		if (result != NM_AUTH_CALL_RESULT_YES) {
-			priv->result_func (self,
-			                   FALSE,
-			                   "Not authorized to share connections via wifi.",
-			                   priv->user_data1,
-			                   priv->user_data2);
-			goto done;
+			auth_complete (self, FALSE, "Not authorized to control networking.");
+			return;
 		}
+	} else {
+		nm_assert (auth_call_id == priv->auth.call_id_wifi_shared_permission);
+		if (result != NM_AUTH_CALL_RESULT_YES) {
+			/* we don't fail right away. Instead, we mark that wifi-shared-permissions
+			 * are missing. We prefer to report the failure about network-control.
+			 * Below, we will wait longer for call_id_network_control (if it's still
+			 * pending). */
+			priv->auth.call_id_wifi_shared_permission = AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED;
+		} else
+			priv->auth.call_id_wifi_shared_permission = NULL;
 	}
 
-	/* Otherwise authorized and available to activate */
-	priv->result_func (self, TRUE, NULL, priv->user_data1, priv->user_data2);
+	if (priv->auth.call_id_network_control)
+		return;
 
-done:
-	nm_auth_chain_unref (chain);
-	priv->chain = NULL;
-	priv->result_func = NULL;
-	priv->user_data1 = NULL;
-	priv->user_data2 = NULL;
+	if (priv->auth.call_id_wifi_shared_permission) {
+		if (priv->auth.call_id_wifi_shared_permission == AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED)
+			auth_complete (self, FALSE, "Not authorized to share connections via wifi.");
+		return;
+	}
 
-	g_object_unref (self);
+	auth_complete (self, TRUE, NULL);
 }
 
 /**
@@ -1060,37 +1100,43 @@ nm_active_connection_authorize (NMActiveConnection *self,
 	const char *wifi_permission = NULL;
 	NMConnection *con;
 
-	g_return_if_fail (result_func != NULL);
-	g_return_if_fail (priv->chain == NULL);
+	g_return_if_fail (result_func);
+	g_return_if_fail (!priv->auth.call_id_network_control);
+	nm_assert (!priv->auth.call_id_wifi_shared_permission);
 
 	if (initial_connection) {
 		g_return_if_fail (NM_IS_CONNECTION (initial_connection));
-		g_return_if_fail (!priv->settings_connection);
+		g_return_if_fail (!priv->settings_connection.obj);
 		g_return_if_fail (!priv->applied_connection);
 		con = initial_connection;
 	} else {
-		g_return_if_fail (NM_IS_SETTINGS_CONNECTION (priv->settings_connection));
+		g_return_if_fail (NM_IS_SETTINGS_CONNECTION (priv->settings_connection.obj));
 		g_return_if_fail (NM_IS_CONNECTION (priv->applied_connection));
 		con = priv->applied_connection;
 	}
 
-	priv->chain = nm_auth_chain_new_subject (priv->subject, NULL, auth_done, self);
-	g_assert (priv->chain);
-
-	/* Check that the subject is allowed to use networking at all */
-	nm_auth_chain_add_call (priv->chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE);
+	priv->auth.call_id_network_control = nm_auth_manager_check_authorization (nm_auth_manager_get (),
+	                                                                          priv->subject,
+	                                                                          NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                                                                          TRUE,
+	                                                                          auth_done,
+	                                                                          self);
 
 	/* Shared wifi connections require special permissions too */
 	wifi_permission = nm_utils_get_shared_wifi_permission (con);
 	if (wifi_permission) {
-		priv->wifi_shared_permission = wifi_permission;
-		nm_auth_chain_add_call (priv->chain, wifi_permission, TRUE);
+		priv->auth.call_id_wifi_shared_permission = nm_auth_manager_check_authorization (nm_auth_manager_get (),
+		                                                                                 priv->subject,
+		                                                                                 wifi_permission,
+		                                                                                 TRUE,
+		                                                                                 auth_done,
+		                                                                                 self);
 	}
 
 	/* Wait for authorization */
-	priv->result_func = result_func;
-	priv->user_data1 = user_data1;
-	priv->user_data2 = user_data2;
+	priv->auth.result_func = result_func;
+	priv->auth.user_data1 = user_data1;
+	priv->auth.user_data2 = user_data2;
 }
 
 /*****************************************************************************/
@@ -1152,31 +1198,40 @@ get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE ((NMActiveConnection *) object);
-	GPtrArray *devices;
+	char **strv;
 	NMDevice *master_device = NULL;
 
 	switch (prop_id) {
+
+	/* note that while priv->settings_connection.obj might not be set initially,
+	 * it will be set before the object is exported on D-Bus. Hence,
+	 * nobody is calling these property getters before the object is
+	 * exported, at which point we will have a valid settings-connection.
+	 *
+	 * Therefore, intentionally not check whether priv->settings_connection.obj
+	 * is set, to get an assertion failure if somebody tries to access the
+	 * getters at the wrong time. */
 	case PROP_CONNECTION:
-		g_value_set_string (value, nm_connection_get_path (NM_CONNECTION (priv->settings_connection)));
+		g_value_set_string (value, nm_dbus_track_obj_path_get (&priv->settings_connection));
 		break;
 	case PROP_ID:
-		g_value_set_string (value, nm_connection_get_id (NM_CONNECTION (priv->settings_connection)));
+		g_value_set_string (value, nm_connection_get_id (NM_CONNECTION (priv->settings_connection.obj)));
 		break;
 	case PROP_UUID:
-		g_value_set_string (value, nm_connection_get_uuid (NM_CONNECTION (priv->settings_connection)));
+		g_value_set_string (value, nm_connection_get_uuid (NM_CONNECTION (priv->settings_connection.obj)));
 		break;
 	case PROP_TYPE:
-		g_value_set_string (value, nm_connection_get_connection_type (NM_CONNECTION (priv->settings_connection)));
+		g_value_set_string (value, nm_connection_get_connection_type (NM_CONNECTION (priv->settings_connection.obj)));
 		break;
+
 	case PROP_SPECIFIC_OBJECT:
-		g_value_set_string (value, priv->specific_object ? priv->specific_object : "/");
+		g_value_set_string (value, priv->specific_object);
 		break;
 	case PROP_DEVICES:
-		devices = g_ptr_array_sized_new (2);
+		strv = g_new0 (char *, 2);
 		if (priv->device && priv->state < NM_ACTIVE_CONNECTION_STATE_DEACTIVATED)
-			g_ptr_array_add (devices, g_strdup (nm_exported_object_get_path (NM_EXPORTED_OBJECT (priv->device))));
-		g_ptr_array_add (devices, NULL);
-		g_value_take_boxed (value, (char **) g_ptr_array_free (devices, FALSE));
+			strv[0] = g_strdup (nm_dbus_object_get_path (NM_DBUS_OBJECT (priv->device)));
+		g_value_take_boxed (value, strv);
 		break;
 	case PROP_STATE:
 		if (priv->state_set)
@@ -1196,19 +1251,19 @@ get_property (GObject *object, guint prop_id,
 		break;
 	case PROP_IP4_CONFIG:
 		/* The IP and DHCP config properties may be overridden by a subclass */
-		g_value_set_string (value, "/");
+		g_value_set_string (value, NULL);
 		break;
 	case PROP_DHCP4_CONFIG:
-		g_value_set_string (value, "/");
+		g_value_set_string (value, NULL);
 		break;
 	case PROP_DEFAULT6:
 		g_value_set_boolean (value, priv->is_default6);
 		break;
 	case PROP_IP6_CONFIG:
-		g_value_set_string (value, "/");
+		g_value_set_string (value, NULL);
 		break;
 	case PROP_DHCP6_CONFIG:
-		g_value_set_string (value, "/");
+		g_value_set_string (value, NULL);
 		break;
 	case PROP_VPN:
 		g_value_set_boolean (value, priv->vpn);
@@ -1216,7 +1271,7 @@ get_property (GObject *object, guint prop_id,
 	case PROP_MASTER:
 		if (priv->master)
 			master_device = nm_active_connection_get_device (priv->master);
-		nm_utils_g_value_set_object_path (value, master_device);
+		nm_dbus_utils_g_value_set_object_path (value, master_device);
 		break;
 	case PROP_INT_SUBJECT:
 		g_value_set_object (value, priv->subject);
@@ -1281,12 +1336,17 @@ set_property (GObject *object, guint prop_id,
 			g_return_if_reached ();
 		_set_activation_type (self, (NMActivationType) i);
 		break;
+	case PROP_INT_ACTIVATION_REASON:
+		/* construct-only */
+		i = g_value_get_int (value);
+		priv->activation_reason = i;
+		nm_assert (priv->activation_reason == ((NMActivationReason) i));
+		break;
 	case PROP_SPECIFIC_OBJECT:
 		/* construct-only */
 		tmp = g_value_get_string (value);
-		/* NM uses "/" to mean NULL */
-		if (g_strcmp0 (tmp, "/") != 0)
-			priv->specific_object = g_strdup (tmp);
+		tmp = nm_utils_dbus_normalize_object_path (tmp);
+		priv->specific_object = g_strdup (tmp);
 		break;
 	case PROP_DEFAULT:
 		priv->is_default = g_value_get_boolean (value);
@@ -1316,6 +1376,10 @@ nm_active_connection_init (NMActiveConnection *self)
 	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_ACTIVE_CONNECTION, NMActiveConnectionPrivate);
 	self->_priv = priv;
 
+	nm_dbus_track_obj_path_init (&priv->settings_connection,
+	                             G_OBJECT (self),
+	                             obj_properties[PROP_CONNECTION]);
+
 	c_list_init (&self->active_connections_lst);
 
 	_LOGT ("creating");
@@ -1333,8 +1397,8 @@ constructed (GObject *object)
 	G_OBJECT_CLASS (nm_active_connection_parent_class)->constructed (object);
 
 	if (   !priv->applied_connection
-	    && priv->settings_connection)
-		priv->applied_connection = nm_simple_connection_new_clone (NM_CONNECTION (priv->settings_connection));
+	    && priv->settings_connection.obj)
+		priv->applied_connection = nm_simple_connection_new_clone (NM_CONNECTION (priv->settings_connection.obj));
 
 	_LOGD ("constructed (%s, version-id %llu, type %s)",
 	       G_OBJECT_TYPE_NAME (self),
@@ -1364,13 +1428,9 @@ dispose (GObject *object)
 
 	_LOGD ("disposing");
 
-	if (priv->chain) {
-		nm_auth_chain_unref (priv->chain);
-		priv->chain = NULL;
-	}
+	auth_cancel (self);
 
-	g_free (priv->specific_object);
-	priv->specific_object = NULL;
+	nm_clear_g_free (&priv->specific_object);
 
 	_set_settings_connection (self, NULL);
 	g_clear_object (&priv->applied_connection);
@@ -1393,21 +1453,70 @@ dispose (GObject *object)
 }
 
 static void
+finalize (GObject *object)
+{
+	NMActiveConnection *self = NM_ACTIVE_CONNECTION (object);
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+	nm_dbus_track_obj_path_set (&priv->settings_connection, NULL, FALSE);
+
+	G_OBJECT_CLASS (nm_active_connection_parent_class)->finalize (object);
+}
+
+static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"StateChanged",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("state",  "u"),
+		NM_DEFINE_GDBUS_ARG_INFO ("reason", "u"),
+	),
+);
+
+static const NMDBusInterfaceInfoExtended interface_info_active_connection = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_ACTIVE_CONNECTION,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+			&signal_info_state_changed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Connection",      "o",  NM_ACTIVE_CONNECTION_CONNECTION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("SpecificObject",  "o",  NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Id",              "s",  NM_ACTIVE_CONNECTION_ID),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Uuid",            "s",  NM_ACTIVE_CONNECTION_UUID),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Type",            "s",  NM_ACTIVE_CONNECTION_TYPE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Devices",         "ao", NM_ACTIVE_CONNECTION_DEVICES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("State",           "u",  NM_ACTIVE_CONNECTION_STATE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("StateFlags",      "u",  NM_ACTIVE_CONNECTION_STATE_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Default",         "b",  NM_ACTIVE_CONNECTION_DEFAULT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Ip4Config",       "o",  NM_ACTIVE_CONNECTION_IP4_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Dhcp4Config",     "o",  NM_ACTIVE_CONNECTION_DHCP4_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Default6",        "b",  NM_ACTIVE_CONNECTION_DEFAULT6),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Ip6Config",       "o",  NM_ACTIVE_CONNECTION_IP6_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Dhcp6Config",     "o",  NM_ACTIVE_CONNECTION_DHCP6_CONFIG),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Vpn",             "b",  NM_ACTIVE_CONNECTION_VPN),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Master",          "o",  NM_ACTIVE_CONNECTION_MASTER),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
+static void
 nm_active_connection_class_init (NMActiveConnectionClass *ac_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (ac_class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (ac_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (ac_class);
 
 	g_type_class_add_private (ac_class, sizeof (NMActiveConnectionPrivate));
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/ActiveConnection");
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/ActiveConnection");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_active_connection);
 
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 	object_class->constructed = constructed;
 	object_class->dispose = dispose;
+	object_class->finalize = finalize;
 
-	/* D-Bus exported properties */
 	obj_properties[PROP_CONNECTION] =
 	     g_param_spec_string (NM_ACTIVE_CONNECTION_CONNECTION, "", "",
 	                          NULL,
@@ -1551,6 +1660,15 @@ nm_active_connection_class_init (NMActiveConnectionClass *ac_class)
 	                       G_PARAM_CONSTRUCT_ONLY |
 	                       G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_INT_ACTIVATION_REASON] =
+	     g_param_spec_int (NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON, "", "",
+	                       NM_ACTIVATION_REASON_UNSET,
+	                       NM_ACTIVATION_REASON_USER_REQUEST,
+	                       NM_ACTIVATION_REASON_UNSET,
+	                       G_PARAM_WRITABLE |
+	                       G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	signals[DEVICE_CHANGED] =
@@ -1583,9 +1701,4 @@ nm_active_connection_class_init (NMActiveConnectionClass *ac_class)
 	                  G_SIGNAL_RUN_FIRST,
 	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (ac_class),
-	                                        NMDBUS_TYPE_ACTIVE_CONNECTION_SKELETON,
-	                                        NULL);
 }
-
diff --git a/src/nm-active-connection.h b/src/nm-active-connection.h
index 3015b5c5..c3d08b64 100644
--- a/src/nm-active-connection.h
+++ b/src/nm-active-connection.h
@@ -21,10 +21,9 @@
 #ifndef __NETWORKMANAGER_ACTIVE_CONNECTION_H__
 #define __NETWORKMANAGER_ACTIVE_CONNECTION_H__
 
-#include "nm-exported-object.h"
+#include "c-list/src/c-list.h"
 #include "nm-connection.h"
-
-#include "nm-utils/c-list.h"
+#include "nm-dbus-object.h"
 
 #define NM_TYPE_ACTIVE_CONNECTION            (nm_active_connection_get_type ())
 #define NM_ACTIVE_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_ACTIVE_CONNECTION, NMActiveConnection))
@@ -54,11 +53,12 @@
 /* Internal non-exported properties */
 #define NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION "int-settings-connection"
 #define NM_ACTIVE_CONNECTION_INT_APPLIED_CONNECTION  "int-applied-connection"
-#define NM_ACTIVE_CONNECTION_INT_DEVICE         "int-device"
-#define NM_ACTIVE_CONNECTION_INT_SUBJECT        "int-subject"
-#define NM_ACTIVE_CONNECTION_INT_MASTER         "int-master"
-#define NM_ACTIVE_CONNECTION_INT_MASTER_READY   "int-master-ready"
-#define NM_ACTIVE_CONNECTION_INT_ACTIVATION_TYPE "int-activation-type"
+#define NM_ACTIVE_CONNECTION_INT_DEVICE              "int-device"
+#define NM_ACTIVE_CONNECTION_INT_SUBJECT             "int-subject"
+#define NM_ACTIVE_CONNECTION_INT_MASTER              "int-master"
+#define NM_ACTIVE_CONNECTION_INT_MASTER_READY        "int-master-ready"
+#define NM_ACTIVE_CONNECTION_INT_ACTIVATION_TYPE     "int-activation-type"
+#define NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON   "int-activation-reason"
 
 /* Signals */
 #define NM_ACTIVE_CONNECTION_STATE_CHANGED           "state-changed"
@@ -71,7 +71,7 @@
 struct _NMActiveConnectionPrivate;
 
 struct _NMActiveConnection {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	struct _NMActiveConnectionPrivate *_priv;
 
 	/* active connection can be tracked in a list by NMManager. This is
@@ -80,7 +80,7 @@ struct _NMActiveConnection {
 };
 
 typedef struct {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 
 	/* re-emits device state changes as a convenience for subclasses for
 	 * device states >= DISCONNECTED.
@@ -185,6 +185,8 @@ void          nm_active_connection_set_parent (NMActiveConnection *self,
 
 NMActivationType nm_active_connection_get_activation_type (NMActiveConnection *self);
 
+NMActivationReason nm_active_connection_get_activation_reason (NMActiveConnection *self);
+
 void          nm_active_connection_clear_secrets (NMActiveConnection *self);
 
 #endif /* __NETWORKMANAGER_ACTIVE_CONNECTION_H__ */
diff --git a/src/nm-audit-manager.c b/src/nm-audit-manager.c
index 2d75c0e7..45d068f9 100644
--- a/src/nm-audit-manager.c
+++ b/src/nm-audit-manager.c
@@ -123,7 +123,7 @@ build_message (GPtrArray *fields, AuditBackend backend)
 	for (i = 0; i < fields->len; i++) {
 		field = fields->pdata[i];
 
-		if (!NM_FLAGS_HAS (field->backends, backend))
+		if (!NM_FLAGS_ANY (field->backends, backend))
 			continue;
 
 		if (first)
diff --git a/src/nm-audit-manager.h b/src/nm-audit-manager.h
index 56e26584..59b8048f 100644
--- a/src/nm-audit-manager.h
+++ b/src/nm-audit-manager.h
@@ -57,6 +57,7 @@ typedef struct _NMAuditManagerClass NMAuditManagerClass;
 #define NM_AUDIT_OP_CHECKPOINT_CREATE       "checkpoint-create"
 #define NM_AUDIT_OP_CHECKPOINT_ROLLBACK     "checkpoint-rollback"
 #define NM_AUDIT_OP_CHECKPOINT_DESTROY      "checkpoint-destroy"
+#define NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT "checkpoint-adjust-rollback-timeout"
 
 GType nm_audit_manager_get_type (void);
 NMAuditManager *nm_audit_manager_get (void);
diff --git a/src/nm-auth-manager.c b/src/nm-auth-manager.c
index 003d9975..199a2e40 100644
--- a/src/nm-auth-manager.c
+++ b/src/nm-auth-manager.c
@@ -22,6 +22,7 @@
 
 #include "nm-auth-manager.h"
 
+#include "c-list/src/c-list.h"
 #include "nm-errors.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
@@ -30,6 +31,9 @@
 #define POLKIT_OBJECT_PATH                  "/org/freedesktop/PolicyKit1/Authority"
 #define POLKIT_INTERFACE                    "org.freedesktop.PolicyKit1.Authority"
 
+#define CANCELLATION_ID_PREFIX "cancellation-id-"
+#define CANCELLATION_TIMEOUT_MS 5000
+
 /*****************************************************************************/
 
 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
@@ -44,13 +48,14 @@ enum {
 static guint signals[LAST_SIGNAL] = {0};
 
 typedef struct {
-	gboolean polkit_enabled;
-#if WITH_POLKIT
-	guint call_id_counter;
-	GCancellable *new_proxy_cancellable;
-	GSList *queued_calls;
+	CList calls_lst_head;
 	GDBusProxy *proxy;
-#endif
+	GCancellable *new_proxy_cancellable;
+	GCancellable *cancel_cancellable;
+	guint64 call_numid_counter;
+	bool polkit_enabled:1;
+	bool disposing:1;
+	bool shutting_down:1;
 } NMAuthManagerPrivate;
 
 struct _NMAuthManager {
@@ -85,6 +90,22 @@ NM_DEFINE_SINGLETON_REGISTER (NMAuthManager);
         } \
     } G_STMT_END
 
+#define _NMLOG2(level, call_id, ...) \
+    G_STMT_START { \
+        if (nm_logging_enabled ((level), (_NMLOG_DOMAIN))) { \
+            NMAuthManagerCallId *_call_id = (call_id); \
+            char __prefix[30] = _NMLOG_PREFIX_NAME; \
+            \
+            if (_call_id->self != singleton_instance) \
+                g_snprintf (__prefix, sizeof (__prefix), ""_NMLOG_PREFIX_NAME"[%p]", _call_id->self); \
+            _nm_log ((level), (_NMLOG_DOMAIN), 0, NULL, NULL, \
+                     "%s: call[%"G_GUINT64_FORMAT"]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                     __prefix, \
+                     _call_id->call_numid \
+                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
 /*****************************************************************************/
 
 gboolean
@@ -97,251 +118,323 @@ nm_auth_manager_get_polkit_enabled (NMAuthManager *self)
 
 /*****************************************************************************/
 
-#if WITH_POLKIT
-
 typedef enum {
 	POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE                   = 0,
 	POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION = (1<<0),
 } PolkitCheckAuthorizationFlags;
 
-typedef struct {
-	guint call_id;
+typedef enum {
+	IDLE_REASON_AUTHORIZED,
+	IDLE_REASON_NO_DBUS,
+} IdleReason;
+
+struct _NMAuthManagerCallId {
+	CList calls_lst;
 	NMAuthManager *self;
-	GSimpleAsyncResult *simple;
-	gchar *cancellation_id;
 	GVariant *dbus_parameters;
-	GCancellable *cancellable;
-} CheckAuthData;
+	GCancellable *dbus_cancellable;
+	NMAuthManagerCheckAuthorizationCallback callback;
+	gpointer user_data;
+	guint64 call_numid;
+	guint idle_id;
+	IdleReason idle_reason:8;
+};
+
+#define cancellation_id_to_str_a(call_numid) \
+	nm_sprintf_bufa (NM_STRLEN (CANCELLATION_ID_PREFIX) + 20, \
+	                 CANCELLATION_ID_PREFIX"%"G_GUINT64_FORMAT, \
+	                 (call_numid))
 
 static void
-_check_auth_data_free (CheckAuthData *data)
+_call_id_free (NMAuthManagerCallId *call_id)
 {
-	if (data->dbus_parameters)
-		g_variant_unref (data->dbus_parameters);
-	g_object_unref (data->self);
-	g_object_unref (data->simple);
-	g_clear_object (&data->cancellable);
-	g_free (data->cancellation_id);
-	g_free (data);
+	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
+		 * for _call_check_authorize_cb() */
+		g_cancellable_cancel (call_id->dbus_cancellable);
+		return;
+	}
+
+	g_object_unref (call_id->self);
+	g_slice_free (NMAuthManagerCallId, call_id);
 }
 
 static void
-_call_check_authorization_complete_with_error (CheckAuthData *data,
-                                               const char *error_message)
+_call_id_invoke_callback (NMAuthManagerCallId *call_id,
+                          gboolean is_authorized,
+                          gboolean is_challenge,
+                          GError *error)
 {
-	NMAuthManager *self = data->self;
-
-	_LOGD ("call[%u]: CheckAuthorization failed due to internal error: %s", data->call_id, error_message);
-	g_simple_async_result_set_error (data->simple,
-	                                 NM_MANAGER_ERROR,
-	                                 NM_MANAGER_ERROR_FAILED,
-	                                 "Authorization check failed: %s",
-	                                 error_message);
-
-	g_simple_async_result_complete_in_idle (data->simple);
-
-	_check_auth_data_free (data);
+	c_list_unlink (&call_id->calls_lst);
+
+	call_id->callback (call_id->self,
+	                   call_id,
+	                   is_authorized,
+	                   is_challenge,
+	                   error,
+	                   call_id->user_data);
+	_call_id_free (call_id);
 }
 
 static void
-cancel_check_authorization_cb (GDBusProxy *proxy,
+cancel_check_authorization_cb (GObject *proxy,
                                GAsyncResult *res,
                                gpointer user_data)
 {
-	NMAuthManager *self = user_data;
-	GVariant *value;
-	GError *error= NULL;
-
-	value = g_dbus_proxy_call_finish (proxy, res, &error);
-	if (value == NULL) {
-		g_dbus_error_strip_remote_error (error);
-		_LOGD ("Error cancelling authorization check: %s", error->message);
-		g_error_free (error);
-	} else
-		g_variant_unref (value);
+	NMAuthManagerCallId *call_id = user_data;
+	gs_unref_variant GVariant *value = NULL;
+	gs_free_error GError *error= NULL;
+
+	value = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		_LOG2T (call_id, "cancel request was cancelled");
+	else if (error)
+		_LOG2T (call_id, "cancel request failed: %s", error->message);
+	else
+		_LOG2T (call_id, "cancel request succeeded");
 
-	g_object_unref (self);
+	_call_id_free (call_id);
 }
 
-typedef struct {
-	gboolean is_authorized;
-	gboolean is_challenge;
-} CheckAuthorizationResult;
-
 static void
-check_authorization_cb (GDBusProxy *proxy,
-                        GAsyncResult *res,
-                        gpointer user_data)
+_call_check_authorize_cb (GObject *proxy,
+                          GAsyncResult *res,
+                          gpointer user_data)
 {
-	CheckAuthData *data = user_data;
-	NMAuthManager *self = data->self;
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
-	GVariant *value;
-	GError *error = NULL;
-
-	value = _nm_dbus_proxy_call_finish (proxy, res, G_VARIANT_TYPE ("((bba{ss}))"), &error);
-	if (value == NULL) {
-		if (data->cancellation_id != NULL &&
-		    (   g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)
-		     && !g_dbus_error_is_remote_error (error))) {
-			_LOGD ("call[%u]: CheckAuthorization cancelled", data->call_id);
-			g_dbus_proxy_call (priv->proxy,
-			                   "CancelCheckAuthorization",
-			                   g_variant_new ("(s)", data->cancellation_id),
-			                   G_DBUS_CALL_FLAGS_NONE,
-			                   -1,
-			                   NULL, /* GCancellable */
-			                   (GAsyncReadyCallback) cancel_check_authorization_cb,
-			                   g_object_ref (self));
-		} else
-			_LOGD ("call[%u]: CheckAuthorization failed: %s", data->call_id, error->message);
-		g_dbus_error_strip_remote_error (error);
-		g_simple_async_result_set_error (data->simple,
-		                                 NM_MANAGER_ERROR,
-		                                 NM_MANAGER_ERROR_FAILED,
-		                                 "Authorization check failed: %s",
-		                                 error->message);
-		g_error_free (error);
-	} else {
-		CheckAuthorizationResult *result;
+	NMAuthManagerCallId *call_id = user_data;
+	NMAuthManager *self;
+	NMAuthManagerPrivate *priv;
+	gs_unref_variant GVariant *value = NULL;
+	gs_free_error GError *error = NULL;
+	gboolean is_authorized = FALSE;
+	gboolean is_challenge = FALSE;
 
-		result = g_new0 (CheckAuthorizationResult, 1);
+	/* we need to clear the cancelable, to signal for _call_id_free() that we
+	 * are not in a pending call.
+	 *
+	 * Note how _call_id_free() kept call-id alive, even if the request was
+	 * already cancelled. */
+	g_clear_object (&call_id->dbus_cancellable);
 
-		g_variant_get (value,
-		               "((bb@a{ss}))",
-		               &result->is_authorized,
-		               &result->is_challenge,
-		               NULL);
-		g_variant_unref (value);
+	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);
+	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) {
+			/* 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
+			 * after call-id was schedule.
+			 * force-shutdown should only be called after:
+			 *   - cancel all pending requests
+			 *   - give enough time to cancel the request and schedule a D-Bus call
+			 *     to CancelCheckAuthorization (below), before issuing force-shutdown. */
+			g_return_if_reached ();
+		}
 
-		_LOGD ("call[%u]: CheckAuthorization succeeded: (is_authorized=%d, is_challenge=%d)", data->call_id, result->is_authorized, result->is_challenge);
-		g_simple_async_result_set_op_res_gpointer (data->simple, result, g_free);
+		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);
+		return;
 	}
 
-	g_simple_async_result_complete (data->simple);
+	if (!error) {
+		g_variant_get (value,
+		               "((bb@a{ss}))",
+		               &is_authorized,
+		               &is_challenge,
+		               NULL);
+		_LOG2T (call_id, "completed: authorized=%d, challenge=%d",
+		        is_authorized, is_challenge);
+	} else
+		_LOG2T (call_id, "completed: failed: %s", error->message);
 
-	_check_auth_data_free (data);
+	_call_id_invoke_callback (call_id, is_authorized, is_challenge, error);
 }
 
 static void
-_call_check_authorization (CheckAuthData *data)
+_call_check_authorize (NMAuthManagerCallId *call_id)
 {
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (data->self);
+	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",
-	                   data->dbus_parameters,
+	                   g_steal_pointer (&call_id->dbus_parameters),
 	                   G_DBUS_CALL_FLAGS_NONE,
 	                   G_MAXINT, /* no timeout */
-	                   data->cancellable,
-	                   (GAsyncReadyCallback) check_authorization_cb,
-	                   data);
-	g_clear_object (&data->cancellable);
-	data->dbus_parameters = NULL;
+	                   call_id->dbus_cancellable,
+	                   _call_check_authorize_cb,
+	                   call_id);
 }
 
-void
-nm_auth_manager_polkit_authority_check_authorization (NMAuthManager *self,
-                                                      NMAuthSubject *subject,
-                                                      const char *action_id,
-                                                      gboolean allow_user_interaction,
-                                                      GCancellable *cancellable,
-                                                      GAsyncReadyCallback callback,
-                                                      gpointer user_data)
+static gboolean
+_call_on_idle (gpointer user_data)
+{
+	NMAuthManagerCallId *call_id = user_data;
+	gs_free_error GError *error = NULL;
+	gboolean is_authorized = FALSE;
+	gboolean is_challenge = FALSE;
+	const char *error_msg = NULL;
+
+	call_id->idle_id = 0;
+	if (call_id->idle_reason == IDLE_REASON_AUTHORIZED) {
+		is_authorized = TRUE;
+		_LOG2T (call_id, "completed: authorized=%d, challenge=%d (simulated)",
+		        is_authorized, is_challenge);
+	} else {
+		nm_assert (call_id->idle_reason == IDLE_REASON_NO_DBUS);
+		error_msg = "failure creating GDBusProxy for authorization request";
+		_LOG2T (call_id, "completed: failed due to no D-Bus proxy");
+	}
+
+	if (error_msg)
+		g_set_error_literal (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, error_msg);
+	_call_id_invoke_callback (call_id, is_authorized, is_challenge, error);
+	return G_SOURCE_REMOVE;
+}
+
+/*
+ * @callback must never be invoked synchronously.
+ *
+ * @callback is always invoked exactly once, and never synchronously.
+ * You may cancel the invocation with nm_auth_manager_check_authorization_cancel(),
+ * but: you may only do so exactly once, and only before @callback is
+ * invoked. Even if you cancel the request, @callback will still be invoked
+ * (synchronously, during the _cancel() callback).
+ *
+ * The request keeps @self alive (it needs to do so, because when cancelling a
+ * request we might need to do an additional CancelCheckAuthorization call, for
+ * which @self must be live long enough).
+ */
+NMAuthManagerCallId *
+nm_auth_manager_check_authorization (NMAuthManager *self,
+                                     NMAuthSubject *subject,
+                                     const char *action_id,
+                                     gboolean allow_user_interaction,
+                                     NMAuthManagerCheckAuthorizationCallback callback,
+                                     gpointer user_data)
 {
 	NMAuthManagerPrivate *priv;
+	PolkitCheckAuthorizationFlags flags;
 	char subject_buf[64];
 	GVariantBuilder builder;
-	PolkitCheckAuthorizationFlags flags;
 	GVariant *subject_value;
 	GVariant *details_value;
-	CheckAuthData *data;
+	NMAuthManagerCallId *call_id;
 
-	g_return_if_fail (NM_IS_AUTH_MANAGER (self));
-	g_return_if_fail (NM_IS_AUTH_SUBJECT (subject));
-	g_return_if_fail (nm_auth_subject_is_unix_process (subject));
-	g_return_if_fail (action_id != NULL);
-	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+	g_return_val_if_fail (NM_IS_AUTH_MANAGER (self), NULL);
+	g_return_val_if_fail (NM_IN_SET (nm_auth_subject_get_subject_type (subject),
+	                                 NM_AUTH_SUBJECT_TYPE_INTERNAL,
+	                                 NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS),
+	                      NULL);
+	g_return_val_if_fail (action_id, NULL);
 
 	priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (priv->polkit_enabled);
+	g_return_val_if_fail (!priv->disposing, NULL);
+	g_return_val_if_fail (!priv->shutting_down, NULL);
 
 	flags = allow_user_interaction
 	    ? POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION
 	    : POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE;
 
-	subject_value = nm_auth_subject_unix_process_to_polkit_gvariant (subject);
-	nm_assert (g_variant_is_floating (subject_value));
-
-	/* ((PolkitDetails *)NULL) */
-	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
-	details_value = g_variant_builder_end (&builder);
-
-	data = g_new0 (CheckAuthData, 1);
-	data->call_id = ++priv->call_id_counter;
-	data->self = g_object_ref (self);
-	data->simple = g_simple_async_result_new (G_OBJECT (self),
-	                                          callback,
-	                                          user_data,
-	                                          nm_auth_manager_polkit_authority_check_authorization);
-	if (cancellable != NULL) {
-		data->cancellation_id = g_strdup_printf ("cancellation-id-%u", data->call_id);
-		data->cancellable = g_object_ref (cancellable);
-	}
-
-	data->dbus_parameters = g_variant_new ("(@(sa{sv})s@a{ss}us)",
-	                                       subject_value,
-	                                       action_id,
-	                                       details_value,
-	                                       (guint32) flags,
-	                                       data->cancellation_id != NULL ? data->cancellation_id : "");
-
-	if (priv->new_proxy_cancellable) {
-		_LOGD ("call[%u]: CheckAuthorization(%s), subject=%s (wait for proxy)", data->call_id, action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
-
-		priv->queued_calls = g_slist_prepend (priv->queued_calls, data);
-	} else if (!priv->proxy) {
-		_LOGD ("call[%u]: CheckAuthorization(%s), subject=%s (fails due to invalid DBUS proxy)", data->call_id, action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
-
-		_call_check_authorization_complete_with_error (data, "invalid DBUS proxy");
+	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;
+	c_list_link_tail (&priv->calls_lst_head, &call_id->calls_lst);
+
+	if (!priv->polkit_enabled) {
+		_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);
+	} else if (nm_auth_subject_is_internal (subject)) {
+		_LOG2T (call_id, "CheckAuthorization(%s), subject=%s (succeeding for internal request)", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
+		call_id->idle_reason = IDLE_REASON_AUTHORIZED;
+		call_id->idle_id = g_idle_add (_call_on_idle, call_id);
+	} else if (nm_auth_subject_get_unix_process_uid (subject) == 0) {
+		_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 {
-		_LOGD ("call[%u]: CheckAuthorization(%s), subject=%s", data->call_id, action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
-
-		_call_check_authorization (data);
+		subject_value = nm_auth_subject_unix_process_to_polkit_gvariant (subject);
+		nm_assert (g_variant_is_floating (subject_value));
+
+		/* ((PolkitDetails *)NULL) */
+		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);
+		}
 	}
+
+	return call_id;
 }
 
-gboolean
-nm_auth_manager_polkit_authority_check_authorization_finish (NMAuthManager *self,
-                                                             GAsyncResult *res,
-                                                             gboolean *out_is_authorized,
-                                                             gboolean *out_is_challenge,
-                                                             GError **error)
+void
+nm_auth_manager_check_authorization_cancel (NMAuthManagerCallId *call_id)
 {
-	gboolean success = FALSE;
-	gboolean is_authorized = FALSE;
-	gboolean is_challenge = FALSE;
+	NMAuthManager *self;
+	gs_free_error GError *error = NULL;
 
-	g_return_val_if_fail (NM_IS_AUTH_MANAGER (self), FALSE);
-	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (res), FALSE);
-	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	g_return_if_fail (call_id);
 
-	if (!g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) {
-		CheckAuthorizationResult *result;
+	self = call_id->self;
 
-		result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
-		is_authorized = !!result->is_authorized;
-		is_challenge = !!result->is_challenge;
-		success = TRUE;
-	}
-	g_assert ((success && !error) || (!success || error));
+	g_return_if_fail (NM_IS_AUTH_MANAGER (self));
+	g_return_if_fail (!c_list_is_empty (&call_id->calls_lst));
+
+	nm_assert (c_list_contains (&NM_AUTH_MANAGER_GET_PRIVATE (self)->calls_lst_head, &call_id->calls_lst));
 
-	if (out_is_authorized)
-		*out_is_authorized = is_authorized;
-	if (out_is_challenge)
-		*out_is_challenge = is_challenge;
-	return success;
+	nm_utils_error_set_cancelled (&error, FALSE, "NMAuthManager");
+	_LOG2T (call_id, "completed: failed due to call cancelled");
+	_call_id_invoke_callback (call_id,
+	                          FALSE,
+	                          FALSE,
+	                          error);
 }
 
 /*****************************************************************************/
@@ -350,14 +443,14 @@ static void
 _emit_changed_signal (NMAuthManager *self)
 {
 	_LOGD ("emit changed signal");
-	g_signal_emit_by_name (self, NM_AUTH_MANAGER_SIGNAL_CHANGED);
+	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);
-	char *name_owner;
+	gs_free char *name_owner = NULL;
 
 	name_owner = g_dbus_proxy_get_name_owner (priv->proxy);
 	if (name_owner)
@@ -365,10 +458,7 @@ _log_name_owner (NMAuthManager *self, char **out_name_owner)
 	else
 		_LOGD ("dbus name owner: none");
 
-	if (out_name_owner)
-		*out_name_owner = name_owner;
-	else
-		g_free (name_owner);
+	NM_SET_OUT (out_name_owner, g_steal_pointer (&name_owner));
 }
 
 static void
@@ -377,20 +467,16 @@ _dbus_on_name_owner_notify_cb (GObject    *object,
                                gpointer    user_data)
 {
 	NMAuthManager *self = user_data;
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
-	char *name_owner;
+	gs_free char *name_owner = NULL;
 
-	g_return_if_fail (priv->proxy == (void *) object);
+	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);
 	}
-
-	g_free (name_owner);
 }
 
 static void
@@ -398,9 +484,8 @@ _dbus_on_changed_signal_cb (GDBusProxy *proxy,
                             gpointer    user_data)
 {
 	NMAuthManager *self = user_data;
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (priv->proxy == proxy);
+	nm_assert (NM_AUTH_MANAGER_GET_PRIVATE (self)->proxy == proxy);
 
 	_LOGD ("dbus signal: \"Changed\"");
 	_emit_changed_signal (self);
@@ -411,49 +496,39 @@ _dbus_new_proxy_cb (GObject *source_object,
                     GAsyncResult *res,
                     gpointer user_data)
 {
-	NMAuthManager **p_self = user_data;
-	NMAuthManager *self = NULL;
+	NMAuthManager *self;
 	NMAuthManagerPrivate *priv;
-	GError *error = NULL;
+	gs_free GError *error = NULL;
 	GDBusProxy *proxy;
-	CheckAuthData *data;
+	NMAuthManagerCallId *call_id;
 
 	proxy = g_dbus_proxy_new_for_bus_finish  (res, &error);
 
-	if (!*p_self) {
-		_LOGD ("_dbus_new_proxy_cb(): manager destroyed before callback finished. Abort");
-		g_clear_object (&proxy);
-		g_clear_error (&error);
-		g_free (p_self);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
-	}
-	self = *p_self;
-	g_object_remove_weak_pointer (G_OBJECT (self), (void **)p_self);
-	g_free (p_self);
 
+	self = user_data;
 	priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (priv->new_proxy_cancellable);
-	g_return_if_fail (!priv->proxy);
-
+	priv->proxy = proxy;
 	g_clear_object (&priv->new_proxy_cancellable);
 
-	priv->queued_calls = g_slist_reverse (priv->queued_calls);
-
-	priv->proxy = proxy;
 	if (!priv->proxy) {
-		_LOGE ("could not get polkit proxy: %s", error->message);
-		g_clear_error (&error);
-
-		while (priv->queued_calls) {
-			data = priv->queued_calls->data;
-			priv->queued_calls = g_slist_remove (priv->queued_calls, data);
-
-			_call_check_authorization_complete_with_error (data, "error creating DBUS 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),
@@ -464,17 +539,16 @@ _dbus_new_proxy_cb (GObject *source_object,
 
 	_log_name_owner (self, NULL);
 
-	while (priv->queued_calls) {
-		data = priv->queued_calls->data;
-		priv->queued_calls = g_slist_remove (priv->queued_calls, data);
-		_LOGD ("call[%u]: CheckAuthorization invoke now", data->call_id);
-		_call_check_authorization (data);
+	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);
 }
 
-#endif
-
 /*****************************************************************************/
 
 NMAuthManager *
@@ -485,23 +559,42 @@ nm_auth_manager_get ()
 	return singleton_instance;
 }
 
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+void
+nm_auth_manager_force_shutdown (NMAuthManager *self)
 {
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE ((NMAuthManager *) object);
+	NMAuthManagerPrivate *priv;
 
-	switch (prop_id) {
-	case PROP_POLKIT_ENABLED:
-		g_value_set_boolean (value, priv->polkit_enabled);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
+	g_return_if_fail (NM_IS_AUTH_MANAGER (self));
+
+	priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
+
+	/* while we have pending requests (NMAuthManagerCallId), the instance
+	 * is kept alive.
+	 *
+	 * Even if the caller cancells all pending call-ids, we still need to keep
+	 * a reference to self, in order to handle pending CancelCheckAuthorization
+	 * requests.
+	 *
+	 * To do a corrdinated shutdown, do the following:
+	 * - cancel all pending NMAuthManagerCallId requests.
+	 * - ensure everybody unrefs the NMAuthManager instance. If by that, the instance
+	 *   gets destroyed, the shutdown already completed successfully.
+	 * - Otherwise, the object is kept alive by pending CancelCheckAuthorization requests.
+	 *   wait a certain timeout (1 second) for all requests to complete (by watching
+	 *   for destruction of NMAuthManager).
+	 * - if that doesn't happen within timeout, issue nm_auth_manager_force_shutdown() and
+	 *   wait longer. After that, soon the instance should be destroyed and you
+	 *   did a successful shutdown.
+	 * - if the instance was still not destroyed within a short timeout, you leaked
+	 *   resources. You cannot properly shutdown.
+	 */
+
+	priv->shutting_down = TRUE;
+	nm_clear_g_cancellable (&priv->cancel_cancellable);
 }
 
+/*****************************************************************************/
+
 static void
 set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
@@ -523,6 +616,9 @@ set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *p
 static void
 nm_auth_manager_init (NMAuthManager *self)
 {
+	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
+
+	c_list_init (&priv->calls_lst_head);
 }
 
 static void
@@ -533,16 +629,10 @@ constructed (GObject *object)
 
 	G_OBJECT_CLASS (nm_auth_manager_parent_class)->constructed (object);
 
-#if WITH_POLKIT
 	_LOGD ("create auth-manager: polkit %s", priv->polkit_enabled ? "enabled" : "disabled");
 
 	if (priv->polkit_enabled) {
-		NMAuthManager **p_self;
-
 		priv->new_proxy_cancellable = g_cancellable_new ();
-		p_self = g_new (NMAuthManager *, 1);
-		*p_self = self;
-		g_object_add_weak_pointer (G_OBJECT (self), (void **) p_self);
 		g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 		                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
 		                          NULL,
@@ -551,14 +641,8 @@ constructed (GObject *object)
 		                          POLKIT_INTERFACE,
 		                          priv->new_proxy_cancellable,
 		                          _dbus_new_proxy_cb,
-		                          p_self);
+		                          self);
 	}
-#else
-	if (priv->polkit_enabled)
-		_LOGW ("create auth-manager: polkit disabled at compile time. All authentication requests will fail");
-	else
-		_LOGD ("create auth-manager: polkit disabled at compile time");
-#endif
 }
 
 NMAuthManager *
@@ -585,23 +669,21 @@ static void
 dispose (GObject *object)
 {
 	NMAuthManager* self = NM_AUTH_MANAGER (object);
-#if WITH_POLKIT
 	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
-#endif
 
 	_LOGD ("dispose");
 
-#if WITH_POLKIT
-	/* since we take a reference for each queued call, we don't expect to have any queued calls in dispose() */
-	g_assert (!priv->queued_calls);
+	nm_assert (c_list_is_empty (&priv->calls_lst_head));
+
+	priv->disposing = TRUE;
 
 	nm_clear_g_cancellable (&priv->new_proxy_cancellable);
+	nm_clear_g_cancellable (&priv->cancel_cancellable);
 
 	if (priv->proxy) {
 		g_signal_handlers_disconnect_by_data (priv->proxy, self);
 		g_clear_object (&priv->proxy);
 	}
-#endif
 
 	G_OBJECT_CLASS (nm_auth_manager_parent_class)->dispose (object);
 }
@@ -611,7 +693,6 @@ nm_auth_manager_class_init (NMAuthManagerClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 	object_class->constructed = constructed;
 	object_class->dispose = dispose;
@@ -619,7 +700,7 @@ nm_auth_manager_class_init (NMAuthManagerClass *klass)
 	obj_properties[PROP_POLKIT_ENABLED] =
 	     g_param_spec_boolean (NM_AUTH_MANAGER_POLKIT_ENABLED, "", "",
 	                           FALSE,
-	                           G_PARAM_READWRITE |
+	                           G_PARAM_WRITABLE |
 	                           G_PARAM_CONSTRUCT_ONLY |
 	                           G_PARAM_STATIC_STRINGS);
 
@@ -628,11 +709,7 @@ nm_auth_manager_class_init (NMAuthManagerClass *klass)
 	signals[CHANGED_SIGNAL] = g_signal_new (NM_AUTH_MANAGER_SIGNAL_CHANGED,
 	                                        NM_TYPE_AUTH_MANAGER,
 	                                        G_SIGNAL_RUN_LAST,
-	                                        0,                      /* class offset     */
-	                                        NULL,                   /* accumulator      */
-	                                        NULL,                   /* accumulator data */
+	                                        0, NULL, NULL,
 	                                        g_cclosure_marshal_VOID__VOID,
-	                                        G_TYPE_NONE,
-	                                        0);
+	                                        G_TYPE_NONE, 0);
 }
-
diff --git a/src/nm-auth-manager.h b/src/nm-auth-manager.h
index e66ef78c..fe7ee787 100644
--- a/src/nm-auth-manager.h
+++ b/src/nm-auth-manager.h
@@ -23,6 +23,31 @@
 
 #include "nm-auth-subject.h"
 
+/*****************************************************************************/
+
+typedef enum {
+	NM_AUTH_CALL_RESULT_UNKNOWN,
+	NM_AUTH_CALL_RESULT_YES,
+	NM_AUTH_CALL_RESULT_AUTH,
+	NM_AUTH_CALL_RESULT_NO,
+} NMAuthCallResult;
+
+static inline NMAuthCallResult
+nm_auth_call_result_eval (gboolean is_authorized,
+                          gboolean is_challenge,
+                          GError *error)
+{
+	if (error)
+		return NM_AUTH_CALL_RESULT_UNKNOWN;
+	if (is_authorized)
+		return NM_AUTH_CALL_RESULT_YES;
+	if (is_challenge)
+		return NM_AUTH_CALL_RESULT_AUTH;
+	return NM_AUTH_CALL_RESULT_NO;
+}
+
+/*****************************************************************************/
+
 #define NM_TYPE_AUTH_MANAGER            (nm_auth_manager_get_type ())
 #define NM_AUTH_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_AUTH_MANAGER, NMAuthManager))
 #define NM_AUTH_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_AUTH_MANAGER, NMAuthManagerClass))
@@ -42,23 +67,28 @@ GType nm_auth_manager_get_type (void);
 NMAuthManager *nm_auth_manager_setup (gboolean polkit_enabled);
 NMAuthManager *nm_auth_manager_get (void);
 
+void nm_auth_manager_force_shutdown (NMAuthManager *self);
+
 gboolean nm_auth_manager_get_polkit_enabled (NMAuthManager *self);
 
-#if WITH_POLKIT
-
-void nm_auth_manager_polkit_authority_check_authorization (NMAuthManager *self,
-                                                           NMAuthSubject *subject,
-                                                           const char *action_id,
-                                                           gboolean allow_user_interaction,
-                                                           GCancellable *cancellable,
-                                                           GAsyncReadyCallback callback,
-                                                           gpointer user_data);
-gboolean nm_auth_manager_polkit_authority_check_authorization_finish (NMAuthManager *self,
-                                                                      GAsyncResult *res,
-                                                                      gboolean *out_is_authorized,
-                                                                      gboolean *out_is_challenge,
-                                                                      GError **error);
-
-#endif
+/*****************************************************************************/
+
+typedef struct _NMAuthManagerCallId NMAuthManagerCallId;
+
+typedef void (*NMAuthManagerCheckAuthorizationCallback) (NMAuthManager *self,
+                                                         NMAuthManagerCallId *call_id,
+                                                         gboolean is_authorized,
+                                                         gboolean is_challenge,
+                                                         GError *error,
+                                                         gpointer user_data);
+
+NMAuthManagerCallId *nm_auth_manager_check_authorization (NMAuthManager *self,
+                                                          NMAuthSubject *subject,
+                                                          const char *action_id,
+                                                          gboolean allow_user_interaction,
+                                                          NMAuthManagerCheckAuthorizationCallback callback,
+                                                          gpointer user_data);
+
+void nm_auth_manager_check_authorization_cancel (NMAuthManagerCallId *call_id);
 
 #endif /* NM_AUTH_MANAGER_H */
diff --git a/src/nm-auth-subject.c b/src/nm-auth-subject.c
index 0f40ff7c..117a3815 100644
--- a/src/nm-auth-subject.c
+++ b/src/nm-auth-subject.c
@@ -33,8 +33,7 @@
 #include <string.h>
 #include <stdlib.h>
 
-#include "nm-bus-manager.h"
-#include "NetworkManagerUtils.h"
+#include "nm-dbus-manager.h"
 
 enum {
 	PROP_0,
@@ -93,17 +92,15 @@ nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len)
 		            (unsigned long long) priv->unix_process.start_time);
 		break;
 	case NM_AUTH_SUBJECT_TYPE_INTERNAL:
-		g_strlcat (buf, "internal", buf_len);
+		g_strlcpy (buf, "internal", buf_len);
 		break;
 	default:
-		g_strlcat (buf, "invalid", buf_len);
+		g_strlcpy (buf, "invalid", buf_len);
 		break;
 	}
 	return buf;
 }
 
-#if WITH_POLKIT
-
 /* returns a floating variant */
 GVariant *
 nm_auth_subject_unix_process_to_polkit_gvariant (NMAuthSubject *self)
@@ -125,8 +122,6 @@ nm_auth_subject_unix_process_to_polkit_gvariant (NMAuthSubject *self)
 	return ret;
 }
 
-#endif
-
 NMAuthSubjectType
 nm_auth_subject_get_subject_type (NMAuthSubject *subject)
 {
@@ -186,20 +181,20 @@ _new_unix_process (GDBusMethodInvocation *context,
 	g_return_val_if_fail (context || (connection && message), NULL);
 
 	if (context) {
-		success = nm_bus_manager_get_caller_info (nm_bus_manager_get (),
-		                                          context,
-		                                          &dbus_sender,
-		                                          &uid,
-		                                          &pid);
-	} else if (message) {
-		success = nm_bus_manager_get_caller_info_from_message (nm_bus_manager_get (),
-		                                                       connection,
-		                                                       message,
-		                                                       &dbus_sender,
-		                                                       &uid,
-		                                                       &pid);
-	} else
-		g_assert_not_reached ();
+		success = nm_dbus_manager_get_caller_info (nm_dbus_manager_get (),
+		                                           context,
+		                                           &dbus_sender,
+		                                           &uid,
+		                                           &pid);
+	} else {
+		nm_assert (message);
+		success = nm_dbus_manager_get_caller_info_from_message (nm_dbus_manager_get (),
+		                                                        connection,
+		                                                        message,
+		                                                        &dbus_sender,
+		                                                        &uid,
+		                                                        &pid);
+	}
 
 	if (!success)
 		return NULL;
diff --git a/src/nm-auth-subject.h b/src/nm-auth-subject.h
index a0b6d14b..a9921dbd 100644
--- a/src/nm-auth-subject.h
+++ b/src/nm-auth-subject.h
@@ -68,10 +68,6 @@ gulong nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject);
 
 const char *nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len);
 
-#if WITH_POLKIT
-
 GVariant *  nm_auth_subject_unix_process_to_polkit_gvariant (NMAuthSubject *self);
 
-#endif
-
 #endif /* __NETWORKMANAGER_AUTH_SUBJECT_H__ */
diff --git a/src/nm-auth-utils.c b/src/nm-auth-utils.c
index f1aff430..b41f6efa 100644
--- a/src/nm-auth-utils.c
+++ b/src/nm-auth-utils.c
@@ -24,48 +24,83 @@
 
 #include <string.h>
 
-#include "nm-utils/nm-hash-utils.h"
+#include "nm-utils/nm-c-list.h"
+
 #include "nm-setting-connection.h"
 #include "nm-auth-subject.h"
 #include "nm-auth-manager.h"
 #include "nm-session-monitor.h"
 
+/*****************************************************************************/
+
 struct NMAuthChain {
-	guint32 refcount;
-	GSList *calls;
-	GHashTable *data;
+	GHashTable *data_hash;
+
+	CList auth_call_lst_head;
 
 	GDBusMethodInvocation *context;
 	NMAuthSubject *subject;
-	GError *error;
-
-	guint idle_id;
-	gboolean done;
 
 	NMAuthChainResultFunc done_func;
 	gpointer user_data;
+
+	guint32 refcount;
+
+	bool done:1;
 };
 
 typedef struct {
+	CList auth_call_lst;
 	NMAuthChain *chain;
-	GCancellable *cancellable;
+	NMAuthManagerCallId *call_id;
 	char *permission;
-	guint call_idle_id;
 } AuthCall;
 
+/*****************************************************************************/
+
+static void
+_ASSERT_call (AuthCall *call)
+{
+	nm_assert (call);
+	nm_assert (call->chain);
+	nm_assert (nm_c_list_contains_entry (&call->chain->auth_call_lst_head, call, auth_call_lst));
+}
+
+/*****************************************************************************/
+
+static void
+auth_call_free (AuthCall *call)
+{
+	if (call->call_id)
+		nm_auth_manager_check_authorization_cancel (call->call_id);
+	c_list_unlink_stale (&call->auth_call_lst);
+	g_free (call->permission);
+	g_slice_free (AuthCall, call);
+}
+
+/*****************************************************************************/
+
 typedef struct {
+
+	/* must be the first field. */
+	const char *tag;
+
 	gpointer data;
 	GDestroyNotify destroy;
+	char tag_data[];
 } ChainData;
 
 static ChainData *
-chain_data_new (gpointer data, GDestroyNotify destroy)
+chain_data_new (const char *tag, gpointer data, GDestroyNotify destroy)
 {
 	ChainData *tmp;
+	gsize l = strlen (tag);
 
-	tmp = g_slice_new (ChainData);
+	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;
 }
 
@@ -76,69 +111,7 @@ chain_data_free (gpointer data)
 
 	if (tmp->destroy)
 		tmp->destroy (tmp->data);
-	memset (tmp, 0, sizeof (ChainData));
-	g_slice_free (ChainData, tmp);
-}
-
-static gboolean
-auth_chain_finish (gpointer user_data)
-{
-	NMAuthChain *self = user_data;
-
-	self->idle_id = 0;
-	self->done = TRUE;
-
-	/* Ensure we stay alive across the callback */
-	self->refcount++;
-	self->done_func (self, self->error, self->context, self->user_data);
-	nm_auth_chain_unref (self);
-	return FALSE;
-}
-
-/* Creates the NMAuthSubject automatically */
-NMAuthChain *
-nm_auth_chain_new_context (GDBusMethodInvocation *context,
-                           NMAuthChainResultFunc done_func,
-                           gpointer user_data)
-{
-	NMAuthSubject *subject;
-	NMAuthChain *chain;
-
-	g_return_val_if_fail (context != NULL, NULL);
-
-	subject = nm_auth_subject_new_unix_process_from_context (context);
-	if (!subject)
-		return NULL;
-
-	chain = nm_auth_chain_new_subject (subject,
-	                                   context,
-	                                   done_func,
-	                                   user_data);
-	g_object_unref (subject);
-	return chain;
-}
-
-/* Requires an NMAuthSubject */
-NMAuthChain *
-nm_auth_chain_new_subject (NMAuthSubject *subject,
-                           GDBusMethodInvocation *context,
-                           NMAuthChainResultFunc done_func,
-                           gpointer user_data)
-{
-	NMAuthChain *self;
-
-	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL);
-	g_return_val_if_fail (nm_auth_subject_is_unix_process (subject) || nm_auth_subject_is_internal (subject), NULL);
-
-	self = g_slice_new0 (NMAuthChain);
-	self->refcount = 1;
-	self->data = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, chain_data_free);
-	self->done_func = done_func;
-	self->user_data = user_data;
-	self->context = context ? g_object_ref (context) : NULL;
-	self->subject = g_object_ref (subject);
-
-	return self;
+	g_free (tmp);
 }
 
 static gpointer
@@ -146,15 +119,17 @@ _get_data (NMAuthChain *self, const char *tag)
 {
 	ChainData *tmp;
 
-	tmp = g_hash_table_lookup (self->data, tag);
+	if (!self->data_hash)
+		return NULL;
+	tmp = g_hash_table_lookup (self->data_hash, &tag);
 	return tmp ? tmp->data : NULL;
 }
 
 gpointer
 nm_auth_chain_get_data (NMAuthChain *self, const char *tag)
 {
-	g_return_val_if_fail (self != NULL, NULL);
-	g_return_val_if_fail (tag != NULL, NULL);
+	g_return_val_if_fail (self, NULL);
+	g_return_val_if_fail (tag, NULL);
 
 	return _get_data (self, tag);
 }
@@ -175,19 +150,22 @@ nm_auth_chain_steal_data (NMAuthChain *self, const char *tag)
 {
 	ChainData *tmp;
 	gpointer value = NULL;
-	void *orig_key;
-
-	g_return_val_if_fail (self != NULL, NULL);
-	g_return_val_if_fail (tag != NULL, NULL);
-
-	if (g_hash_table_lookup_extended (self->data, tag, &orig_key, (gpointer)&tmp)) {
-		g_hash_table_steal (self->data, tag);
-		value = tmp->data;
-		/* Make sure the destroy handler isn't called when freeing */
-		tmp->destroy = NULL;
-		chain_data_free (tmp);
-		g_free (orig_key);
-	}
+
+	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)
+		return NULL;
+
+	value = tmp->data;
+
+	/* Make sure the destroy handler isn't called when freeing */
+	tmp->destroy = NULL;
+	g_hash_table_remove (self->data_hash, tmp);
 	return value;
 }
 
@@ -197,165 +175,105 @@ nm_auth_chain_set_data (NMAuthChain *self,
                         gpointer data,
                         GDestroyNotify data_destroy)
 {
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (tag != NULL);
-
-	if (data == NULL)
-		g_hash_table_remove (self->data, tag);
-	else {
-		g_hash_table_insert (self->data,
-		                     g_strdup (tag),
-		                     chain_data_new (data, data_destroy));
+	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));
 	}
 }
 
-gulong
-nm_auth_chain_get_data_ulong (NMAuthChain *self, const char *tag)
-{
-	gulong *data;
+/*****************************************************************************/
 
-	g_return_val_if_fail (self != NULL, 0);
-	g_return_val_if_fail (tag != NULL, 0);
-
-	data = _get_data (self, tag);
-	return data ? *data : 0ul;
-}
-
-void
-nm_auth_chain_set_data_ulong (NMAuthChain *self,
-                              const char *tag,
-                              gulong data)
+NMAuthCallResult
+nm_auth_chain_get_result (NMAuthChain *self, const char *permission)
 {
-	gulong *ptr;
+	gpointer data;
 
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (tag != NULL);
+	g_return_val_if_fail (self, NM_AUTH_CALL_RESULT_UNKNOWN);
+	g_return_val_if_fail (permission, NM_AUTH_CALL_RESULT_UNKNOWN);
 
-	ptr = g_malloc (sizeof (*ptr));
-	*ptr = data;
-	nm_auth_chain_set_data (self, tag, ptr, g_free);
+	data = _get_data (self, permission);
+	return data ? GPOINTER_TO_UINT (data) : NM_AUTH_CALL_RESULT_UNKNOWN;
 }
 
 NMAuthSubject *
 nm_auth_chain_get_subject (NMAuthChain *self)
 {
-	g_return_val_if_fail (self != NULL, NULL);
+	g_return_val_if_fail (self, NULL);
 
 	return self->subject;
 }
 
-NMAuthCallResult
-nm_auth_chain_get_result (NMAuthChain *self, const char *permission)
-{
-	gpointer data;
-
-	g_return_val_if_fail (self != NULL, NM_AUTH_CALL_RESULT_UNKNOWN);
-	g_return_val_if_fail (permission != NULL, NM_AUTH_CALL_RESULT_UNKNOWN);
+/*****************************************************************************/
 
-	data = _get_data (self, permission);
-	return data ? GPOINTER_TO_UINT (data) : NM_AUTH_CALL_RESULT_UNKNOWN;
-}
-
-static AuthCall *
-auth_call_new (NMAuthChain *chain, const char *permission)
+static gboolean
+auth_chain_finish (NMAuthChain *self)
 {
-	AuthCall *call;
+	self->done = TRUE;
 
-	call = g_slice_new0 (AuthCall);
-	call->chain = chain;
-	call->permission = g_strdup (permission);
-	return call;
+	/* 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_free (AuthCall *call)
-{
-	g_free (call->permission);
-	g_clear_object (&call->cancellable);
-	g_slice_free (AuthCall, call);
-}
-
-static gboolean
 auth_call_complete (AuthCall *call)
 {
 	NMAuthChain *self;
 
-	g_return_val_if_fail (call, G_SOURCE_REMOVE);
+	_ASSERT_call (call);
 
 	self = call->chain;
 
-	g_return_val_if_fail (self, G_SOURCE_REMOVE);
-	g_return_val_if_fail (g_slist_find (self->calls, call), G_SOURCE_REMOVE);
-
-	self->calls = g_slist_remove (self->calls, call);
+	nm_assert (!self->done);
 
-	if (!self->calls) {
-		g_assert (!self->idle_id && !self->done);
-		self->idle_id = g_idle_add (auth_chain_finish, self);
-	}
 	auth_call_free (call);
-	return FALSE;
-}
 
-static void
-auth_call_cancel (gpointer user_data)
-{
-	AuthCall *call = user_data;
-
-	if (nm_clear_g_cancellable (&call->cancellable)) {
-		/* we don't free call immediately. Instead we cancel the async operation
-		 * and set cancellable to NULL. pk_call_cb() will check for this and
-		 * do the final cleanup. */
-	} else {
-		g_source_remove (call->call_idle_id);
-		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);
 	}
 }
 
-#if WITH_POLKIT
 static void
-pk_call_cb (GObject *object, GAsyncResult *result, gpointer user_data)
+pk_call_cb (NMAuthManager *auth_manager,
+            NMAuthManagerCallId *call_id,
+            gboolean is_authorized,
+            gboolean is_challenge,
+            GError *error,
+            gpointer user_data)
 {
-	AuthCall *call = user_data;
-	GError *error = NULL;
-	gboolean is_authorized = FALSE, is_challenge = FALSE;
-	guint call_result = NM_AUTH_CALL_RESULT_UNKNOWN;
-
-	nm_auth_manager_polkit_authority_check_authorization_finish (NM_AUTH_MANAGER (object),
-	                                                             result,
-	                                                             &is_authorized,
-	                                                             &is_challenge,
-	                                                             &error);
-
-	/* If the call is already canceled do nothing */
-	if (!call->cancellable) {
-		nm_log_dbg (LOGD_CORE, "callback already cancelled");
-		g_clear_error (&error);
-		auth_call_free (call);
+	AuthCall *call;
+	NMAuthCallResult call_result;
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
-	}
 
-	if (error) {
-		/* Don't ruin the chain. Just leave the result unknown. */
-		nm_log_warn (LOGD_CORE, "error requesting auth for %s: %s",
-		             call->permission, error->message);
-		g_clear_error (&error);
-	} else {
-		if (is_authorized) {
-			/* Caller has the permission */
-			call_result = NM_AUTH_CALL_RESULT_YES;
-		} else if (is_challenge) {
-			/* Caller could authenticate to get the permission */
-			call_result = NM_AUTH_CALL_RESULT_AUTH;
-		} else
-			call_result = NM_AUTH_CALL_RESULT_NO;
-	}
+	call = user_data;
+
+	nm_assert (call->call_id == call_id);
+
+	call->call_id = NULL;
+
+	call_result = nm_auth_call_result_eval (is_authorized, is_challenge, error);
 
 	nm_auth_chain_set_data (call->chain, call->permission, GUINT_TO_POINTER (call_result), NULL);
 
 	auth_call_complete (call);
 }
-#endif
 
 void
 nm_auth_chain_add_call (NMAuthChain *self,
@@ -365,81 +283,109 @@ nm_auth_chain_add_call (NMAuthChain *self,
 	AuthCall *call;
 	NMAuthManager *auth_manager = nm_auth_manager_get ();
 
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (permission && *permission);
+	g_return_if_fail (self);
 	g_return_if_fail (self->subject);
+	g_return_if_fail (!self->done);
+	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));
-	g_return_if_fail (!self->idle_id && !self->done);
 
-	call = auth_call_new (self, permission);
-	self->calls = g_slist_append (self->calls, call);
+	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,
+	                                                     self->subject,
+	                                                     permission,
+	                                                     allow_interaction,
+	                                                     pk_call_cb,
+	                                                     call);
+}
 
-	if (   nm_auth_subject_is_internal (self->subject)
-	    || nm_auth_subject_get_unix_process_uid (self->subject) == 0
-	    || !nm_auth_manager_get_polkit_enabled (auth_manager)) {
-		/* Root user or non-polkit always gets the permission */
-		nm_auth_chain_set_data (self, permission, GUINT_TO_POINTER (NM_AUTH_CALL_RESULT_YES), NULL);
-		call->call_idle_id = g_idle_add ((GSourceFunc) auth_call_complete, call);
-	} else {
-		/* Non-root always gets authenticated when using polkit */
-#if WITH_POLKIT
-		call->cancellable = g_cancellable_new ();
-		nm_auth_manager_polkit_authority_check_authorization (auth_manager,
-		                                                      self->subject,
-		                                                      permission,
-		                                                      allow_interaction,
-		                                                      call->cancellable,
-		                                                      pk_call_cb,
-		                                                      call);
-#else
-		if (!call->chain->error) {
-			call->chain->error = g_error_new_literal (NM_MANAGER_ERROR,
-			                                          NM_MANAGER_ERROR_FAILED,
-			                                          "Polkit support is disabled at compile time");
-		}
-		call->call_idle_id = g_idle_add ((GSourceFunc) auth_call_complete, call);
-#endif
-	}
+/*****************************************************************************/
+
+/* Creates the NMAuthSubject automatically */
+NMAuthChain *
+nm_auth_chain_new_context (GDBusMethodInvocation *context,
+                           NMAuthChainResultFunc done_func,
+                           gpointer user_data)
+{
+	NMAuthSubject *subject;
+	NMAuthChain *chain;
+
+	g_return_val_if_fail (context, NULL);
+
+	subject = nm_auth_subject_new_unix_process_from_context (context);
+	if (!subject)
+		return NULL;
+
+	chain = nm_auth_chain_new_subject (subject,
+	                                   context,
+	                                   done_func,
+	                                   user_data);
+	g_object_unref (subject);
+	return chain;
+}
+
+/* Requires an NMAuthSubject */
+NMAuthChain *
+nm_auth_chain_new_subject (NMAuthSubject *subject,
+                           GDBusMethodInvocation *context,
+                           NMAuthChainResultFunc done_func,
+                           gpointer user_data)
+{
+	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);
+	return self;
 }
 
 /**
- * nm_auth_chain_unref:
+ * nm_auth_chain_destroy:
  * @self: the auth-chain
  *
- * Unrefs the auth-chain. By unrefing the auth-chain, you also cancel
+ * 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 you call nm_auth_chain_unref() from inside
+ * 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.
+ *
+ * Note that you might only destroy an auth-chain exactly once, and never
+ * after the callback was handled.
  */
 void
-nm_auth_chain_unref (NMAuthChain *self)
+nm_auth_chain_destroy (NMAuthChain *self)
 {
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (self->refcount > 0);
-
-	self->refcount--;
-	if (self->refcount > 0)
-		return;
+	AuthCall *call;
 
-	if (self->idle_id)
-		g_source_remove (self->idle_id);
+	g_return_if_fail (self);
+	g_return_if_fail (NM_IN_SET (self->refcount, 1, 2));
 
-	g_object_unref (self->subject);
+	if (--self->refcount > 0)
+		return;
 
-	if (self->context)
-		g_object_unref (self->context);
+	nm_clear_g_object (&self->subject);
+	nm_clear_g_object (&self->context);
 
-	g_slist_free_full (self->calls, auth_call_cancel);
+	while ((call = c_list_first_entry (&self->auth_call_lst_head, AuthCall, auth_call_lst)))
+		auth_call_free (call);
 
-	g_clear_error (&self->error);
-	g_hash_table_destroy (self->data);
+	nm_clear_pointer (&self->data_hash, g_hash_table_destroy);
 
-	memset (self, 0, sizeof (NMAuthChain));
 	g_slice_free (NMAuthChain, self);
 }
 
-/************ utils **************/
+/******************************************************************************
+ * utils
+ *****************************************************************************/
 
 gboolean
 nm_auth_is_subject_in_acl (NMConnection *connection,
@@ -450,7 +396,7 @@ nm_auth_is_subject_in_acl (NMConnection *connection,
 	const char *user = NULL;
 	gulong uid;
 
-	g_return_val_if_fail (connection != NULL, FALSE);
+	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);
 
@@ -464,8 +410,8 @@ nm_auth_is_subject_in_acl (NMConnection *connection,
 		return TRUE;
 
 	if (!nm_session_monitor_uid_to_user (uid, &user)) {
-		if (out_error_desc)
-			*out_error_desc = g_strdup_printf ("Could not determine username for uid %lu", uid);
+		NM_SET_OUT (out_error_desc,
+		            g_strdup_printf ("Could not determine username for uid %lu", uid));
 		return FALSE;
 	}
 
@@ -479,12 +425,31 @@ nm_auth_is_subject_in_acl (NMConnection *connection,
 
 	/* Match the username returned by the session check to a user in the ACL */
 	if (!nm_setting_connection_permissions_user_allowed (s_con, user)) {
-		if (out_error_desc)
-			*out_error_desc = g_strdup_printf ("uid %lu has no permission to perform this operation", uid);
+		NM_SET_OUT (out_error_desc,
+		            g_strdup_printf ("uid %lu has no permission to perform this operation", uid));
 		return FALSE;
 	}
 
 	return TRUE;
 }
 
+gboolean
+nm_auth_is_subject_in_acl_set_error (NMConnection *connection,
+                                     NMAuthSubject *subject,
+                                     GQuark err_domain,
+                                     int err_code,
+                                     GError **error)
+{
+	char *error_desc = NULL;
+
+	nm_assert (!error || !*error);
+
+	if (nm_auth_is_subject_in_acl (connection,
+	                               subject,
+	                               error ? &error_desc : NULL))
+		return TRUE;
 
+	g_set_error_literal (error, err_domain, err_code, error_desc);
+	g_free (error_desc);
+	return FALSE;
+}
diff --git a/src/nm-auth-utils.h b/src/nm-auth-utils.h
index 89ed79cc..5f9823b6 100644
--- a/src/nm-auth-utils.h
+++ b/src/nm-auth-utils.h
@@ -23,14 +23,9 @@
 
 #include "nm-connection.h"
 
-typedef struct NMAuthChain NMAuthChain;
+#include "nm-auth-manager.h"
 
-typedef enum {
-	NM_AUTH_CALL_RESULT_UNKNOWN,
-	NM_AUTH_CALL_RESULT_YES,
-	NM_AUTH_CALL_RESULT_AUTH,
-	NM_AUTH_CALL_RESULT_NO,
-} NMAuthCallResult;
+typedef struct NMAuthChain NMAuthChain;
 
 typedef void (*NMAuthChainResultFunc) (NMAuthChain *chain,
                                        GError *error,
@@ -55,12 +50,6 @@ void nm_auth_chain_set_data (NMAuthChain *chain,
                              gpointer data,
                              GDestroyNotify data_destroy);
 
-void nm_auth_chain_set_data_ulong (NMAuthChain *chain,
-                                   const char *tag,
-                                   gulong data);
-
-gulong nm_auth_chain_get_data_ulong (NMAuthChain *chain, const char *tag);
-
 NMAuthCallResult nm_auth_chain_get_result (NMAuthChain *chain,
                                            const char *permission);
 
@@ -68,14 +57,20 @@ void nm_auth_chain_add_call (NMAuthChain *chain,
                              const char *permission,
                              gboolean allow_interaction);
 
-void nm_auth_chain_unref (NMAuthChain *chain);
+void nm_auth_chain_destroy (NMAuthChain *chain);
+
+NMAuthSubject *nm_auth_chain_get_subject (NMAuthChain *self);
 
 /* Caller must free returned error description */
 gboolean nm_auth_is_subject_in_acl (NMConnection *connection,
                                     NMAuthSubject *subect,
                                     char **out_error_desc);
 
-NMAuthSubject *nm_auth_chain_get_subject (NMAuthChain *self);
+gboolean nm_auth_is_subject_in_acl_set_error (NMConnection *connection,
+                                              NMAuthSubject *subject,
+                                              GQuark err_domain,
+                                              int err_code,
+                                              GError **error);
 
 #endif /* __NETWORKMANAGER_MANAGER_AUTH_H__ */
 
diff --git a/src/nm-bus-manager.c b/src/nm-bus-manager.c
deleted file mode 100644
index f6b86e90..00000000
--- a/src/nm-bus-manager.c
+++ /dev/null
@@ -1,1024 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2006 - 2013 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-bus-manager.h"
-
-#include <unistd.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <errno.h>
-#include <string.h>
-
-#include "nm-dbus-interface.h"
-#include "nm-core-internal.h"
-#include "nm-dbus-compat.h"
-#include "nm-exported-object.h"
-#include "NetworkManagerUtils.h"
-
-/* The base path for our GDBusObjectManagerServers.  They do not contain
- * "NetworkManager" because GDBusObjectManagerServer requires that all
- * exported objects be *below* the base path, and eg the Manager object
- * is the base path already.
- */
-#define OBJECT_MANAGER_SERVER_BASE_PATH "/org/freedesktop"
-
-/*****************************************************************************/
-
-enum {
-	DBUS_CONNECTION_CHANGED = 0,
-	PRIVATE_CONNECTION_NEW,
-	PRIVATE_CONNECTION_DISCONNECTED,
-	NUMBER_OF_SIGNALS,
-};
-
-static guint signals[NUMBER_OF_SIGNALS];
-
-typedef struct {
-	GDBusConnection *connection;
-	GDBusObjectManagerServer *obj_manager;
-	gboolean started;
-
-	GSList *private_servers;
-
-	GDBusProxy *proxy;
-
-	gulong bus_closed_id;
-	guint reconnect_id;
-} NMBusManagerPrivate;
-
-struct _NMBusManager {
-	GObject parent;
-	NMBusManagerPrivate _priv;
-};
-
-struct _NMBusManagerClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE(NMBusManager, nm_bus_manager, G_TYPE_OBJECT)
-
-#define NM_BUS_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBusManager, NM_IS_BUS_MANAGER)
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN      LOGD_CORE
-#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bus-manager", __VA_ARGS__)
-
-/*****************************************************************************/
-
-static gboolean nm_bus_manager_init_bus (NMBusManager *self);
-static void nm_bus_manager_cleanup (NMBusManager *self);
-static void start_reconnection_timeout (NMBusManager *self);
-
-/*****************************************************************************/
-
-NM_DEFINE_SINGLETON_REGISTER (NMBusManager);
-
-NMBusManager *
-nm_bus_manager_get (void)
-{
-	if (G_UNLIKELY (!singleton_instance)) {
-		nm_bus_manager_setup (g_object_new (NM_TYPE_BUS_MANAGER, NULL));
-		if (!nm_bus_manager_init_bus (singleton_instance))
-			start_reconnection_timeout (singleton_instance);
-	}
-	return singleton_instance;
-}
-
-void
-nm_bus_manager_setup (NMBusManager *instance)
-{
-	static char already_setup = FALSE;
-
-	g_assert (NM_IS_BUS_MANAGER (instance));
-	g_assert (!already_setup);
-	g_assert (!singleton_instance);
-
-	already_setup = TRUE;
-	singleton_instance = instance;
-	nm_singleton_instance_register ();
-	_LOGD ("setup %s singleton (%p)", "NMBusManager", singleton_instance);
-}
-
-/*****************************************************************************/
-
-typedef struct {
-	const char *tag;
-	GQuark detail;
-	char *address;
-	GDBusServer *server;
-
-	/* With peer bus connections, we'll get a new connection for each
-	 * client.  For each connection we create an ObjectManager for
-	 * that connection to handle exporting our objects.  This table
-	 * maps GDBusObjectManager :: 'fake sender'.
-	 *
-	 * Note that even for connections that don't export any objects
-	 * we'll still create GDBusObjectManager since that's where we store
-	 * the pointer to the GDBusConnection.
-	 */
-	GHashTable *obj_managers;
-
-	NMBusManager *manager;
-} PrivateServer;
-
-typedef struct {
-	GDBusConnection *connection;
-	PrivateServer *server;
-	gboolean remote_peer_vanished;
-} CloseConnectionInfo;
-
-static gboolean
-close_connection_in_idle (gpointer user_data)
-{
-	CloseConnectionInfo *info = user_data;
-	PrivateServer *server = info->server;
-	GHashTableIter iter;
-	GDBusObjectManagerServer *manager;
-
-	/* Emit this for the manager */
-	g_signal_emit (server->manager,
-	               signals[PRIVATE_CONNECTION_DISCONNECTED],
-	               server->detail,
-	               info->connection);
-
-	/* FIXME: there's a bug (754730) in GLib for which the connection
-	 * is marked as closed when the remote peer vanishes but its
-	 * resources are not cleaned up.  Work around it by explicitly
-	 * closing the connection in that case. */
-	if (info->remote_peer_vanished)
-		g_dbus_connection_close (info->connection, NULL, NULL, NULL);
-
-	g_hash_table_iter_init (&iter, server->obj_managers);
-	while (g_hash_table_iter_next (&iter, (gpointer) &manager, NULL)) {
-		gs_unref_object GDBusConnection *connection = NULL;
-
-		connection = g_dbus_object_manager_server_get_connection (manager);
-		if (connection == info->connection) {
-			g_hash_table_iter_remove (&iter);
-			break;
-		}
-	}
-
-	g_object_unref (server->manager);
-	g_slice_free (CloseConnectionInfo, info);
-
-	return G_SOURCE_REMOVE;
-}
-
-static void
-private_server_closed_connection (GDBusConnection *conn,
-                                  gboolean remote_peer_vanished,
-                                  GError *error,
-                                  gpointer user_data)
-{
-	PrivateServer *s = user_data;
-	CloseConnectionInfo *info;
-
-	/* Clean up after the connection */
-	_LOGD ("(%s) closed connection %p on private socket", s->tag, conn);
-
-	info = g_slice_new0 (CloseConnectionInfo);
-	info->connection = conn;
-	info->server = s;
-	info->remote_peer_vanished = remote_peer_vanished;
-
-	g_object_ref (s->manager);
-
-	/* Delay the close of connection to ensure that D-Bus signals
-	 * are handled */
-	g_idle_add (close_connection_in_idle, info);
-}
-
-static gboolean
-private_server_new_connection (GDBusServer *server,
-                               GDBusConnection *conn,
-                               gpointer user_data)
-{
-	PrivateServer *s = user_data;
-	static guint32 counter = 0;
-	GDBusObjectManagerServer *manager;
-	char *sender;
-
-	g_signal_connect (conn, "closed", G_CALLBACK (private_server_closed_connection), s);
-
-	/* Fake a sender since private connections don't have one */
-	sender = g_strdup_printf ("x:y:%d", counter++);
-
-	manager = g_dbus_object_manager_server_new (OBJECT_MANAGER_SERVER_BASE_PATH);
-	g_dbus_object_manager_server_set_connection (manager, conn);
-	g_hash_table_insert (s->obj_managers, manager, sender);
-
-	_LOGD ("(%s) accepted connection %p on private socket", s->tag, conn);
-
-	/* Emit this for the manager.
-	 *
-	 * It is essential to do this from the "new-connection" signal handler, as
-	 * at that point no messages from the connection are yet processed
-	 * (which avoids races with registering objects). */
-	g_signal_emit (s->manager,
-	               signals[PRIVATE_CONNECTION_NEW],
-	               s->detail,
-	               conn,
-	               manager);
-	return TRUE;
-}
-
-static void
-private_server_manager_destroy (GDBusObjectManagerServer *manager)
-{
-	GDBusConnection *connection = g_dbus_object_manager_server_get_connection (manager);
-
-	if (!g_dbus_connection_is_closed (connection))
-		g_dbus_connection_close (connection, NULL, NULL, NULL);
-	g_dbus_object_manager_server_set_connection (manager, NULL);
-	g_object_unref (manager);
-	g_object_unref (connection);
-}
-
-static gboolean
-private_server_authorize (GDBusAuthObserver *observer,
-                          GIOStream         *stream,
-                          GCredentials      *credentials,
-                          gpointer           user_data)
-{
-	return g_credentials_get_unix_user (credentials, NULL) == 0;
-}
-
-static PrivateServer *
-private_server_new (const char *path,
-                    const char *tag,
-                    NMBusManager *manager)
-{
-	PrivateServer *s;
-	GDBusAuthObserver *auth_observer;
-	GDBusServer *server;
-	GError *error = NULL;
-	char *address, *guid;
-
-	unlink (path);
-	address = g_strdup_printf ("unix:path=%s", path);
-
-	_LOGD ("(%s) creating private socket %s", tag, address);
-
-	guid = g_dbus_generate_guid ();
-	auth_observer = g_dbus_auth_observer_new ();
-	g_signal_connect (auth_observer, "authorize-authenticated-peer",
-	                  G_CALLBACK (private_server_authorize), NULL);
-	server = g_dbus_server_new_sync (address,
-	                                 G_DBUS_SERVER_FLAGS_NONE,
-	                                 guid,
-	                                 auth_observer,
-	                                 NULL, &error);
-	g_free (guid);
-	g_object_unref (auth_observer);
-
-	if (!server) {
-		_LOGW ("(%s) failed to set up private socket %s: %s",
-		       tag, address, error->message);
-		g_error_free (error);
-		g_free (address);
-		return NULL;
-	}
-
-	s = g_malloc0 (sizeof (*s));
-	s->address = address;
-	s->server = server;
-	g_signal_connect (server, "new-connection",
-	                  G_CALLBACK (private_server_new_connection), s);
-
-	s->obj_managers = g_hash_table_new_full (g_direct_hash, g_direct_equal,
-	                                         (GDestroyNotify) private_server_manager_destroy,
-	                                         g_free);
-	s->manager = manager;
-	s->detail = g_quark_from_string (tag);
-	s->tag = g_quark_to_string (s->detail);
-
-	g_dbus_server_start (server);
-
-	return s;
-}
-
-static void
-private_server_free (gpointer ptr)
-{
-	PrivateServer *s = ptr;
-
-	unlink (s->address);
-	g_free (s->address);
-	g_hash_table_destroy (s->obj_managers);
-
-	g_dbus_server_stop (s->server);
-	g_object_unref (s->server);
-
-	memset (s, 0, sizeof (*s));
-	g_free (s);
-}
-
-void
-nm_bus_manager_private_server_register (NMBusManager *self,
-                                        const char *path,
-                                        const char *tag)
-{
-	NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-	PrivateServer *s;
-	GSList *iter;
-
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (path != NULL);
-	g_return_if_fail (tag != NULL);
-
-	/* Only one instance per tag; but don't warn */
-	for (iter = priv->private_servers; iter; iter = g_slist_next (iter)) {
-		s = iter->data;
-		if (g_strcmp0 (tag, s->tag) == 0)
-			return;
-	}
-
-	s = private_server_new (path, tag, self);
-	if (s)
-		priv->private_servers = g_slist_append (priv->private_servers, s);
-}
-
-static const char *
-private_server_get_connection_owner (PrivateServer *s, GDBusConnection *connection)
-{
-	GHashTableIter iter;
-	GDBusObjectManagerServer *manager;
-	const char *owner;
-
-	g_return_val_if_fail (s != NULL, NULL);
-	g_return_val_if_fail (connection != NULL, NULL);
-
-	g_hash_table_iter_init (&iter, s->obj_managers);
-	while (g_hash_table_iter_next (&iter, (gpointer) &manager, (gpointer) &owner)) {
-		gs_unref_object GDBusConnection *c = NULL;
-
-		c = g_dbus_object_manager_server_get_connection (manager);
-		if (c == connection)
-			return owner;
-	}
-	return NULL;
-}
-
-static GDBusConnection *
-private_server_get_connection_by_owner (PrivateServer *s, const char *owner)
-{
-	GHashTableIter iter;
-	GDBusObjectManagerServer *manager;
-	const char *priv_sender;
-
-	g_hash_table_iter_init (&iter, s->obj_managers);
-	while (g_hash_table_iter_next (&iter, (gpointer) &manager, (gpointer) &priv_sender)) {
-		if (g_strcmp0 (owner, priv_sender) == 0)
-			return g_dbus_object_manager_server_get_connection (manager);
-	}
-	return NULL;
-}
-
-/*****************************************************************************/
-
-static gboolean
-_bus_get_unix_pid (NMBusManager *self,
-                   const char *sender,
-                   gulong *out_pid,
-                   GError **error)
-{
-	guint32 unix_pid = G_MAXUINT32;
-	gs_unref_variant GVariant *ret = NULL;
-
-	ret = _nm_dbus_proxy_call_sync (NM_BUS_MANAGER_GET_PRIVATE (self)->proxy,
-	                                "GetConnectionUnixProcessID",
-	                                g_variant_new ("(s)", sender),
-	                                G_VARIANT_TYPE ("(u)"),
-	                                G_DBUS_CALL_FLAGS_NONE, 2000,
-	                                NULL, error);
-	if (!ret)
-		return FALSE;
-
-	g_variant_get (ret, "(u)", &unix_pid);
-
-	*out_pid = (gulong) unix_pid;
-	return TRUE;
-}
-
-static gboolean
-_bus_get_unix_user (NMBusManager *self,
-                    const char *sender,
-                    gulong *out_user,
-                    GError **error)
-{
-	guint32 unix_uid = G_MAXUINT32;
-	gs_unref_variant GVariant *ret = NULL;
-
-	ret = _nm_dbus_proxy_call_sync (NM_BUS_MANAGER_GET_PRIVATE (self)->proxy,
-	                                "GetConnectionUnixUser",
-	                                g_variant_new ("(s)", sender),
-	                                G_VARIANT_TYPE ("(u)"),
-	                                G_DBUS_CALL_FLAGS_NONE, 2000,
-	                                NULL, error);
-	if (!ret)
-		return FALSE;
-
-	g_variant_get (ret, "(u)", &unix_uid);
-
-	*out_user = (gulong) unix_uid;
-	return TRUE;
-}
-
-/**
- * _get_caller_info():
- *
- * Given a GDBus method invocation, or a GDBusConnection + GDBusMessage,
- * return the sender and the UID of the sender.
- */
-static gboolean
-_get_caller_info (NMBusManager *self,
-                  GDBusMethodInvocation *context,
-                  GDBusConnection *connection,
-                  GDBusMessage *message,
-                  char **out_sender,
-                  gulong *out_uid,
-                  gulong *out_pid)
-{
-	NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-	const char *sender;
-	GSList *iter;
-
-	if (context) {
-		connection = g_dbus_method_invocation_get_connection (context);
-
-		/* only bus connections will have a sender */
-		sender = g_dbus_method_invocation_get_sender (context);
-	} else {
-		g_assert (message);
-		sender = g_dbus_message_get_sender (message);
-	}
-	g_assert (connection);
-
-	if (!sender) {
-		/* Might be a private connection, for which we fake a sender */
-		for (iter = priv->private_servers; iter; iter = g_slist_next (iter)) {
-			PrivateServer *s = iter->data;
-
-			sender = private_server_get_connection_owner (s, connection);
-			if (sender) {
-				if (out_uid)
-					*out_uid = 0;
-				if (out_sender)
-					*out_sender = g_strdup (sender);
-				if (out_pid) {
-					GCredentials *creds;
-
-					creds = g_dbus_connection_get_peer_credentials (connection);
-					if (creds) {
-						pid_t pid;
-
-						pid = g_credentials_get_unix_pid (creds, NULL);
-						if (pid == -1)
-							*out_pid = G_MAXULONG;
-						else
-							*out_pid = pid;
-					} else
-						*out_pid = G_MAXULONG;
-				}
-				return TRUE;
-			}
-		}
-		return FALSE;
-	}
-
-	/* Bus connections always have a sender */
-	g_assert (sender);
-	if (out_uid) {
-		if (!_bus_get_unix_user (self, sender, out_uid, NULL)) {
-			*out_uid = G_MAXULONG;
-			return FALSE;
-		}
-	}
-
-	if (out_pid) {
-		if (!_bus_get_unix_pid (self, sender, out_pid, NULL)) {
-			*out_pid = G_MAXULONG;
-			return FALSE;
-		}
-	}
-
-	if (out_sender)
-		*out_sender = g_strdup (sender);
-
-	return TRUE;
-}
-
-gboolean
-nm_bus_manager_get_caller_info (NMBusManager *self,
-                                GDBusMethodInvocation *context,
-                                char **out_sender,
-                                gulong *out_uid,
-                                gulong *out_pid)
-{
-	return _get_caller_info (self, context, NULL, NULL, out_sender, out_uid, out_pid);
-}
-
-gboolean
-nm_bus_manager_get_caller_info_from_message (NMBusManager *self,
-                                             GDBusConnection *connection,
-                                             GDBusMessage *message,
-                                             char **out_sender,
-                                             gulong *out_uid,
-                                             gulong *out_pid)
-{
-	return _get_caller_info (self, NULL, connection, message, out_sender, out_uid, out_pid);
-}
-
-/**
- * nm_bus_manager_ensure_uid:
- *
- * @self: bus manager instance
- * @context: D-Bus method invocation
- * @uid: a user-id
- * @error_domain: error domain to return on failure
- * @error_code: error code to return on failure
- *
- * Retrieves the uid of the D-Bus method caller and
- * checks that it matches @uid, unless @uid is G_MAXULONG.
- * In case of failure the function returns FALSE and finishes
- * handling the D-Bus method with an error.
- *
- * Returns: %TRUE if the check succeeded, %FALSE otherwise
- */
-gboolean
-nm_bus_manager_ensure_uid (NMBusManager          *self,
-                           GDBusMethodInvocation *context,
-                           gulong uid,
-                           GQuark error_domain,
-                           int error_code)
-{
-	gulong caller_uid;
-	GError *error = NULL;
-
-	g_return_val_if_fail (NM_IS_BUS_MANAGER (self), FALSE);
-	g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (context), FALSE);
-
-	if (!nm_bus_manager_get_caller_info (self, context, NULL, &caller_uid, NULL)) {
-		error = g_error_new_literal (error_domain,
-		                             error_code,
-		                             "Unable to determine request UID.");
-		g_dbus_method_invocation_take_error (context, error);
-		return FALSE;
-	}
-
-	if (uid != G_MAXULONG && caller_uid != uid) {
-		error = g_error_new_literal (error_domain,
-		                             error_code,
-		                             "Permission denied");
-		g_dbus_method_invocation_take_error (context, error);
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
-gboolean
-nm_bus_manager_get_unix_user (NMBusManager *self,
-                              const char *sender,
-                              gulong *out_uid)
-{
-	NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-	GSList *iter;
-	GError *error = NULL;
-
-	g_return_val_if_fail (sender != NULL, FALSE);
-	g_return_val_if_fail (out_uid != NULL, FALSE);
-
-	/* Check if it's a private connection sender, which we fake */
-	for (iter = priv->private_servers; iter; iter = iter->next) {
-		gs_unref_object GDBusConnection *connection = NULL;
-
-		connection = private_server_get_connection_by_owner (iter->data, sender);
-		if (connection) {
-			*out_uid = 0;
-			return TRUE;
-		}
-	}
-
-	/* Otherwise, a bus connection */
-	if (!_bus_get_unix_user (self, sender, out_uid, &error)) {
-		_LOGW ("failed to get unix user for dbus sender '%s': %s",
-		       sender, error->message);
-		g_error_free (error);
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
-/*****************************************************************************/
-
-/* Only cleanup a specific dbus connection, not all our private data */
-static void
-nm_bus_manager_cleanup (NMBusManager *self)
-{
-	NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-
-	g_clear_object (&priv->proxy);
-
-	if (priv->connection) {
-		g_signal_handler_disconnect (priv->connection, priv->bus_closed_id);
-		priv->bus_closed_id = 0;
-		g_clear_object (&priv->connection);
-	}
-
-	g_dbus_object_manager_server_set_connection (priv->obj_manager, NULL);
-	priv->started = FALSE;
-}
-
-static gboolean
-nm_bus_manager_reconnect (gpointer user_data)
-{
-	NMBusManager *self = NM_BUS_MANAGER (user_data);
-	NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-
-	g_assert (self != NULL);
-
-	if (nm_bus_manager_init_bus (self)) {
-		if (nm_bus_manager_start_service (self)) {
-			_LOGI ("reconnected to the system bus");
-			g_signal_emit (self, signals[DBUS_CONNECTION_CHANGED],
-			               0, priv->connection);
-			priv->reconnect_id = 0;
-			return FALSE;
-		}
-	}
-
-	/* Try again */
-	nm_bus_manager_cleanup (self);
-	return TRUE;
-}
-
-static void
-start_reconnection_timeout (NMBusManager *self)
-{
-	NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-
-	if (priv->reconnect_id)
-		g_source_remove (priv->reconnect_id);
-
-	/* Schedule timeout for reconnection attempts */
-	priv->reconnect_id = g_timeout_add_seconds (3, nm_bus_manager_reconnect, self);
-}
-
-static void
-closed_cb (GDBusConnection *connection,
-           gboolean remote_peer_vanished,
-           GError *error,
-           gpointer user_data)
-{
-	NMBusManager *self = NM_BUS_MANAGER (user_data);
-
-	/* Clean up existing connection */
-	_LOGW ("disconnected by the system bus");
-
-	nm_bus_manager_cleanup (self);
-
-	g_signal_emit (G_OBJECT (self), signals[DBUS_CONNECTION_CHANGED], 0, NULL);
-
-	start_reconnection_timeout (self);
-}
-
-static gboolean
-nm_bus_manager_init_bus (NMBusManager *self)
-{
-	NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-	GError *error = NULL;
-
-	if (priv->connection) {
-		_LOGW ("DBus Manager already has a valid connection");
-		return FALSE;
-	}
-
-	priv->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
-	if (!priv->connection) {
-		/* Log with 'info' severity; there won't be a bus daemon in minimal
-		 * environments (eg, initrd) where we only want to use the private
-		 * socket.
-		 */
-		_LOGI ("could not connect to the system bus (%s); only the "
-		       "private D-Bus socket will be available",
-		       error->message);
-		g_error_free (error);
-		return FALSE;
-	}
-
-	g_dbus_connection_set_exit_on_close (priv->connection, FALSE);
-	priv->bus_closed_id = g_signal_connect (priv->connection, "closed",
-	                                        G_CALLBACK (closed_cb), self);
-
-	priv->proxy = g_dbus_proxy_new_sync (priv->connection,
-	                                     G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
-	                                         G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
-	                                     NULL,
-	                                     DBUS_SERVICE_DBUS,
-	                                     DBUS_PATH_DBUS,
-	                                     DBUS_INTERFACE_DBUS,
-	                                     NULL, &error);
-	if (!priv->proxy) {
-		g_clear_object (&priv->connection);
-		_LOGW ("could not create org.freedesktop.DBus proxy (%s); only the "
-		       "private D-Bus socket will be available",
-		       error->message);
-		g_error_free (error);
-		return FALSE;
-	}
-
-	g_dbus_object_manager_server_set_connection (priv->obj_manager, priv->connection);
-	return TRUE;
-}
-
-/* Register our service on the bus; shouldn't be called until
- * all necessary message handlers have been registered, because
- * when we register on the bus, clients may start to call.
- */
-gboolean
-nm_bus_manager_start_service (NMBusManager *self)
-{
-	NMBusManagerPrivate *priv;
-	gs_unref_variant GVariant *ret = NULL;
-	int result;
-	GError *err = NULL;
-
-	g_return_val_if_fail (NM_IS_BUS_MANAGER (self), FALSE);
-
-	priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-
-	if (priv->started) {
-		_LOGE ("service has already started");
-		return FALSE;
-	}
-
-	/* Pointless to request a name when we aren't connected to the bus */
-	if (!priv->proxy)
-		return FALSE;
-
-	ret = _nm_dbus_proxy_call_sync (priv->proxy,
-	                                "RequestName",
-	                                g_variant_new ("(su)",
-	                                               NM_DBUS_SERVICE,
-	                                               DBUS_NAME_FLAG_DO_NOT_QUEUE),
-	                                G_VARIANT_TYPE ("(u)"),
-	                                G_DBUS_CALL_FLAGS_NONE, -1,
-	                                NULL, &err);
-	if (!ret) {
-		_LOGE ("could not acquire the NetworkManager service: '%s'", err->message);
-		g_error_free (err);
-		return FALSE;
-	}
-
-	g_variant_get (ret, "(u)", &result);
-
-	if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
-		_LOGE ("could not acquire the NetworkManager service as it is already taken");
-		return FALSE;
-	}
-
-	priv->started = TRUE;
-	return priv->started;
-}
-
-GDBusConnection *
-nm_bus_manager_get_connection (NMBusManager *self)
-{
-	g_return_val_if_fail (NM_IS_BUS_MANAGER (self), NULL);
-
-	return NM_BUS_MANAGER_GET_PRIVATE (self)->connection;
-}
-
-void
-nm_bus_manager_register_object (NMBusManager *self,
-                                GDBusObjectSkeleton *object)
-{
-	NMBusManagerPrivate *priv;
-
-	g_return_if_fail (NM_IS_BUS_MANAGER (self));
-	g_return_if_fail (NM_IS_EXPORTED_OBJECT (object));
-
-	priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-
-#if NM_MORE_ASSERTS >= 1
-#if GLIB_CHECK_VERSION(2,34,0)
-	if (g_dbus_object_manager_server_is_exported (priv->obj_manager, object))
-		g_return_if_reached ();
-#endif
-#endif
-
-	g_dbus_object_manager_server_export (priv->obj_manager, object);
-}
-
-GDBusObjectSkeleton *
-nm_bus_manager_get_registered_object (NMBusManager *self,
-                                      const char *path)
-{
-	NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-
-	return G_DBUS_OBJECT_SKELETON (g_dbus_object_manager_get_object ((GDBusObjectManager *) priv->obj_manager, path));
-}
-
-void
-nm_bus_manager_unregister_object (NMBusManager *self,
-                                  GDBusObjectSkeleton *object)
-{
-	NMBusManagerPrivate *priv;
-	gs_free char *path = NULL;
-
-	g_return_if_fail (NM_IS_BUS_MANAGER (self));
-	g_return_if_fail (NM_IS_EXPORTED_OBJECT (object));
-
-	priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-
-#if NM_MORE_ASSERTS >= 1
-#if GLIB_CHECK_VERSION(2,34,0)
-	if (!g_dbus_object_manager_server_is_exported (priv->obj_manager, object))
-		g_return_if_reached ();
-#endif
-#endif
-
-	g_object_get (G_OBJECT (object), "g-object-path", &path, NULL);
-	g_return_if_fail (path != NULL);
-
-	g_dbus_object_manager_server_unexport (priv->obj_manager, path);
-}
-
-const char *
-nm_bus_manager_connection_get_private_name (NMBusManager *self,
-                                            GDBusConnection *connection)
-{
-	NMBusManagerPrivate *priv;
-	GSList *iter;
-	const char *owner;
-
-	g_return_val_if_fail (NM_IS_BUS_MANAGER (self), FALSE);
-	g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
-
-	if (g_dbus_connection_get_unique_name (connection)) {
-		/* Shortcut. The connection is not a private connection. */
-		return NULL;
-	}
-
-	priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-	for (iter = priv->private_servers; iter; iter = g_slist_next (iter)) {
-		PrivateServer *s = iter->data;
-
-		if ((owner = private_server_get_connection_owner (s, connection)))
-			return owner;
-	}
-	g_return_val_if_reached (NULL);
-}
-
-/**
- * nm_bus_manager_new_proxy:
- * @self: the #NMBusManager
- * @connection: the GDBusConnection for which this connection should be created
- * @proxy_type: the type of #GDBusProxy to create
- * @name: any name on the message bus
- * @path: name of the object instance to call methods on
- * @iface: name of the interface to call methods on
- *
- * Creates a new proxy (of type @proxy_type) for a name on a given bus.  Since
- * the process which called the D-Bus method could be coming from a private
- * connection or the system bus connection, different proxies must be created
- * for each case.  This function abstracts that.
- *
- * Returns: a #GDBusProxy capable of calling D-Bus methods of the calling process
- */
-GDBusProxy *
-nm_bus_manager_new_proxy (NMBusManager *self,
-                          GDBusConnection *connection,
-                          GType proxy_type,
-                          const char *name,
-                          const char *path,
-                          const char *iface)
-{
-	const char *owner;
-	GDBusProxy *proxy;
-	GError *error = NULL;
-
-	g_return_val_if_fail (g_type_is_a (proxy_type, G_TYPE_DBUS_PROXY), NULL);
-	g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
-
-	/* Might be a private connection, for which @name is fake */
-	owner = nm_bus_manager_connection_get_private_name (self, connection);
-	if (owner) {
-		g_return_val_if_fail (!g_strcmp0 (owner, name), NULL);
-		name = NULL;
-	}
-
-	proxy = g_initable_new (proxy_type, NULL, &error,
-	                        "g-connection", connection,
-	                        "g-flags", (G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
-	                                    G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
-	                        "g-name", name,
-	                        "g-object-path", path,
-	                        "g-interface-name", iface,
-	                        NULL);
-	if (!proxy) {
-		_LOGW ("could not create proxy for %s on connection %s: %s",
-		       iface, name, error->message);
-		g_error_free (error);
-	}
-	return proxy;
-}
-
-/*****************************************************************************/
-
-static void
-nm_bus_manager_init (NMBusManager *self)
-{
-	NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-
-	priv->obj_manager = g_dbus_object_manager_server_new (OBJECT_MANAGER_SERVER_BASE_PATH);
-}
-
-static void
-dispose (GObject *object)
-{
-	NMBusManager *self = NM_BUS_MANAGER (object);
-	NMBusManagerPrivate *priv = NM_BUS_MANAGER_GET_PRIVATE (self);
-	GList *exported, *iter;
-
-	g_slist_free_full (priv->private_servers, private_server_free);
-	priv->private_servers = NULL;
-
-	nm_bus_manager_cleanup (self);
-
-	if (priv->obj_manager) {
-		/* The ObjectManager owns the last reference to many exported
-		 * objects, and when that reference is dropped the objects unregister
-		 * themselves via nm_bus_manager_unregister_object().  By that time
-		 * priv->obj_manager is already NULL and that prints warnings.  Unregister
-		 * them before clearing the ObjectManager instead.
-		 */
-		exported = g_dbus_object_manager_get_objects ((GDBusObjectManager *) priv->obj_manager);
-		for (iter = exported; iter; iter = iter->next) {
-			nm_bus_manager_unregister_object (self, iter->data);
-			g_object_unref (iter->data);
-		}
-		g_list_free (exported);
-		g_clear_object (&priv->obj_manager);
-	}
-
-	nm_clear_g_source (&priv->reconnect_id);
-
-	G_OBJECT_CLASS (nm_bus_manager_parent_class)->dispose (object);
-}
-
-static void
-nm_bus_manager_class_init (NMBusManagerClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->dispose = dispose;
-
-	signals[DBUS_CONNECTION_CHANGED] =
-	    g_signal_new (NM_BUS_MANAGER_DBUS_CONNECTION_CHANGED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1, G_TYPE_POINTER);
-
-	signals[PRIVATE_CONNECTION_NEW] =
-	    g_signal_new (NM_BUS_MANAGER_PRIVATE_CONNECTION_NEW,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 2, G_TYPE_DBUS_CONNECTION, G_TYPE_DBUS_OBJECT_MANAGER_SERVER);
-
-	signals[PRIVATE_CONNECTION_DISCONNECTED] =
-	    g_signal_new (NM_BUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1, G_TYPE_POINTER);
-}
-
-
-
diff --git a/src/nm-bus-manager.h b/src/nm-bus-manager.h
deleted file mode 100644
index 153f7cdc..00000000
--- a/src/nm-bus-manager.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2006 - 2008 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- */
-
-#ifndef __NM_BUS_MANAGER_H__
-#define __NM_BUS_MANAGER_H__
-
-#define NM_TYPE_BUS_MANAGER (nm_bus_manager_get_type ())
-#define NM_BUS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NM_TYPE_BUS_MANAGER, NMBusManager))
-#define NM_BUS_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NM_TYPE_BUS_MANAGER, NMBusManagerClass))
-#define NM_IS_BUS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NM_TYPE_BUS_MANAGER))
-#define NM_IS_BUS_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NM_TYPE_BUS_MANAGER))
-#define NM_BUS_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NM_TYPE_BUS_MANAGER, NMBusManagerClass))
-
-#define NM_BUS_MANAGER_DBUS_CONNECTION_CHANGED          "dbus-connection-changed"
-#define NM_BUS_MANAGER_PRIVATE_CONNECTION_NEW           "private-connection-new"
-#define NM_BUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED  "private-connection-disconnected"
-
-typedef struct _NMBusManagerClass NMBusManagerClass;
-
-GType nm_bus_manager_get_type (void);
-
-NMBusManager * nm_bus_manager_get       (void);
-void           nm_bus_manager_setup     (NMBusManager *instance);
-
-gboolean nm_bus_manager_start_service    (NMBusManager *self);
-
-GDBusConnection * nm_bus_manager_get_connection (NMBusManager *self);
-
-gboolean nm_bus_manager_get_caller_info (NMBusManager *self,
-                                         GDBusMethodInvocation *context,
-                                         char **out_sender,
-                                         gulong *out_uid,
-                                         gulong *out_pid);
-
-gboolean nm_bus_manager_ensure_uid (NMBusManager          *self,
-                                    GDBusMethodInvocation *context,
-                                    gulong uid,
-                                    GQuark error_domain,
-                                    int error_code);
-
-const char *nm_bus_manager_connection_get_private_name (NMBusManager *self,
-                                                        GDBusConnection *connection);
-
-gboolean nm_bus_manager_get_unix_user (NMBusManager *self,
-                                       const char *sender,
-                                       gulong *out_uid);
-
-gboolean nm_bus_manager_get_caller_info_from_message (NMBusManager *self,
-                                                      GDBusConnection *connection,
-                                                      GDBusMessage *message,
-                                                      char **out_sender,
-                                                      gulong *out_uid,
-                                                      gulong *out_pid);
-
-void nm_bus_manager_register_object (NMBusManager *self,
-                                     GDBusObjectSkeleton *object);
-
-void nm_bus_manager_unregister_object (NMBusManager *self,
-                                       GDBusObjectSkeleton *object);
-
-GDBusObjectSkeleton *nm_bus_manager_get_registered_object (NMBusManager *self,
-                                                           const char *path);
-
-void nm_bus_manager_private_server_register (NMBusManager *self,
-                                             const char *path,
-                                             const char *tag);
-
-GDBusProxy *nm_bus_manager_new_proxy (NMBusManager *self,
-                                      GDBusConnection *connection,
-                                      GType proxy_type,
-                                      const char *name,
-                                      const char *path,
-                                      const char *iface);
-
-#endif /* __NM_BUS_MANAGER_H__ */
diff --git a/src/nm-checkpoint-manager.c b/src/nm-checkpoint-manager.c
index 6da220c4..8ba19db9 100644
--- a/src/nm-checkpoint-manager.c
+++ b/src/nm-checkpoint-manager.c
@@ -26,16 +26,16 @@
 #include "nm-connection.h"
 #include "nm-core-utils.h"
 #include "devices/nm-device.h"
-#include "nm-exported-object.h"
 #include "nm-manager.h"
 #include "nm-utils.h"
+#include "c-list/src/c-list.h"
 
 /*****************************************************************************/
 
 struct _NMCheckpointManager {
 	NMManager *_manager;
-	GHashTable *checkpoints;
-	guint rollback_timeout_id;
+	GParamSpec *property_spec;
+	CList checkpoints_lst_head;
 };
 
 #define GET_MANAGER(self) \
@@ -56,80 +56,67 @@ struct _NMCheckpointManager {
 
 /*****************************************************************************/
 
-static void update_rollback_timeout (NMCheckpointManager *self);
-
 static void
-checkpoint_destroy (gpointer checkpoint)
-{
-	nm_exported_object_unexport (NM_EXPORTED_OBJECT (checkpoint));
-	g_object_unref (G_OBJECT (checkpoint));
+notify_checkpoints (NMCheckpointManager *self) {
+	g_object_notify_by_pspec ((GObject *) GET_MANAGER (self),
+	                          self->property_spec);
 }
 
-static gboolean
-rollback_timeout_cb (NMCheckpointManager *self)
+static void
+destroy_checkpoint (NMCheckpointManager *self, NMCheckpoint *checkpoint, gboolean log_destroy)
 {
-	NMCheckpoint *checkpoint;
-	GHashTableIter iter;
-	GVariant *result;
-	gint64 ts, now;
-
-	now = nm_utils_get_monotonic_timestamp_ms ();
-
-	g_hash_table_iter_init (&iter, self->checkpoints);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &checkpoint)) {
-		ts = nm_checkpoint_get_rollback_ts (checkpoint);
-		if (ts && ts <= now) {
-			result = nm_checkpoint_rollback (checkpoint);
-			if (result)
-				g_variant_unref (result);
-			g_hash_table_iter_remove (&iter);
-		}
-	}
+	nm_assert (NM_IS_CHECKPOINT (checkpoint));
+	nm_assert (nm_dbus_object_is_exported (NM_DBUS_OBJECT (checkpoint)));
+	nm_assert (c_list_contains (&self->checkpoints_lst_head, &checkpoint->checkpoints_lst));
+
+	nm_checkpoint_set_timeout_callback (checkpoint, NULL, NULL);
+
+	c_list_unlink (&checkpoint->checkpoints_lst);
 
-	self->rollback_timeout_id = 0;
-	update_rollback_timeout (self);
+	if (log_destroy)
+		nm_checkpoint_log_destroy (checkpoint);
 
-	return G_SOURCE_REMOVE;
+	notify_checkpoints (self);
+
+	nm_dbus_object_unexport (NM_DBUS_OBJECT (checkpoint));
+	g_object_unref (checkpoint);
 }
 
-static void
-update_rollback_timeout (NMCheckpointManager *self)
+static GVariant *
+rollback_checkpoint (NMCheckpointManager *self, NMCheckpoint *checkpoint)
 {
-	NMCheckpoint *checkpoint;
-	GHashTableIter iter;
-	gint64 ts, delta, next = G_MAXINT64;
-
-	g_hash_table_iter_init (&iter, self->checkpoints);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &checkpoint)) {
-		ts = nm_checkpoint_get_rollback_ts (checkpoint);
-		if (ts && ts < next)
-			next = ts;
-	}
+	GVariant *result;
+	const CList *iter;
 
-	nm_clear_g_source (&self->rollback_timeout_id);
+	nm_assert (c_list_contains (&self->checkpoints_lst_head, &checkpoint->checkpoints_lst));
 
-	if (next != G_MAXINT64) {
-		delta = MAX (next - nm_utils_get_monotonic_timestamp_ms (), 0);
-		self->rollback_timeout_id = g_timeout_add (delta,
-		                                           (GSourceFunc) rollback_timeout_cb,
-		                                           self);
-		_LOGT ("update timeout: next check in %" G_GINT64_FORMAT " ms", delta);
+	/* we destroy first all overlapping checkpoints that are younger/newer. */
+	for (iter = checkpoint->checkpoints_lst.next;
+	     iter != &self->checkpoints_lst_head;
+	     ) {
+		NMCheckpoint *cp = c_list_entry (iter, NMCheckpoint, checkpoints_lst);
+
+		iter = iter->next;
+		if (nm_checkpoint_includes_devices_of (cp, checkpoint)) {
+			/* the younger checkpoint has overlapping devices and gets obsoleted.
+			 * Destroy it. */
+			destroy_checkpoint (self, cp, TRUE);
+		}
 	}
+
+	result = nm_checkpoint_rollback (checkpoint);
+	destroy_checkpoint (self, checkpoint, FALSE);
+	return result;
 }
 
-static NMCheckpoint *
-find_checkpoint_for_device (NMCheckpointManager *self, NMDevice *device)
+static void
+rollback_timeout_cb (NMCheckpoint *checkpoint,
+                     gpointer user_data)
 {
-	GHashTableIter iter;
-	NMCheckpoint *checkpoint;
+	NMCheckpointManager *self = user_data;
+	gs_unref_variant GVariant *result = NULL;
 
-	g_hash_table_iter_init (&iter, self->checkpoints);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &checkpoint)) {
-		if (nm_checkpoint_includes_device (checkpoint, device))
-			return checkpoint;
-	}
-
-	return NULL;
+	result = rollback_checkpoint (self, checkpoint);
 }
 
 NMCheckpoint *
@@ -141,152 +128,200 @@ nm_checkpoint_manager_create (NMCheckpointManager *self,
 {
 	NMManager *manager;
 	NMCheckpoint *checkpoint;
-	const char * const *path;
 	gs_unref_ptrarray GPtrArray *devices = NULL;
 	NMDevice *device;
-	const char *checkpoint_path;
-	gs_free const char **device_paths_free = NULL;
-	guint i;
 
 	g_return_val_if_fail (self, FALSE);
 	g_return_val_if_fail (!error || !*error, FALSE);
 	manager = GET_MANAGER (self);
 
+	devices = g_ptr_array_new ();
+
 	if (!device_paths || !device_paths[0]) {
-		const char *device_path;
-		const GSList *iter;
-		GPtrArray *paths;
-
-		paths = g_ptr_array_new ();
-		for (iter = nm_manager_get_devices (manager);
-		     iter;
-		     iter = g_slist_next (iter)) {
-			device = NM_DEVICE (iter->data);
+		const CList *tmp_lst;
+
+		nm_manager_for_each_device (manager, device, tmp_lst) {
+			/* FIXME: there is no strong reason to skip over unrealized devices.
+			 *        Also, NMCheckpoint anticipates to handle them (in parts). */
 			if (!nm_device_is_real (device))
 				continue;
-			device_path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (device));
-			if (device_path)
-				g_ptr_array_add (paths, (gpointer) device_path);
+			nm_assert (nm_dbus_object_get_path (NM_DBUS_OBJECT (device)));
+			g_ptr_array_add (devices, device);
 		}
-		g_ptr_array_add (paths, NULL);
-		device_paths_free = (const char **) g_ptr_array_free (paths, FALSE);
-		device_paths = (const char *const *) device_paths_free;
 	} else if (NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES)) {
 		g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS,
 		                     "the DISCONNECT_NEW_DEVICES flag can only be used with an empty device list");
 		return NULL;
+	} else {
+		for (; *device_paths; device_paths++) {
+			device = nm_manager_get_device_by_path (manager, *device_paths);
+			if (!device) {
+				g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+				             "device %s does not exist", *device_paths);
+				return NULL;
+			}
+			if (!nm_device_is_real (device)) {
+				g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+				             "device %s is not realized", *device_paths);
+				return NULL;
+			}
+			g_ptr_array_add (devices, device);
+		}
 	}
 
-	devices = g_ptr_array_new ();
-	for (path = device_paths; *path; path++) {
-		device = nm_manager_get_device_by_path (manager, *path);
-		if (!device) {
-			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
-			             "device %s does not exist", *path);
-			return NULL;
-		}
-		g_ptr_array_add (devices, device);
+	if (!devices->len) {
+		g_set_error_literal (error,
+		                     NM_MANAGER_ERROR,
+		                     NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+		                     "no device available");
+		return NULL;
 	}
 
-	if (!NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL)) {
-		for (i = 0; i < devices->len; i++) {
-			device = devices->pdata[i];
-			checkpoint = find_checkpoint_for_device (self, device);
-			if (checkpoint) {
+	if (NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL))
+		nm_checkpoint_manager_destroy_all (self);
+	else if (!NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_ALLOW_OVERLAPPING)) {
+		c_list_for_each_entry (checkpoint, &self->checkpoints_lst_head, checkpoints_lst) {
+			device = nm_checkpoint_includes_devices (checkpoint, (NMDevice *const*) devices->pdata, devices->len);
+			if (device) {
 				g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS,
 				             "device '%s' is already included in checkpoint %s",
 				             nm_device_get_iface (device),
-				             nm_exported_object_get_path (NM_EXPORTED_OBJECT (checkpoint)));
+				             nm_dbus_object_get_path (NM_DBUS_OBJECT (checkpoint)));
 				return NULL;
 			}
 		}
 	}
 
-	checkpoint = nm_checkpoint_new (manager, devices, rollback_timeout, flags, error);
-	if (!checkpoint)
-		return NULL;
-
-	if (NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DESTROY_ALL))
-		g_hash_table_remove_all (self->checkpoints);
-
-	nm_exported_object_export (NM_EXPORTED_OBJECT (checkpoint));
-	checkpoint_path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (checkpoint));
+	checkpoint = nm_checkpoint_new (manager, devices, rollback_timeout, flags);
 
-	if (!nm_g_hash_table_insert (self->checkpoints,
-	                             (gpointer) checkpoint_path,
-	                             checkpoint))
-		g_return_val_if_reached (NULL);
-
-	update_rollback_timeout (self);
+	nm_dbus_object_export (NM_DBUS_OBJECT (checkpoint));
 
+	nm_checkpoint_set_timeout_callback (checkpoint, rollback_timeout_cb, self);
+	c_list_link_tail (&self->checkpoints_lst_head, &checkpoint->checkpoints_lst);
+	notify_checkpoints (self);
 	return checkpoint;
 }
 
-gboolean
-nm_checkpoint_manager_destroy_all (NMCheckpointManager *self,
-                                   GError **error)
+void
+nm_checkpoint_manager_destroy_all (NMCheckpointManager *self)
 {
-	g_return_val_if_fail (self, FALSE);
+	NMCheckpoint *checkpoint;
 
-	g_hash_table_remove_all (self->checkpoints);
+	g_return_if_fail (self);
 
-	return TRUE;
+	while ((checkpoint = c_list_first_entry (&self->checkpoints_lst_head, NMCheckpoint, checkpoints_lst)))
+		destroy_checkpoint (self, checkpoint, TRUE);
 }
 
 gboolean
 nm_checkpoint_manager_destroy (NMCheckpointManager *self,
-                               const char *checkpoint_path,
+                               const char *path,
                                GError **error)
 {
-	gboolean ret;
+	NMCheckpoint *checkpoint;
 
 	g_return_val_if_fail (self, FALSE);
-	g_return_val_if_fail (checkpoint_path && checkpoint_path[0] == '/', FALSE);
+	g_return_val_if_fail (path && path[0] == '/', FALSE);
 	g_return_val_if_fail (!error || !*error, FALSE);
 
-	if (!nm_streq (checkpoint_path, "/")) {
-		ret = g_hash_table_remove (self->checkpoints, checkpoint_path);
-		if (!ret) {
-			g_set_error (error,
-			             NM_MANAGER_ERROR,
-			             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
-			             "checkpoint %s does not exist", checkpoint_path);
-		}
-		return ret;
-	} else
-		return nm_checkpoint_manager_destroy_all (self, error);
+	if (!nm_streq (path, "/")) {
+		nm_checkpoint_manager_destroy_all (self);
+		return TRUE;
+	}
+
+	checkpoint = nm_checkpoint_manager_lookup_by_path (self, path, error);
+	if (!checkpoint)
+		return FALSE;
+
+	destroy_checkpoint (self, checkpoint, TRUE);
+	return TRUE;
 }
 
 gboolean
 nm_checkpoint_manager_rollback (NMCheckpointManager *self,
-                                const char *checkpoint_path,
+                                const char *path,
                                 GVariant **results,
                                 GError **error)
 {
-	NMCheckpoint *cp;
+	NMCheckpoint *checkpoint;
 
 	g_return_val_if_fail (self, FALSE);
-	g_return_val_if_fail (checkpoint_path && checkpoint_path[0] == '/', FALSE);
+	g_return_val_if_fail (path && path[0] == '/', FALSE);
 	g_return_val_if_fail (results, FALSE);
 	g_return_val_if_fail (!error || !*error, FALSE);
 
-	cp = g_hash_table_lookup (self->checkpoints, checkpoint_path);
-	if (!cp) {
-		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
-		             "checkpoint %s does not exist", checkpoint_path);
+	checkpoint = nm_checkpoint_manager_lookup_by_path (self, path, error);
+	if (!checkpoint)
 		return FALSE;
+
+	*results = rollback_checkpoint (self, checkpoint);
+	return TRUE;
+}
+
+NMCheckpoint *
+nm_checkpoint_manager_lookup_by_path (NMCheckpointManager *self, const char *path, GError **error)
+{
+	NMCheckpoint *checkpoint;
+
+	g_return_val_if_fail (self, NULL);
+
+	checkpoint = (NMCheckpoint *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (GET_MANAGER (self))),
+	                                                             path);
+	if (   !checkpoint
+	    || !NM_IS_CHECKPOINT (checkpoint)) {
+		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+		             "checkpoint %s does not exist", path);
+		return NULL;
 	}
 
-	*results = nm_checkpoint_rollback (cp);
-	g_hash_table_remove (self->checkpoints, checkpoint_path);
+	nm_assert (c_list_contains (&self->checkpoints_lst_head, &checkpoint->checkpoints_lst));
+	return checkpoint;
+}
+
+const char **
+nm_checkpoint_manager_get_checkpoint_paths (NMCheckpointManager *self, guint *out_length)
+{
+	NMCheckpoint *checkpoint;
+	const char **strv;
+	guint num, i = 0;
 
+	num = c_list_length (&self->checkpoints_lst_head);
+	NM_SET_OUT (out_length, num);
+	if (!num)
+		return NULL;
+
+	strv = g_new (const char *, num + 1);
+	c_list_for_each_entry (checkpoint, &self->checkpoints_lst_head, checkpoints_lst)
+		strv[i++] = nm_dbus_object_get_path (NM_DBUS_OBJECT (checkpoint));
+	nm_assert (i == num);
+	strv[i] = NULL;
+	return strv;
+}
+
+gboolean
+nm_checkpoint_manager_adjust_rollback_timeout (NMCheckpointManager *self,
+                                               const char *path,
+                                               guint32 add_timeout,
+                                               GError **error)
+{
+	NMCheckpoint *checkpoint;
+
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (path && path[0] == '/', FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+
+	checkpoint = nm_checkpoint_manager_lookup_by_path (self, path, error);
+	if (!checkpoint)
+		return FALSE;
+
+	nm_checkpoint_adjust_rollback_timeout (checkpoint, add_timeout);
 	return TRUE;
 }
 
 /*****************************************************************************/
 
 NMCheckpointManager *
-nm_checkpoint_manager_new (NMManager *manager)
+nm_checkpoint_manager_new (NMManager *manager, GParamSpec *spec)
 {
 	NMCheckpointManager *self;
 
@@ -301,20 +336,17 @@ nm_checkpoint_manager_new (NMManager *manager)
 	 * of NMManager shall surpass the lifetime of the NMCheckpointManager
 	 * instance. */
 	self->_manager = manager;
-	self->checkpoints = g_hash_table_new_full (nm_str_hash, g_str_equal,
-	                                           NULL, checkpoint_destroy);
-
+	self->property_spec = spec;
+	c_list_init (&self->checkpoints_lst_head);
 	return self;
 }
 
 void
-nm_checkpoint_manager_unref (NMCheckpointManager *self)
+nm_checkpoint_manager_free (NMCheckpointManager *self)
 {
 	if (!self)
 		return;
 
-	nm_clear_g_source (&self->rollback_timeout_id);
-	g_hash_table_destroy (self->checkpoints);
-
+	nm_checkpoint_manager_destroy_all (self);
 	g_slice_free (NMCheckpointManager, self);
 }
diff --git a/src/nm-checkpoint-manager.h b/src/nm-checkpoint-manager.h
index 30e49041..ca66ef16 100644
--- a/src/nm-checkpoint-manager.h
+++ b/src/nm-checkpoint-manager.h
@@ -27,8 +27,13 @@
 
 typedef struct _NMCheckpointManager NMCheckpointManager;
 
-NMCheckpointManager *nm_checkpoint_manager_new (NMManager *manager);
-void nm_checkpoint_manager_unref (NMCheckpointManager *self);
+NMCheckpointManager *nm_checkpoint_manager_new (NMManager *manager, GParamSpec *spec);
+
+void nm_checkpoint_manager_free (NMCheckpointManager *self);
+
+NMCheckpoint *nm_checkpoint_manager_lookup_by_path (NMCheckpointManager *self,
+                                                    const char *path,
+                                                    GError **error);
 
 NMCheckpoint *nm_checkpoint_manager_create (NMCheckpointManager *self,
                                             const char *const*device_names,
@@ -36,15 +41,22 @@ NMCheckpoint *nm_checkpoint_manager_create (NMCheckpointManager *self,
                                             NMCheckpointCreateFlags flags,
                                             GError **error);
 
-gboolean nm_checkpoint_manager_destroy_all (NMCheckpointManager *self,
-                                            GError **error);
+void nm_checkpoint_manager_destroy_all (NMCheckpointManager *self);
 
 gboolean nm_checkpoint_manager_destroy (NMCheckpointManager *self,
-                                        const char *checkpoint_path,
+                                        const char *path,
                                         GError **error);
 gboolean nm_checkpoint_manager_rollback (NMCheckpointManager *self,
-                                         const char *checkpoint_path,
+                                         const char *path,
                                          GVariant **results,
                                          GError **error);
 
+gboolean nm_checkpoint_manager_adjust_rollback_timeout (NMCheckpointManager *self,
+                                                        const char *path,
+                                                        guint32 add_timeout,
+                                                        GError **error);
+
+const char **nm_checkpoint_manager_get_checkpoint_paths (NMCheckpointManager *self,
+                                                         guint *out_length);
+
 #endif /* __NM_CHECKPOINT_MANAGER_H__ */
diff --git a/src/nm-checkpoint.c b/src/nm-checkpoint.c
index d606dee3..a17f7eda 100644
--- a/src/nm-checkpoint.c
+++ b/src/nm-checkpoint.c
@@ -25,6 +25,7 @@
 #include <string.h>
 
 #include "nm-active-connection.h"
+#include "nm-act-request.h"
 #include "nm-auth-subject.h"
 #include "nm-core-utils.h"
 #include "nm-dbus-interface.h"
@@ -34,7 +35,6 @@
 #include "settings/nm-settings-connection.h"
 #include "nm-simple-connection.h"
 #include "nm-utils.h"
-#include "introspection/org.freedesktop.NetworkManager.Checkpoint.h"
 
 /*****************************************************************************/
 
@@ -47,38 +47,38 @@ typedef struct {
 	NMDeviceState state;
 	bool realized:1;
 	NMUnmanFlagOp unmanaged_explicit;
+	NMActivationReason activation_reason;
 } DeviceCheckpoint;
 
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+NM_GOBJECT_PROPERTIES_DEFINE (NMCheckpoint,
 	PROP_DEVICES,
 	PROP_CREATED,
 	PROP_ROLLBACK_TIMEOUT,
 );
 
-typedef struct {
+struct _NMCheckpointPrivate {
 	/* properties */
 	GHashTable *devices;
-	gint64 created;
-	guint32 rollback_timeout;
+	gint64 created_at_ms;
+	guint32 rollback_timeout_s;
+	guint timeout_id;
+	/* private members */
 	/* private members */
 	NMManager *manager;
-	gint64 rollback_ts;
 	NMCheckpointCreateFlags flags;
 	GHashTable *connection_uuids;
-} NMCheckpointPrivate;
 
-struct _NMCheckpoint {
-	NMExportedObject parent;
-	NMCheckpointPrivate _priv;
+	NMCheckpointTimeoutCallback timeout_cb;
+	gpointer timeout_data;
 };
 
 struct _NMCheckpointClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 };
 
-G_DEFINE_TYPE (NMCheckpoint, nm_checkpoint, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMCheckpoint, nm_checkpoint, NM_TYPE_DBUS_OBJECT)
 
-#define NM_CHECKPOINT_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMCheckpoint, NM_IS_CHECKPOINT)
+#define NM_CHECKPOINT_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMCheckpoint, NM_IS_CHECKPOINT)
 
 /*****************************************************************************/
 
@@ -102,20 +102,53 @@ G_DEFINE_TYPE (NMCheckpoint, nm_checkpoint, NM_TYPE_EXPORTED_OBJECT)
 
 /*****************************************************************************/
 
-guint64
-nm_checkpoint_get_rollback_ts (NMCheckpoint *self)
+void
+nm_checkpoint_log_destroy (NMCheckpoint *self)
 {
-	g_return_val_if_fail (NM_IS_CHECKPOINT (self), 0);
+	_LOGI ("destroy %s", nm_dbus_object_get_path (NM_DBUS_OBJECT (self)));
+}
 
-	return NM_CHECKPOINT_GET_PRIVATE (self)->rollback_ts;
+void
+nm_checkpoint_set_timeout_callback (NMCheckpoint *self,
+                                    NMCheckpointTimeoutCallback callback,
+                                    gpointer user_data)
+{
+	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
+
+	/* in glib world, we would have a GSignal for this. But as there
+	 * is only one subscriber, it's simpler to just set and unset(!)
+	 * the callback this way. */
+	priv->timeout_cb = callback;
+	priv->timeout_data = user_data;
 }
 
-gboolean
-nm_checkpoint_includes_device (NMCheckpoint *self, NMDevice *device)
+NMDevice *
+nm_checkpoint_includes_devices (NMCheckpoint *self, NMDevice *const*devices, guint n_devices)
 {
 	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
+	guint i;
 
-	return g_hash_table_contains (priv->devices, device);
+	for (i = 0; i < n_devices; i++) {
+		if (g_hash_table_contains (priv->devices, devices[i]))
+			return devices[i];
+	}
+	return NULL;
+}
+
+NMDevice *
+nm_checkpoint_includes_devices_of (NMCheckpoint *self, NMCheckpoint *cp_for_devices)
+{
+	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
+	NMCheckpointPrivate *priv2 = NM_CHECKPOINT_GET_PRIVATE (cp_for_devices);
+	GHashTableIter iter;
+	NMDevice *device;
+
+	g_hash_table_iter_init (&iter, priv2->devices);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &device, NULL)) {
+		if (g_hash_table_contains (priv->devices, device))
+			return device;
+	}
+	return NULL;
 }
 
 static NMSettingsConnection *
@@ -183,7 +216,7 @@ nm_checkpoint_rollback (NMCheckpoint *self)
 	GError *local_error = NULL;
 	GVariantBuilder builder;
 
-	_LOGI ("rollback of %s", nm_exported_object_get_path ((NMExportedObject *) self));
+	_LOGI ("rollback of %s", nm_dbus_object_get_path (NM_DBUS_OBJECT (self)));
 	 g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{su}"));
 
 	/* Start rolling-back each device */
@@ -301,10 +334,11 @@ activate:
 				                                     device,
 				                                     subject,
 				                                     NM_ACTIVATION_TYPE_MANAGED,
+				                                     dev_checkpoint->activation_reason,
 				                                     &local_error)) {
 					_LOGW ("rollback: reactivation of connection %s/%s failed: %s",
 					       nm_connection_get_id ((NMConnection *) connection),
-					       nm_connection_get_uuid ((NMConnection *	) connection),
+					       nm_connection_get_uuid ((NMConnection *) connection),
 					       local_error->message);
 					g_clear_error (&local_error);
 					result = NM_ROLLBACK_RESULT_ERR_FAILED;
@@ -349,21 +383,19 @@ next_dev:
 	}
 
 	if (NM_FLAGS_HAS (priv->flags, NM_CHECKPOINT_CREATE_FLAG_DISCONNECT_NEW_DEVICES)) {
-		const GSList *list;
+		const CList *tmp_lst;
 		NMDeviceState state;
-		NMDevice *dev;
-
-		for (list = nm_manager_get_devices (priv->manager); list ; list = g_slist_next (list)) {
-			dev = list->data;
-			if (!g_hash_table_contains (priv->devices, dev)) {
-				state = nm_device_get_state (dev);
-				if (   state > NM_DEVICE_STATE_DISCONNECTED
-				    && state < NM_DEVICE_STATE_DEACTIVATING) {
-					_LOGD ("rollback: disconnecting new device %s", nm_device_get_iface (dev));
-					nm_device_state_changed (dev,
-					                         NM_DEVICE_STATE_DEACTIVATING,
-					                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
-				}
+
+		nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+			if (g_hash_table_contains (priv->devices, device))
+				continue;
+			state = nm_device_get_state (device);
+			if (   state > NM_DEVICE_STATE_DISCONNECTED
+			    && state < NM_DEVICE_STATE_DEACTIVATING) {
+				_LOGD ("rollback: disconnecting new device %s", nm_device_get_iface (device));
+				nm_device_state_changed (device,
+				                         NM_DEVICE_STATE_DEACTIVATING,
+				                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
 			}
 		}
 
@@ -373,8 +405,7 @@ next_dev:
 }
 
 static DeviceCheckpoint *
-device_checkpoint_create (NMDevice *device,
-                          GError **error)
+device_checkpoint_create (NMDevice *device)
 {
 	DeviceCheckpoint *dev_checkpoint;
 	NMConnection *applied_connection;
@@ -382,7 +413,10 @@ device_checkpoint_create (NMDevice *device,
 	const char *path;
 	NMActRequest *act_request;
 
-	path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (device));
+	nm_assert (NM_IS_DEVICE (device));
+	nm_assert (nm_device_is_real (device));
+
+	path = nm_dbus_object_get_path (NM_DBUS_OBJECT (device));
 
 	dev_checkpoint = g_slice_new0 (DeviceCheckpoint);
 	dev_checkpoint->device = g_object_ref (device);
@@ -391,25 +425,23 @@ device_checkpoint_create (NMDevice *device,
 	dev_checkpoint->realized = nm_device_is_real (device);
 
 	if (nm_device_get_unmanaged_mask (device, NM_UNMANAGED_USER_EXPLICIT)) {
-		dev_checkpoint->unmanaged_explicit =
-			!!nm_device_get_unmanaged_flags (device, NM_UNMANAGED_USER_EXPLICIT);
+		dev_checkpoint->unmanaged_explicit = !!nm_device_get_unmanaged_flags (device,
+		                                                                      NM_UNMANAGED_USER_EXPLICIT);
 	} else
 		dev_checkpoint->unmanaged_explicit = NM_UNMAN_FLAG_OP_FORGET;
 
-	applied_connection = nm_device_get_applied_connection (device);
-	if (applied_connection) {
-		dev_checkpoint->applied_connection =
-			nm_simple_connection_new_clone (applied_connection);
+	act_request = nm_device_get_act_request (device);
+	if (act_request) {
+		settings_connection = nm_act_request_get_settings_connection (act_request);
+		applied_connection = nm_act_request_get_applied_connection (act_request);
 
-		settings_connection = nm_device_get_settings_connection (device);
-		g_return_val_if_fail (settings_connection, NULL);
+		dev_checkpoint->applied_connection = nm_simple_connection_new_clone (applied_connection);
 		dev_checkpoint->settings_connection =
-			nm_simple_connection_new_clone (NM_CONNECTION (settings_connection));
-
-		act_request = nm_device_get_act_request (device);
-		g_return_val_if_fail (act_request, NULL);
+		    nm_simple_connection_new_clone (NM_CONNECTION (settings_connection));
 		dev_checkpoint->ac_version_id =
-			nm_active_connection_version_id_get (NM_ACTIVE_CONNECTION (act_request));
+		    nm_active_connection_version_id_get (NM_ACTIVE_CONNECTION (act_request));
+		dev_checkpoint->activation_reason =
+		    nm_active_connection_get_activation_reason (NM_ACTIVE_CONNECTION (act_request));
 	}
 
 	return dev_checkpoint;
@@ -428,6 +460,57 @@ device_checkpoint_destroy (gpointer data)
 	g_slice_free (DeviceCheckpoint, dev_checkpoint);
 }
 
+static gboolean
+_timeout_cb (gpointer user_data)
+{
+	NMCheckpoint *self = user_data;
+	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
+
+	priv->timeout_id = 0;
+
+	if (priv->timeout_cb)
+		priv->timeout_cb (self, priv->timeout_data);
+
+	/* beware, @self likely got destroyed! */
+	return G_SOURCE_REMOVE;
+}
+
+void
+nm_checkpoint_adjust_rollback_timeout (NMCheckpoint *self, guint32 add_timeout)
+{
+	guint32 rollback_timeout_s;
+	gint64 now_ms, add_timeout_ms, rollback_timeout_ms;
+
+	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->timeout_id);
+
+	if (add_timeout == 0)
+		rollback_timeout_s = 0;
+	else {
+		now_ms = nm_utils_get_monotonic_timestamp_ms ();
+		add_timeout_ms = ((gint64) add_timeout) * 1000;
+		rollback_timeout_ms = (now_ms - priv->created_at_ms) + add_timeout_ms;
+
+		/* round to nearest integer second. Since NM_CHECKPOINT_ROLLBACK_TIMEOUT is
+		 * in units seconds, it will be able to exactly express the timeout. */
+		rollback_timeout_s = NM_MIN ((rollback_timeout_ms + 500) / 1000, (gint64) G_MAXUINT32);
+
+		/* we expect the timeout to be positive, because add_timeout_ms is positive.
+		 * We cannot accept a zero, because it means "infinity". */
+		nm_assert (rollback_timeout_s > 0);
+
+		priv->timeout_id = g_timeout_add (NM_MIN (add_timeout_ms, (gint64) G_MAXUINT32),
+		                                  _timeout_cb,
+		                                  self);
+	}
+
+	if (rollback_timeout_s != priv->rollback_timeout_s) {
+		priv->rollback_timeout_s = rollback_timeout_s;
+		_notify (self, PROP_ROLLBACK_TIMEOUT);
+	}
+}
+
 /*****************************************************************************/
 
 static void
@@ -436,22 +519,20 @@ get_property (GObject *object, guint prop_id,
 {
 	NMCheckpoint *self = NM_CHECKPOINT (object);
 	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
-	gs_free_slist GSList *devices = NULL;
-	GHashTableIter iter;
-	NMDevice *device;
 
 	switch (prop_id) {
 	case PROP_DEVICES:
-		g_hash_table_iter_init (&iter, priv->devices);
-		while (g_hash_table_iter_next (&iter, (gpointer *) &device, NULL))
-			devices = g_slist_append (devices, device);
-		nm_utils_g_value_set_object_path_array (value, devices, NULL, NULL);
+		nm_dbus_utils_g_value_set_object_path_from_hash (value,
+		                                                 priv->devices,
+		                                                 FALSE);
 		break;
 	case PROP_CREATED:
-		g_value_set_int64 (value, priv->created);
+		g_value_set_int64 (value,
+		                   nm_utils_monotonic_timestamp_as_boottime (priv->created_at_ms,
+		                                                             NM_UTILS_NS_PER_MSEC));
 		break;
 	case PROP_ROLLBACK_TIMEOUT:
-		g_value_set_uint (value, priv->rollback_timeout);
+		g_value_set_uint (value, priv->rollback_timeout_s);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -464,47 +545,47 @@ get_property (GObject *object, guint prop_id,
 static void
 nm_checkpoint_init (NMCheckpoint *self)
 {
-	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
+	NMCheckpointPrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_CHECKPOINT, NMCheckpointPrivate);
 
-	priv->devices = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+	self->_priv = priv;
+
+	c_list_init (&self->checkpoints_lst);
+
+	priv->devices = g_hash_table_new_full (nm_direct_hash, NULL,
 	                                       NULL, device_checkpoint_destroy);
 }
 
 NMCheckpoint *
-nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_timeout,
-                   NMCheckpointCreateFlags flags, GError **error)
+nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_timeout_s,
+                   NMCheckpointCreateFlags flags)
 {
 	NMCheckpoint *self;
 	NMCheckpointPrivate *priv;
 	NMSettingsConnection *const *con;
-	DeviceCheckpoint *dev_checkpoint;
-	NMDevice *device;
+	gint64 rollback_timeout_ms;
 	guint i;
 
 	g_return_val_if_fail (manager, NULL);
 	g_return_val_if_fail (devices, NULL);
-	g_return_val_if_fail (!error || !*error, NULL);
-
-	if (!devices->len) {
-		g_set_error_literal (error,
-		                     NM_MANAGER_ERROR,
-		                     NM_MANAGER_ERROR_INVALID_ARGUMENTS,
-		                     "no device available");
-		return NULL;
-	}
+	g_return_val_if_fail (devices->len > 0, NULL);
 
 	self = g_object_new (NM_TYPE_CHECKPOINT, NULL);
 
 	priv = NM_CHECKPOINT_GET_PRIVATE (self);
 	priv->manager = manager;
-	priv->created = nm_utils_monotonic_timestamp_as_boottime (nm_utils_get_monotonic_timestamp_ms (),
-	                                                          NM_UTILS_NS_PER_MSEC);
-	priv->rollback_timeout = rollback_timeout;
-	priv->rollback_ts = rollback_timeout ?
-	    (nm_utils_get_monotonic_timestamp_ms () + ((gint64) rollback_timeout * 1000)) :
-	    0;
+	priv->rollback_timeout_s = rollback_timeout_s;
+	priv->created_at_ms = nm_utils_get_monotonic_timestamp_ms ();
 	priv->flags = flags;
 
+	if (rollback_timeout_s != 0) {
+		rollback_timeout_ms = ((gint64) rollback_timeout_s) * 1000;
+		priv->timeout_id = g_timeout_add (NM_MIN (rollback_timeout_ms, (gint64) G_MAXUINT32),
+		                                  _timeout_cb,
+		                                  self);
+	}
+
 	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++) {
@@ -514,13 +595,15 @@ nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_time
 	}
 
 	for (i = 0; i < devices->len; i++) {
-		device = (NMDevice *) devices->pdata[i];
-		dev_checkpoint = device_checkpoint_create (device, error);
-		if (!dev_checkpoint) {
-			g_object_unref (self);
-			return NULL;
-		}
-		g_hash_table_insert (priv->devices, device, dev_checkpoint);
+		NMDevice *device = devices->pdata[i];
+
+		/* FIXME: as long as the check point instance exists, it won't let go
+		 *        of the device. That is a bug, for example, if you have a ethernet
+		 *        device that gets removed (rmmod), the checkpoint will reference
+		 *        a non-existing D-Bus path of a device. */
+		g_hash_table_insert (priv->devices,
+		                     device,
+		                     device_checkpoint_create (device));
 	}
 
 	return self;
@@ -532,20 +615,41 @@ dispose (GObject *object)
 	NMCheckpoint *self = NM_CHECKPOINT (object);
 	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
 
+	nm_assert (c_list_is_empty (&self->checkpoints_lst));
+
 	g_clear_pointer (&priv->devices, g_hash_table_unref);
 	g_clear_pointer (&priv->connection_uuids, g_hash_table_unref);
 
+	nm_clear_g_source (&priv->timeout_id);
+
 	G_OBJECT_CLASS (nm_checkpoint_parent_class)->dispose (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_checkpoint = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_CHECKPOINT,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Devices",         "ao", NM_CHECKPOINT_DEVICES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Created",         "x",  NM_CHECKPOINT_CREATED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("RollbackTimeout", "u",  NM_CHECKPOINT_ROLLBACK_TIMEOUT),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_checkpoint_class_init (NMCheckpointClass *checkpoint_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (checkpoint_class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (checkpoint_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (checkpoint_class);
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/Checkpoint");
-	exported_object_class->export_on_construction = FALSE;
+	g_type_class_add_private (object_class, sizeof (NMCheckpointPrivate));
+
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/Checkpoint");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_checkpoint);
 
 	object_class->dispose = dispose;
 	object_class->get_property = get_property;
@@ -569,8 +673,4 @@ nm_checkpoint_class_init (NMCheckpointClass *checkpoint_class)
 	                       G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (checkpoint_class),
-	                                        NMDBUS_TYPE_CHECKPOINT_SKELETON,
-	                                        NULL);
 }
diff --git a/src/nm-checkpoint.h b/src/nm-checkpoint.h
index fccf8af3..c8598f38 100644
--- a/src/nm-checkpoint.h
+++ b/src/nm-checkpoint.h
@@ -21,7 +21,7 @@
 #ifndef __NETWORKMANAGER_CHECKPOINT_H__
 #define __NETWORKMANAGER_CHECKPOINT_H__
 
-#include "nm-exported-object.h"
+#include "nm-dbus-object.h"
 #include "nm-dbus-interface.h"
 
 #define NM_TYPE_CHECKPOINT            (nm_checkpoint_get_type ())
@@ -35,16 +35,35 @@
 #define NM_CHECKPOINT_CREATED "created"
 #define NM_CHECKPOINT_ROLLBACK_TIMEOUT "rollback-timeout"
 
-typedef struct _NMCheckpoint NMCheckpoint;
+typedef struct _NMCheckpointPrivate NMCheckpointPrivate;
+
+typedef struct {
+	NMDBusObject parent;
+	NMCheckpointPrivate *_priv;
+	CList checkpoints_lst;
+} NMCheckpoint;
+
 typedef struct _NMCheckpointClass NMCheckpointClass;
 
 GType nm_checkpoint_get_type (void);
 
 NMCheckpoint *nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_timeout,
-                                 NMCheckpointCreateFlags flags, GError **error);
+                                 NMCheckpointCreateFlags flags);
+
+typedef void (*NMCheckpointTimeoutCallback) (NMCheckpoint *self,
+                                             gpointer user_data);
+
+void nm_checkpoint_log_destroy (NMCheckpoint *self);
+
+void nm_checkpoint_set_timeout_callback (NMCheckpoint *self,
+                                         NMCheckpointTimeoutCallback callback,
+                                         gpointer user_data);
 
-guint64 nm_checkpoint_get_rollback_ts (NMCheckpoint *checkpoint);
-gboolean nm_checkpoint_includes_device (NMCheckpoint *checkpoint, NMDevice *device);
 GVariant *nm_checkpoint_rollback (NMCheckpoint *self);
 
+void nm_checkpoint_adjust_rollback_timeout (NMCheckpoint *self, guint32 add_timeout);
+
+NMDevice *nm_checkpoint_includes_devices (NMCheckpoint *self, NMDevice *const*devices, guint n_devices);
+NMDevice *nm_checkpoint_includes_devices_of (NMCheckpoint *self, NMCheckpoint *cp_for_devices);
+
 #endif /* __NETWORKMANAGER_CHECKPOINT_H__ */
diff --git a/src/nm-config-data.c b/src/nm-config-data.c
index 00e5c635..5f19eabe 100644
--- a/src/nm-config-data.c
+++ b/src/nm-config-data.c
@@ -54,7 +54,7 @@ struct _NMGlobalDnsConfig {
 	char **searches;
 	char **options;
 	GHashTable *domains;
-	char **domain_list;
+	const char **domain_list;
 	gboolean internal;
 };
 
@@ -238,7 +238,7 @@ nm_config_data_get_plugins (const NMConfigData *self, gboolean allow_default)
 	if (!list && allow_default) {
 		gs_unref_keyfile GKeyFile *kf = nm_config_create_keyfile ();
 
-		/* let keyfile split the default string according to it's own escaping rules. */
+		/* let keyfile split the default string according to its own escaping rules. */
 		g_key_file_set_value (kf, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NM_CONFIG_DEFAULT_MAIN_PLUGINS);
 		list = g_key_file_get_string_list (kf, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NULL, NULL);
 	}
@@ -705,54 +705,53 @@ nm_config_data_log (const NMConfigData *self,
 
 /*****************************************************************************/
 
-const char *const *
-nm_global_dns_config_get_searches (const NMGlobalDnsConfig *dns)
+const char *const*
+nm_global_dns_config_get_searches (const NMGlobalDnsConfig *dns_config)
 {
-	g_return_val_if_fail (dns, NULL);
+	g_return_val_if_fail (dns_config, NULL);
 
-	return (const char *const *) dns->searches;
+	return (const char *const*) dns_config->searches;
 }
 
 const char *const *
-nm_global_dns_config_get_options (const NMGlobalDnsConfig *dns)
+nm_global_dns_config_get_options (const NMGlobalDnsConfig *dns_config)
 {
-	g_return_val_if_fail (dns, NULL);
+	g_return_val_if_fail (dns_config, NULL);
 
-	return (const char *const *) dns->options;
+	return (const char *const*) dns_config->options;
 }
 
 guint
-nm_global_dns_config_get_num_domains (const NMGlobalDnsConfig *dns)
+nm_global_dns_config_get_num_domains (const NMGlobalDnsConfig *dns_config)
 {
-	g_return_val_if_fail (dns, 0);
-	g_return_val_if_fail (dns->domains, 0);
+	g_return_val_if_fail (dns_config, 0);
 
-	return g_hash_table_size (dns->domains);
+	return dns_config->domains ? g_hash_table_size (dns_config->domains) : 0;
 }
 
 NMGlobalDnsDomain *
-nm_global_dns_config_get_domain (const NMGlobalDnsConfig *dns, guint i)
+nm_global_dns_config_get_domain (const NMGlobalDnsConfig *dns_config, guint i)
 {
 	NMGlobalDnsDomain *domain;
 
-	g_return_val_if_fail (dns, NULL);
-	g_return_val_if_fail (dns->domains, NULL);
-	g_return_val_if_fail (dns->domain_list, NULL);
-	g_return_val_if_fail (i < g_strv_length (dns->domain_list), NULL);
+	g_return_val_if_fail (dns_config, NULL);
+	g_return_val_if_fail (dns_config->domains, NULL);
+	g_return_val_if_fail (i < g_hash_table_size (dns_config->domains), NULL);
 
-	domain = g_hash_table_lookup (dns->domains, dns->domain_list[i]);
-	g_return_val_if_fail (domain, NULL);
+	nm_assert (NM_PTRARRAY_LEN (dns_config->domain_list) == g_hash_table_size (dns_config->domains));
 
+	domain = g_hash_table_lookup (dns_config->domains, dns_config->domain_list[i]);
+
+	nm_assert (domain);
 	return domain;
 }
 
-NMGlobalDnsDomain *nm_global_dns_config_lookup_domain (const NMGlobalDnsConfig *dns, const char *name)
+NMGlobalDnsDomain *nm_global_dns_config_lookup_domain (const NMGlobalDnsConfig *dns_config, const char *name)
 {
-	g_return_val_if_fail (dns, NULL);
-	g_return_val_if_fail (dns->domains, NULL);
+	g_return_val_if_fail (dns_config, NULL);
 	g_return_val_if_fail (name, NULL);
 
-	return g_hash_table_lookup (dns->domains, name);
+	return dns_config->domains ? g_hash_table_lookup (dns_config->domains, name) : NULL;
 }
 
 const char *
@@ -775,55 +774,73 @@ const char *const *
 nm_global_dns_domain_get_options (const NMGlobalDnsDomain *domain)
 {
 	g_return_val_if_fail (domain, NULL);
+
 	return (const char *const *) domain->options;
 }
 
 gboolean
-nm_global_dns_config_is_internal (const NMGlobalDnsConfig *dns)
+nm_global_dns_config_is_internal (const NMGlobalDnsConfig *dns_config)
 {
-	return dns->internal;
+	return dns_config->internal;
 }
 
 gboolean
-nm_global_dns_config_is_empty (const NMGlobalDnsConfig *dns)
+nm_global_dns_config_is_empty (const NMGlobalDnsConfig *dns_config)
 {
-	g_return_val_if_fail (dns, TRUE);
-	g_return_val_if_fail (dns->domains, TRUE);
+	g_return_val_if_fail (dns_config, TRUE);
 
-	return    (!dns->searches || g_strv_length (dns->searches) == 0)
-	       && (!dns->options || g_strv_length (dns->options) == 0)
-	       && g_hash_table_size (dns->domains) == 0;
+	return    !dns_config->searches
+	       && !dns_config->options
+	       && !dns_config->domain_list;
 }
 
 void
-nm_global_dns_config_update_checksum (const NMGlobalDnsConfig *dns, GChecksum *sum)
+nm_global_dns_config_update_checksum (const NMGlobalDnsConfig *dns_config, GChecksum *sum)
 {
 	NMGlobalDnsDomain *domain;
-	GList *keys, *key;
-	guint i;
+	guint i, j;
+	guint8 v8;
 
-	g_return_if_fail (dns);
-	g_return_if_fail (dns->domains);
+	g_return_if_fail (dns_config);
 	g_return_if_fail (sum);
 
-	for (i = 0; dns->searches && dns->searches[i]; i++)
-		g_checksum_update (sum, (guchar *) dns->searches[i], strlen (dns->searches[i]));
-	for (i = 0; dns->options && dns->options[i]; i++)
-		g_checksum_update (sum, (guchar *) dns->options[i], strlen (dns->options[i]));
+	v8 = NM_HASH_COMBINE_BOOLS (guint8,
+	                            !dns_config->searches,
+	                            !dns_config->options,
+	                            !dns_config->domain_list);
+	g_checksum_update (sum, (guchar *) &v8, 1);
+
+	if (dns_config->searches) {
+		for (i = 0; dns_config->searches[i]; i++)
+			g_checksum_update (sum, (guchar *) dns_config->searches[i], strlen (dns_config->searches[i]) + 1);
+	}
+	if (dns_config->options) {
+		for (i = 0; dns_config->options[i]; i++)
+			g_checksum_update (sum, (guchar *) dns_config->options[i], strlen (dns_config->options[i]) + 1);
+	}
+
+	if (dns_config->domain_list) {
+		for (i = 0; dns_config->domain_list[i]; i++) {
+			domain = g_hash_table_lookup (dns_config->domains, dns_config->domain_list[i]);
+			nm_assert (domain);
 
-	keys = g_list_sort (g_hash_table_get_keys (dns->domains), (GCompareFunc) strcmp);
-	for (key = keys; key; key = g_list_next (key)) {
+			v8 = NM_HASH_COMBINE_BOOLS (guint8,
+			                            !domain->servers,
+			                            !domain->options);
+			g_checksum_update (sum, (guchar *) &v8, 1);
 
-		domain = g_hash_table_lookup (dns->domains, key->data);
-		g_assert (domain != NULL);
-		g_checksum_update (sum, (guchar *) domain->name, strlen (domain->name));
+			g_checksum_update (sum, (guchar *) domain->name, strlen (domain->name) + 1);
 
-		for (i = 0; domain->servers && domain->servers[i]; i++)
-			g_checksum_update (sum, (guchar *) domain->servers[i], strlen (domain->servers[i]));
-		for (i = 0; domain->options && domain->options[i]; i++)
-			g_checksum_update (sum, (guchar *) domain->options[i], strlen (domain->options[i]));
+			if (domain->servers) {
+				for (j = 0; domain->servers[j]; j++)
+					g_checksum_update (sum, (guchar *) domain->servers[j], strlen (domain->servers[j]) + 1);
+			}
+			if (domain->options) {
+				for (j = 0; domain->options[j]; j++)
+					g_checksum_update (sum, (guchar *) domain->options[j], strlen (domain->options[j]) + 1);
+			}
+		}
 	}
-	g_list_free (keys);
 }
 
 static void
@@ -838,14 +855,15 @@ global_dns_domain_free (NMGlobalDnsDomain  *domain)
 }
 
 void
-nm_global_dns_config_free (NMGlobalDnsConfig *conf)
-{
-	if (conf) {
-		g_strfreev (conf->searches);
-		g_strfreev (conf->options);
-		g_free (conf->domain_list);
-		g_hash_table_unref (conf->domains);
-		g_free (conf);
+nm_global_dns_config_free (NMGlobalDnsConfig *dns_config)
+{
+	if (dns_config) {
+		g_strfreev (dns_config->searches);
+		g_strfreev (dns_config->options);
+		g_free (dns_config->domain_list);
+		if (dns_config->domains)
+			g_hash_table_unref (dns_config->domains);
+		g_free (dns_config);
 	}
 }
 
@@ -858,18 +876,22 @@ nm_config_data_get_global_dns_config (const NMConfigData *self)
 }
 
 static void
-global_dns_config_update_domain_list (NMGlobalDnsConfig *dns)
+global_dns_config_seal_domains (NMGlobalDnsConfig *dns_config)
 {
-	guint length;
+	nm_assert (dns_config);
+	nm_assert (dns_config->domains);
+	nm_assert (!dns_config->domain_list);
 
-	g_free (dns->domain_list);
-	dns->domain_list = (char **) g_hash_table_get_keys_as_array (dns->domains, &length);
+	if (g_hash_table_size (dns_config->domains) == 0)
+		nm_clear_pointer (&dns_config->domains, g_hash_table_unref);
+	else
+		dns_config->domain_list = nm_utils_strdict_get_keys (dns_config->domains, TRUE, NULL);
 }
 
 static NMGlobalDnsConfig *
 load_global_dns (GKeyFile *keyfile, gboolean internal)
 {
-	NMGlobalDnsConfig *conf;
+	NMGlobalDnsConfig *dns_config;
 	char *group, *domain_prefix;
 	gs_strfreev char **groups = NULL;
 	int g, i, j, domain_prefix_len;
@@ -887,13 +909,18 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
 	if (!nm_config_keyfile_has_global_dns_config (keyfile, internal))
 		return NULL;
 
-	conf = g_malloc0 (sizeof (NMGlobalDnsConfig));
-	conf->domains = g_hash_table_new_full (nm_str_hash, g_str_equal,
-	                                       g_free, (GDestroyNotify) global_dns_domain_free);
+	dns_config = g_malloc0 (sizeof (NMGlobalDnsConfig));
+	dns_config->domains = g_hash_table_new_full (nm_str_hash, g_str_equal,
+	                                             g_free, (GDestroyNotify) global_dns_domain_free);
 
 	strv = g_key_file_get_string_list (keyfile, group, "searches", NULL, NULL);
-	if (strv)
-		conf->searches = _nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+	if (strv) {
+		_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+		if (!strv[0])
+			g_free (strv);
+		else
+			dns_config->searches = strv;
+	}
 
 	strv = g_key_file_get_string_list (keyfile, group, "options", NULL, NULL);
 	if (strv) {
@@ -904,8 +931,12 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
 			else
 				g_free (strv[i]);
 		}
-		strv[j] = NULL;
-		conf->options = strv;
+		if (j == 0)
+			g_free (strv);
+		else {
+			strv[j] = NULL;
+			dns_config->options = strv;
+		}
 	}
 
 	groups = g_key_file_get_groups (keyfile, NULL);
@@ -928,20 +959,23 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
 				else
 					g_free (strv[i]);
 			}
-			if (j) {
+			if (j == 0)
+				g_free (strv);
+			else {
 				strv[j] = NULL;
 				servers = strv;
 			}
-			else
-				g_free (strv);
 		}
 
 		if (!servers)
 			continue;
 
 		strv = g_key_file_get_string_list (keyfile, groups[g], "options", NULL, NULL);
-		if (strv)
+		if (strv) {
 			options = _nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+			if (!options[0])
+				nm_clear_g_free (&options);
+		}
 
 		name = strdup (&groups[g][domain_prefix_len]);
 		domain = g_malloc0 (sizeof (NMGlobalDnsDomain));
@@ -949,7 +983,7 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
 		domain->servers = servers;
 		domain->options = options;
 
-		g_hash_table_insert (conf->domains, strdup (name), domain);
+		g_hash_table_insert (dns_config->domains, strdup (name), domain);
 
 		if (!strcmp (name, "*"))
 			default_found = TRUE;
@@ -958,59 +992,61 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
 	if (!default_found) {
 		nm_log_dbg (LOGD_CORE, "%s global DNS configuration is missing default domain, ignore it",
 		            internal ? "internal" : "user");
-		nm_global_dns_config_free (conf);
+		nm_global_dns_config_free (dns_config);
 		return NULL;
 	}
 
-	conf->internal = internal;
-	global_dns_config_update_domain_list (conf);
-	return conf;
+	dns_config->internal = internal;
+	global_dns_config_seal_domains (dns_config);
+	return dns_config;
 }
 
 
 void
-nm_global_dns_config_to_dbus (const NMGlobalDnsConfig *dns, GValue *value)
+nm_global_dns_config_to_dbus (const NMGlobalDnsConfig *dns_config, GValue *value)
 {
 	GVariantBuilder conf_builder, domains_builder, domain_builder;
-	NMGlobalDnsDomain *domain;
-	GHashTableIter iter;
+	guint i;
 
 	g_variant_builder_init (&conf_builder, G_VARIANT_TYPE ("a{sv}"));
-	if (!dns)
+	if (!dns_config)
 		goto out;
 
-	if (dns->searches) {
+	if (dns_config->searches) {
 		g_variant_builder_add (&conf_builder, "{sv}", "searches",
-		                       g_variant_new_strv ((const char *const *) dns->searches, -1));
+		                       g_variant_new_strv ((const char *const *) dns_config->searches, -1));
 	}
 
-	if (dns->options) {
+	if (dns_config->options) {
 		g_variant_builder_add (&conf_builder, "{sv}", "options",
-		                       g_variant_new_strv ((const char *const *) dns->options, -1));
+		                       g_variant_new_strv ((const char *const *) dns_config->options, -1));
 	}
 
 	g_variant_builder_init (&domains_builder, G_VARIANT_TYPE ("a{sv}"));
+	if (dns_config->domain_list) {
+		for (i = 0; dns_config->domain_list[i]; i++) {
+			NMGlobalDnsDomain *domain;
 
-	g_hash_table_iter_init (&iter, dns->domains);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &domain)) {
+			domain = g_hash_table_lookup (dns_config->domains, dns_config->domain_list[i]);
 
-		g_variant_builder_init (&domain_builder, G_VARIANT_TYPE ("a{sv}"));
+			g_variant_builder_init (&domain_builder, G_VARIANT_TYPE ("a{sv}"));
 
-		if (domain->servers) {
-			g_variant_builder_add (&domain_builder, "{sv}", "servers",
-			                       g_variant_new_strv ((const char *const *) domain->servers, -1));
-		}
-		if (domain->options) {
-			g_variant_builder_add (&domain_builder, "{sv}", "options",
-			                       g_variant_new_strv ((const char *const *) domain->options, -1));
-		}
+			if (domain->servers) {
+				g_variant_builder_add (&domain_builder, "{sv}", "servers",
+				                       g_variant_new_strv ((const char *const *) domain->servers, -1));
+			}
+			if (domain->options) {
+				g_variant_builder_add (&domain_builder, "{sv}", "options",
+				                       g_variant_new_strv ((const char *const *) domain->options, -1));
+			}
 
-		g_variant_builder_add (&domains_builder, "{sv}", domain->name,
-		                       g_variant_builder_end (&domain_builder));
+			g_variant_builder_add (&domains_builder, "{sv}", domain->name,
+			                       g_variant_builder_end (&domain_builder));
+		}
 	}
-
 	g_variant_builder_add (&conf_builder, "{sv}", "domains",
 	                       g_variant_builder_end (&domains_builder));
+
 out:
 	g_value_take_variant (value, g_variant_builder_end (&conf_builder));
 }
@@ -1044,15 +1080,20 @@ global_dns_domain_from_dbus (char *name, GVariant *variant)
 				else
 					g_free (strv[i]);
 			}
-			if (j) {
+			if (j == 0)
+				g_free (strv);
+			else {
 				strv[j] = NULL;
+				g_strfreev (domain->servers);
 				domain->servers = strv;
-			} else
-				g_free (strv);
+			}
 		} else if (   !g_strcmp0 (key, "options")
 		           && g_variant_is_of_type (val, G_VARIANT_TYPE ("as"))) {
 			strv = g_variant_dup_strv (val, NULL);
+			g_strfreev (domain->options);
 			domain->options = _nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+			if (!domain->options[0])
+				nm_clear_g_free (&domain->options);
 		}
 
 		g_variant_unref (val);
@@ -1111,11 +1152,12 @@ nm_global_dns_config_from_dbus (const GValue *value, GError **error)
 				else
 					g_free (strv[i]);
 			}
-
-			if (strv)
+			if (j == 0)
+				g_free (strv);
+			else {
 				strv[j] = NULL;
-
-			dns_config->options = strv;
+				dns_config->options = strv;
+			}
 		} else if (   !g_strcmp0 (key, "domains")
 		           && g_variant_is_of_type (val, G_VARIANT_TYPE ("a{sv}"))) {
 			NMGlobalDnsDomain *domain;
@@ -1145,7 +1187,7 @@ nm_global_dns_config_from_dbus (const GValue *value, GError **error)
 		return NULL;
 	}
 
-	global_dns_config_update_domain_list (dns_config);
+	global_dns_config_seal_domains (dns_config);
 	return dns_config;
 }
 
@@ -1196,6 +1238,8 @@ _match_section_infos_lookup (const MatchSectionInfo *match_section_infos,
                              GKeyFile *keyfile,
                              const char *property,
                              NMDevice *device,
+                             const NMPlatformLink *pllink,
+                             const char *match_device_type,
                              char **out_value)
 {
 	if (!match_section_infos)
@@ -1216,9 +1260,15 @@ _match_section_infos_lookup (const MatchSectionInfo *match_section_infos,
 		if (!value && !match_section_infos->stop_match)
 			continue;
 
-		match = TRUE;
-		if (match_section_infos->match_device.has)
-			match = device && nm_device_spec_match_list (device, match_section_infos->match_device.spec);
+		if (match_section_infos->match_device.has) {
+			if (device)
+				match = nm_device_spec_match_list (device, match_section_infos->match_device.spec);
+			else if (pllink)
+				match = nm_match_spec_device_by_pllink (pllink, match_device_type, match_section_infos->match_device.spec, FALSE);
+			else
+				match = FALSE;
+		} else
+			match = TRUE;
 
 		if (match) {
 			*out_value = value;
@@ -1248,6 +1298,35 @@ nm_config_data_get_device_config (const NMConfigData *self,
 	                                               priv->keyfile,
 	                                               property,
 	                                               device,
+	                                               NULL,
+	                                               NULL,
+	                                               &value);
+	NM_SET_OUT (has_match, !!connection_info);
+	return value;
+}
+
+char *
+nm_config_data_get_device_config_by_pllink (const NMConfigData *self,
+                                            const char *property,
+                                            const NMPlatformLink *pllink,
+                                            const char *match_device_type,
+                                            gboolean *has_match)
+{
+	const NMConfigDataPrivate *priv;
+	const MatchSectionInfo *connection_info;
+	char *value = NULL;
+
+	g_return_val_if_fail (self, NULL);
+	g_return_val_if_fail (property && *property, NULL);
+
+	priv = NM_CONFIG_DATA_GET_PRIVATE (self);
+
+	connection_info = _match_section_infos_lookup (&priv->device_infos[0],
+	                                               priv->keyfile,
+	                                               property,
+	                                               NULL,
+	                                               pllink,
+	                                               match_device_type,
 	                                               &value);
 	NM_SET_OUT (has_match, !!connection_info);
 	return value;
@@ -1287,6 +1366,8 @@ nm_config_data_get_connection_default (const NMConfigData *self,
 	                             priv->keyfile,
 	                             property,
 	                             device,
+	                             NULL,
+	                             NULL,
 	                             &value);
 	return value;
 }
diff --git a/src/nm-config-data.h b/src/nm-config-data.h
index 3092a87f..a11b38b2 100644
--- a/src/nm-config-data.h
+++ b/src/nm-config-data.h
@@ -188,6 +188,12 @@ char *nm_config_data_get_device_config (const NMConfigData *self,
                                         NMDevice *device,
                                         gboolean *has_match);
 
+char *nm_config_data_get_device_config_by_pllink (const NMConfigData *self,
+                                                  const char *property,
+                                                  const NMPlatformLink *pllink,
+                                                  const char *match_device_type,
+                                                  gboolean *has_match);
+
 gboolean nm_config_data_get_device_config_boolean (const NMConfigData *self,
                                                    const char *property,
                                                    NMDevice *device,
@@ -200,18 +206,18 @@ gboolean nm_config_data_is_intern_atomic_group (const NMConfigData *self, const
 
 GKeyFile *nm_config_data_clone_keyfile_intern (const NMConfigData *self);
 
-const char *const *nm_global_dns_config_get_searches (const NMGlobalDnsConfig *dns);
-const char *const *nm_global_dns_config_get_options (const NMGlobalDnsConfig *dns);
-guint nm_global_dns_config_get_num_domains (const NMGlobalDnsConfig *dns);
-NMGlobalDnsDomain *nm_global_dns_config_get_domain (const NMGlobalDnsConfig *dns, guint i);
-NMGlobalDnsDomain *nm_global_dns_config_lookup_domain (const NMGlobalDnsConfig *dns, const char *name);
+const char *const *nm_global_dns_config_get_searches (const NMGlobalDnsConfig *dns_config);
+const char *const *nm_global_dns_config_get_options (const NMGlobalDnsConfig *dns_config);
+guint nm_global_dns_config_get_num_domains (const NMGlobalDnsConfig *dns_config);
+NMGlobalDnsDomain *nm_global_dns_config_get_domain (const NMGlobalDnsConfig *dns_config, guint i);
+NMGlobalDnsDomain *nm_global_dns_config_lookup_domain (const NMGlobalDnsConfig *dns_config, const char *name);
 const char *nm_global_dns_domain_get_name (const NMGlobalDnsDomain *domain);
 const char *const *nm_global_dns_domain_get_servers (const NMGlobalDnsDomain *domain);
 const char *const *nm_global_dns_domain_get_options (const NMGlobalDnsDomain *domain);
-gboolean nm_global_dns_config_is_internal (const NMGlobalDnsConfig *dns);
-gboolean nm_global_dns_config_is_empty (const NMGlobalDnsConfig *dns);
-void nm_global_dns_config_update_checksum (const NMGlobalDnsConfig *dns, GChecksum *sum);
-void nm_global_dns_config_free (NMGlobalDnsConfig *conf);
+gboolean nm_global_dns_config_is_internal (const NMGlobalDnsConfig *dns_config);
+gboolean nm_global_dns_config_is_empty (const NMGlobalDnsConfig *dns_config);
+void nm_global_dns_config_update_checksum (const NMGlobalDnsConfig *dns_config, GChecksum *sum);
+void nm_global_dns_config_free (NMGlobalDnsConfig *dns_config);
 
 NMGlobalDnsConfig *nm_global_dns_config_from_dbus (const GValue *value, GError **error);
 void nm_global_dns_config_to_dbus (const NMGlobalDnsConfig *dns_config, GValue *value);
diff --git a/src/nm-config.c b/src/nm-config.c
index 29f0d517..bef05fc7 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -553,7 +553,7 @@ nm_config_create_keyfile ()
 
 /* this is an external variable, to make loading testable. Other then that,
  * no code is supposed to change this. */
-guint _nm_config_match_nm_version = NM_VERSION_CUR_STABLE;
+guint _nm_config_match_nm_version = NM_VERSION;
 char *_nm_config_match_env = NULL;
 
 static gboolean
@@ -615,7 +615,7 @@ _sort_groups_cmp (const char **pa, const char **pb, gpointer dummy)
 	if (a_is_connection) {
 		/* both are [connection.\+] entries. Reverse their order.
 		 * One of the sections might be literally [connection]. That section
-		 * is special and it's order will be fixed later. It doesn't actually
+		 * is special and its order will be fixed later. It doesn't actually
 		 * matter here how it compares with [connection.\+] sections. */
 		return pa > pb ? -1 : 1;
 	}
@@ -633,7 +633,7 @@ _sort_groups_cmp (const char **pa, const char **pb, gpointer dummy)
 	if (a_is_device) {
 		/* both are [device.\+] entries. Reverse their order.
 		 * One of the sections might be literally [device]. That section
-		 * is special and it's order will be fixed later. It doesn't actually
+		 * is special and its order will be fixed later. It doesn't actually
 		 * matter here how it compares with [device.\+] sections. */
 		return pa > pb ? -1 : 1;
 	}
@@ -1383,7 +1383,6 @@ intern_config_write (const char *filename,
 	GKeyFile *keyfile;
 	gs_strfreev char **groups = NULL;
 	guint g, k;
-	gboolean has_intern = FALSE;
 	gboolean success = FALSE;
 	GError *local = NULL;
 
@@ -1449,10 +1448,7 @@ intern_config_write (const char *filename,
 
 			value_set = g_key_file_get_value (keyfile_intern, group, key, NULL);
 
-			if (is_intern) {
-				has_intern = TRUE;
-				g_key_file_set_value (keyfile, group, key, value_set);
-			} else if (is_atomic)
+			if (is_intern || is_atomic)
 				g_key_file_set_value (keyfile, group, key, value_set);
 			else {
 				gs_free char *value_was = NULL;
@@ -2117,7 +2113,7 @@ nm_config_device_state_load_all (void)
 		if (!state)
 			continue;
 
-		if (!nm_g_hash_table_insert (states, GINT_TO_POINTER (ifindex), state))
+		if (!g_hash_table_insert (states, GINT_TO_POINTER (ifindex), state))
 			nm_assert_not_reached ();
 	}
 	g_dir_close (dir);
@@ -2401,15 +2397,15 @@ _set_config_data (NMConfig *self, NMConfigData *new_data, NMConfigChangeFlags re
 	}
 
 	if (new_data) {
-		_LOGI ("config: signal %s (%s)",
+		_LOGI ("signal: %s (%s)",
 		       nm_config_change_flags_to_string (changes, NULL, 0),
 		       nm_config_data_get_config_description (new_data));
 		nm_config_data_log (new_data, "CONFIG: ", "  ", NULL);
 		priv->config_data = new_data;
 	} else if (had_new_data)
-		_LOGI ("config: signal %s (no changes from disk)", nm_config_change_flags_to_string (changes, NULL, 0));
+		_LOGI ("signal: %s (no changes from disk)", nm_config_change_flags_to_string (changes, NULL, 0));
 	else
-		_LOGI ("config: signal %s", nm_config_change_flags_to_string (changes, NULL, 0));
+		_LOGI ("signal: %s", nm_config_change_flags_to_string (changes, NULL, 0));
 	g_signal_emit (self, signals[SIGNAL_CONFIG_CHANGED], 0,
 	               new_data ? new_data : old_data,
 	               changes, old_data);
diff --git a/src/nm-config.h b/src/nm-config.h
index 42ab4682..5d027ce0 100644
--- a/src/nm-config.h
+++ b/src/nm-config.h
@@ -57,7 +57,6 @@
 
 #define NM_CONFIG_KEYFILE_GROUP_KEYFILE                     "keyfile"
 #define NM_CONFIG_KEYFILE_GROUP_IFUPDOWN                    "ifupdown"
-#define NM_CONFIG_KEYFILE_GROUP_IFNET                       "ifnet"
 
 #define NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT              "auth-polkit"
 #define NM_CONFIG_KEYFILE_KEY_MAIN_AUTOCONNECT_RETRIES_DEFAULT "autoconnect-retries-default"
@@ -79,6 +78,8 @@
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED                "managed"
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER         "ignore-carrier"
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS          "sriov-num-vfs"
+#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND           "wifi.backend"
+#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS "wifi.scan-rand-mac-address"
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT   "carrier-wait-timeout"
 
 #define NM_CONFIG_KEYFILE_KEYPREFIX_WAS                     ".was."
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index 4ccc5719..389e72ad 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -25,22 +25,82 @@
 #include "nm-connectivity.h"
 
 #include <string.h>
+
+#if WITH_CONCHECK
 #include <curl/curl.h>
+#endif
 
+#include "c-list/src/c-list.h"
 #include "nm-config.h"
 #include "NetworkManagerUtils.h"
 
+#define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n"
+
 /*****************************************************************************/
 
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_state_to_string, int /*NMConnectivityState*/,
+	NM_UTILS_LOOKUP_DEFAULT_WARN ("???"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_UNKNOWN,  "UNKNOWN"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_NONE,     "NONE"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_LIMITED,  "LIMITED"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_PORTAL,   "PORTAL"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FULL,     "FULL"),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_ERROR,    "ERROR"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FAKE,     "FAKE"),
+);
+
+const char *
+nm_connectivity_state_to_string (NMConnectivityState state)
+{
+	return _state_to_string (state);
+}
+
+/*****************************************************************************/
+
+struct _NMConnectivityCheckHandle {
+	CList handles_lst;
+	NMConnectivity *self;
+	NMConnectivityCheckCallback callback;
+	gpointer user_data;
+
+	char *ifspec;
+
+#if WITH_CONCHECK
+	struct {
+		char *response;
+
+		CURL *curl_ehandle;
+		struct curl_slist *request_headers;
+
+		GString *recv_msg;
+	} concheck;
+#endif
+
+	guint timeout_id;
+};
+
+enum {
+	CONFIG_CHANGED,
+
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
 typedef struct {
+	CList handles_lst_head;
 	char *uri;
 	char *response;
 	gboolean enabled;
 	guint interval;
 	NMConfig *config;
-	guint periodic_check_id;
-	CURLM *curl_mhandle;
-	guint curl_timer;
+#if WITH_CONCHECK
+	struct {
+		CURLM *curl_mhandle;
+		guint curl_timer;
+	} concheck;
+#endif
 } NMConnectivityPrivate;
 
 struct _NMConnectivity {
@@ -58,14 +118,6 @@ G_DEFINE_TYPE (NMConnectivity, nm_connectivity, G_TYPE_OBJECT)
 
 NM_DEFINE_SINGLETON_GETTER (NMConnectivity, nm_connectivity_get, NM_TYPE_CONNECTIVITY);
 
-enum {
-	PERIODIC_CHECK,
-
-	LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
 /*****************************************************************************/
 
 #define _NMLOG_DOMAIN      LOGD_CONCHECK
@@ -78,116 +130,172 @@ static guint signals[LAST_SIGNAL] = { 0 };
         \
         if (nm_logging_enabled (__level, _NMLOG2_DOMAIN)) { \
             _nm_log (__level, _NMLOG2_DOMAIN, 0, \
-                        &cb_data->ifspec[3], NULL, \
-                        "connectivity: (%s) " \
-                        _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                        &cb_data->ifspec[3] \
-                        _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+                     (cb_data->ifspec ? &cb_data->ifspec[3] : NULL), \
+                     NULL, \
+                     "connectivity: (%s) " \
+                     _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                     (cb_data->ifspec ? &cb_data->ifspec[3] : "") \
+                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
         } \
     } G_STMT_END
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE (nm_connectivity_state_to_string, NMConnectivityState,
-	NM_UTILS_LOOKUP_DEFAULT_WARN ("???"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_UNKNOWN,  "UNKNOWN"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_NONE,     "NONE"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_LIMITED,  "LIMITED"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_PORTAL,   "PORTAL"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FULL,     "FULL"),
-);
+static void
+cb_data_invoke_callback (NMConnectivityCheckHandle *cb_data,
+                         NMConnectivityState state,
+                         GError *error,
+                         const char *log_message)
+{
+	NMConnectivityCheckCallback callback;
 
-/*****************************************************************************/
+	nm_assert (cb_data);
+	nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
 
-typedef struct {
-	GSimpleAsyncResult *simple;
-	char *response;
-	CURL *curl_ehandle;
-	size_t msg_size;
-	char *msg;
-	struct curl_slist *request_headers;
-	guint timeout_id;
-	char *ifspec;
-} ConCheckCbData;
+	callback = cb_data->callback;
+	if (!callback)
+		return;
+
+	cb_data->callback = NULL;
+
+	nm_assert (log_message);
+
+	_LOG2D ("check completed: %s; %s",
+	        nm_connectivity_state_to_string (state),
+	        log_message);
+
+	callback (cb_data->self,
+	          cb_data,
+	          state,
+	          error,
+	          cb_data->user_data);
+}
 
 static void
-finish_cb_data (ConCheckCbData *cb_data, NMConnectivityState new_state)
+cb_data_free (NMConnectivityCheckHandle *cb_data,
+              NMConnectivityState state,
+              GError *error,
+              const char *log_message)
 {
-	/* Contrary to what cURL manual claim it is *not* safe to remove
-	 * the easy handle "at any moment"; specifically not from the
-	 * write function. Thus here we just dissociate the cb_data from
-	 * the easy handle and the easy handle will be cleaned up when the
-	 * message goes to CURLMSG_DONE in curl_check_connectivity(). */
-	curl_easy_setopt (cb_data->curl_ehandle, CURLOPT_PRIVATE, NULL);
-
-	g_simple_async_result_set_op_res_gssize (cb_data->simple, new_state);
-	g_simple_async_result_complete (cb_data->simple);
-	g_object_unref (cb_data->simple);
-	curl_slist_free_all (cb_data->request_headers);
-	g_free (cb_data->response);
-	g_free (cb_data->msg);
+	NMConnectivity *self;
+
+	nm_assert (cb_data);
+
+	self = cb_data->self;
+
+	nm_assert (NM_IS_CONNECTIVITY (self));
+
+	c_list_unlink (&cb_data->handles_lst);
+
+#if WITH_CONCHECK
+	if (cb_data->concheck.curl_ehandle) {
+		NMConnectivityPrivate *priv;
+
+		/* Contrary to what cURL manual claim it is *not* safe to remove
+		 * the easy handle "at any moment"; specifically not from the
+		 * write function. Thus here we just dissociate the cb_data from
+		 * the easy handle and the easy handle will be cleaned up when the
+		 * message goes to CURLMSG_DONE in curl_check_connectivity(). */
+		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_WRITEFUNCTION, NULL);
+		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_WRITEDATA, NULL);
+		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_HEADERFUNCTION, NULL);
+		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_HEADERDATA, NULL);
+		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_PRIVATE, NULL);
+		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_HTTPHEADER, NULL);
+
+		priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+
+		curl_multi_remove_handle (priv->concheck.curl_mhandle, cb_data->concheck.curl_ehandle);
+		curl_easy_cleanup (cb_data->concheck.curl_ehandle);
+
+		curl_slist_free_all (cb_data->concheck.request_headers);
+	}
+#endif
+
+	nm_clear_g_source (&cb_data->timeout_id);
+
+	cb_data_invoke_callback (cb_data, state, error, log_message);
+
+#if WITH_CONCHECK
+	g_free (cb_data->concheck.response);
+	if (cb_data->concheck.recv_msg)
+		g_string_free (cb_data->concheck.recv_msg, TRUE);
+#endif
 	g_free (cb_data->ifspec);
-	g_source_remove (cb_data->timeout_id);
-	g_slice_free (ConCheckCbData, cb_data);
+	g_slice_free (NMConnectivityCheckHandle, cb_data);
+}
+
+/*****************************************************************************/
+
+#if WITH_CONCHECK
+static const char *
+_check_handle_get_response (NMConnectivityCheckHandle *cb_data)
+{
+	return cb_data->concheck.response ?: NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE;
 }
 
 static void
-curl_check_connectivity (CURLM *mhandle, CURLMcode ret)
+curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 {
-	ConCheckCbData *cb_data;
+	NMConnectivityCheckHandle *cb_data;
 	CURLMsg *msg;
 	CURLcode eret;
 	gint m_left;
+	long response_code;
+	CURLMcode ret;
+	int running_handles;
 
+	ret = curl_multi_socket_action (mhandle, sockfd, ev_bitmask, &running_handles);
 	if (ret != CURLM_OK)
-		_LOGW ("connectivity check failed");
+		_LOGE ("connectivity check failed: %d", ret);
 
 	while ((msg = curl_multi_info_read (mhandle, &m_left))) {
+
 		if (msg->msg != CURLMSG_DONE)
 			continue;
 
 		/* Here we have completed a session. Check easy session result. */
 		eret = curl_easy_getinfo (msg->easy_handle, CURLINFO_PRIVATE, (char **) &cb_data);
 		if (eret != CURLE_OK) {
-			_LOG2E ("curl cannot extract cb_data for easy handle %p, skipping msg", msg->easy_handle);
+			_LOGE ("curl cannot extract cb_data for easy handle, skipping msg");
 			continue;
 		}
 
-		if (cb_data) {
-			/* If cb_data is still there this message hasn't been
-			 * taken care of. Do so now. */
-			if (msg->data.result == CURLE_OK) {
-				/* If we get here, it means that easy_write_cb() didn't read enough
-				 * bytes to be able to do a match. */
-				_LOG2I ("response shorter than expected '%s'; assuming captive portal.",
-				        cb_data->response);
-				finish_cb_data (cb_data, NM_CONNECTIVITY_PORTAL);
-			} else {
-				_LOG2D ("check failed (%d)", msg->data.result);
-				finish_cb_data (cb_data, NM_CONNECTIVITY_LIMITED);
-			}
+		if (!cb_data->callback) {
+			/* callback was already invoked earlier. */
+			cb_data_free (cb_data, NM_CONNECTIVITY_UNKNOWN, NULL, NULL);
+		} else if (msg->data.result != CURLE_OK) {
+			gs_free char *log_message = NULL;
+
+			log_message = g_strdup_printf ("check failed with curl status %d", msg->data.result);
+			cb_data_free (cb_data, NM_CONNECTIVITY_LIMITED, NULL,
+			              log_message);
+		} else if (   !((_check_handle_get_response (cb_data))[0])
+		           && (curl_easy_getinfo (msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code) == CURLE_OK)
+		           && response_code == 204) {
+			/* If we got a 204 response code (no content) and we actually
+			 * requested no content, report full connectivity. */
+			cb_data_free (cb_data, NM_CONNECTIVITY_FULL, NULL,
+			              "no content, as expected");
+		} else {
+			/* If we get here, it means that easy_write_cb() didn't read enough
+			 * bytes to be able to do a match, or that we were asking for no content
+			 * (204 response code) and we actually got some. Either way, that is
+			 * an indication of a captive portal */
+			cb_data_free (cb_data, NM_CONNECTIVITY_PORTAL, NULL,
+			              "unexpected short response");
 		}
-
-		curl_multi_remove_handle (mhandle, msg->easy_handle);
-		curl_easy_cleanup (msg->easy_handle);
 	}
 }
 
 static gboolean
 curl_timeout_cb (gpointer user_data)
 {
-	NMConnectivity *self = NM_CONNECTIVITY (user_data);
+	gs_unref_object NMConnectivity *self = g_object_ref (NM_CONNECTIVITY (user_data));
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-	CURLMcode ret;
-	int pending_conn;
-
-	priv->curl_timer = 0;
-
-	ret = curl_multi_socket_action (priv->curl_mhandle, CURL_SOCKET_TIMEOUT, 0, &pending_conn);
-	_LOGT ("timeout elapsed - multi_socket_action (%d conn remaining)", pending_conn);
-
-	curl_check_connectivity (priv->curl_mhandle, ret);
 
+	priv->concheck.curl_timer = 0;
+	curl_check_connectivity (priv->concheck.curl_mhandle, CURL_SOCKET_TIMEOUT, 0);
 	return G_SOURCE_REMOVE;
 }
 
@@ -197,21 +305,17 @@ multi_timer_cb (CURLM *multi, long timeout_ms, void *userdata)
 	NMConnectivity *self = NM_CONNECTIVITY (userdata);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->curl_timer);
+	nm_clear_g_source (&priv->concheck.curl_timer);
 	if (timeout_ms != -1)
-		priv->curl_timer = g_timeout_add (timeout_ms * 1000, curl_timeout_cb, self);
-
+		priv->concheck.curl_timer = g_timeout_add (timeout_ms, curl_timeout_cb, self);
 	return 0;
 }
 
 static gboolean
-curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer data)
+curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_data)
 {
-	NMConnectivity *self = NM_CONNECTIVITY (data);
+	gs_unref_object NMConnectivity *self = g_object_ref (NM_CONNECTIVITY (user_data));
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-	CURLMcode ret;
-	int pending_conn = 0;
-	gboolean bret = TRUE;
 	int fd = g_io_channel_unix_get_fd (ch);
 	int action = 0;
 
@@ -219,16 +323,11 @@ curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer data)
 		action |= CURL_CSELECT_IN;
 	if (condition & G_IO_OUT)
 		action |= CURL_CSELECT_OUT;
+	if (condition & G_IO_ERR)
+		action |= CURL_CSELECT_ERR;
 
-	ret = curl_multi_socket_action (priv->curl_mhandle, fd, 0, &pending_conn);
-
-	curl_check_connectivity (priv->curl_mhandle, ret);
-
-	if (pending_conn == 0) {
-		nm_clear_g_source (&priv->curl_timer);
-		bret = FALSE;
-	}
-	return bret;
+	curl_check_connectivity (priv->concheck.curl_mhandle, fd, action);
+	return G_SOURCE_CONTINUE;
 }
 
 typedef struct {
@@ -241,11 +340,12 @@ multi_socket_cb (CURL *e_handle, curl_socket_t s, int what, void *userdata, void
 {
 	NMConnectivity *self = NM_CONNECTIVITY (userdata);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-	CurlSockData *fdp = (CurlSockData *) socketp;
+	CurlSockData *fdp = socketp;
 	GIOCondition condition = 0;
 
 	if (what == CURL_POLL_REMOVE) {
 		if (fdp) {
+			curl_multi_assign (priv->concheck.curl_mhandle, s, NULL);
 			nm_clear_g_source (&fdp->ev);
 			g_io_channel_unref (fdp->ch);
 			g_slice_free (CurlSockData, fdp);
@@ -254,33 +354,34 @@ multi_socket_cb (CURL *e_handle, curl_socket_t s, int what, void *userdata, void
 		if (!fdp) {
 			fdp = g_slice_new0 (CurlSockData);
 			fdp->ch = g_io_channel_unix_new (s);
+			curl_multi_assign (priv->concheck.curl_mhandle, s, fdp);
 		} else
 			nm_clear_g_source (&fdp->ev);
 
-		if (what & CURL_POLL_IN)
-			condition |= G_IO_IN;
-		if (what & CURL_POLL_OUT)
-			condition |= G_IO_OUT;
+		if (what == CURL_POLL_IN)
+			condition = G_IO_IN;
+		else if (what == CURL_POLL_OUT)
+			condition = G_IO_OUT;
+		else if (what == CURL_POLL_INOUT)
+			condition = G_IO_IN | G_IO_OUT;
 
-		fdp->ev = g_io_add_watch (fdp->ch, condition, curl_socketevent_cb, self);
-		curl_multi_assign (priv->curl_mhandle, s, fdp);
+		if (condition)
+			fdp->ev = g_io_add_watch (fdp->ch, condition, curl_socketevent_cb, self);
 	}
 
 	return CURLM_OK;
 }
 
-#define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n"
-
 static size_t
 easy_header_cb (char *buffer, size_t size, size_t nitems, void *userdata)
 {
-	ConCheckCbData *cb_data = userdata;
+	NMConnectivityCheckHandle *cb_data = userdata;
 	size_t len = size * nitems;
 
 	if (   len >= sizeof (HEADER_STATUS_ONLINE) - 1
 	    && !g_ascii_strncasecmp (buffer, HEADER_STATUS_ONLINE, sizeof (HEADER_STATUS_ONLINE) - 1)) {
-		_LOG2D ("status header found, check successful");
-		finish_cb_data (cb_data, NM_CONNECTIVITY_FULL);
+		cb_data_invoke_callback (cb_data, NM_CONNECTIVITY_FULL,
+		                         NULL, "status header found");
 		return 0;
 	}
 
@@ -290,23 +391,26 @@ easy_header_cb (char *buffer, size_t size, size_t nitems, void *userdata)
 static size_t
 easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
 {
-	ConCheckCbData *cb_data = userdata;
+	NMConnectivityCheckHandle *cb_data = userdata;
 	size_t len = size * nmemb;
+	const char *response = _check_handle_get_response (cb_data);;
+
+	if (!cb_data->concheck.recv_msg)
+		cb_data->concheck.recv_msg = g_string_sized_new (len + 10);
 
-	cb_data->msg = g_realloc (cb_data->msg, cb_data->msg_size + len);
-	memcpy (cb_data->msg + cb_data->msg_size, buffer, len);
-	cb_data->msg_size += len;
+	g_string_append_len (cb_data->concheck.recv_msg, buffer, len);
 
-	if (cb_data->msg_size >= strlen (cb_data->response)) {
+	if (   response
+	    && cb_data->concheck.recv_msg->len >= strlen (response)) {
 		/* We already have enough data -- check response */
-		if (g_str_has_prefix (cb_data->msg, cb_data->response)) {
-			_LOG2D ("check successful.");
-			finish_cb_data (cb_data, NM_CONNECTIVITY_FULL);
+		if (g_str_has_prefix (cb_data->concheck.recv_msg->str, response)) {
+			cb_data_invoke_callback (cb_data, NM_CONNECTIVITY_FULL, NULL,
+			                         "expected response");
 		} else {
-			_LOG2I ("response did not match expected response '%s'; assuming captive portal.",
-			        cb_data->response);
-			finish_cb_data (cb_data, NM_CONNECTIVITY_PORTAL);
+			cb_data_invoke_callback (cb_data, NM_CONNECTIVITY_PORTAL, NULL,
+			                         "unexpected response");
 		}
+
 		return 0;
 	}
 
@@ -314,105 +418,139 @@ easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
 }
 
 static gboolean
-timeout_cb (gpointer user_data)
+_timeout_cb (gpointer user_data)
 {
-	ConCheckCbData *cb_data = user_data;
-	NMConnectivity *self = NM_CONNECTIVITY (g_async_result_get_source_object (G_ASYNC_RESULT (cb_data->simple)));
-	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-	CURL *ehandle = cb_data->curl_ehandle;
+	NMConnectivityCheckHandle *cb_data = user_data;
+	NMConnectivity *self;
+
+	nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
 
-	_LOG2I ("timed out");
-	finish_cb_data (cb_data, NM_CONNECTIVITY_LIMITED);
-	curl_multi_remove_handle (priv->curl_mhandle, ehandle);
-	curl_easy_cleanup (ehandle);
+	self = cb_data->self;
 
+	nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (self)->handles_lst_head, &cb_data->handles_lst));
+
+	cb_data_free (cb_data, NM_CONNECTIVITY_LIMITED, NULL, "timeout");
 	return G_SOURCE_REMOVE;
 }
+#endif
 
-void
-nm_connectivity_check_async (NMConnectivity      *self,
-                             const char          *iface,
-                             GAsyncReadyCallback  callback,
-                             gpointer             user_data)
+static gboolean
+_idle_cb (gpointer user_data)
 {
-	NMConnectivityPrivate *priv;
-	GSimpleAsyncResult *simple;
-	CURL *ehandle = NULL;
+	NMConnectivityCheckHandle *cb_data = user_data;
 
-	g_return_if_fail (NM_IS_CONNECTIVITY (self));
-	priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	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));
 
-	simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
-	                                    nm_connectivity_check_async);
+	cb_data->timeout_id = 0;
+	if (!cb_data->ifspec) {
+		gs_free_error GError *error = NULL;
 
-	if (priv->enabled)
-		ehandle = curl_easy_init ();
+		/* 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_free (cb_data, NM_CONNECTIVITY_ERROR, NULL, "missing interface");
+	} else
+		cb_data_free (cb_data, NM_CONNECTIVITY_FAKE, NULL, "fake result");
+	return G_SOURCE_REMOVE;
+}
 
-	if (ehandle) {
-		ConCheckCbData *cb_data = g_slice_new0 (ConCheckCbData);
+NMConnectivityCheckHandle *
+nm_connectivity_check_start (NMConnectivity *self,
+                             const char *iface,
+                             NMConnectivityCheckCallback callback,
+                             gpointer user_data)
+{
+	NMConnectivityPrivate *priv;
+	NMConnectivityCheckHandle *cb_data;
 
-		cb_data->curl_ehandle = ehandle;
-		cb_data->request_headers = curl_slist_append (NULL, "Connection: close");
+	g_return_val_if_fail (NM_IS_CONNECTIVITY (self), NULL);
+	g_return_val_if_fail (!iface || iface[0], NULL);
+	g_return_val_if_fail (callback, NULL);
+
+	priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+
+	cb_data = g_slice_new0 (NMConnectivityCheckHandle);
+	cb_data->self = self;
+	c_list_link_tail (&priv->handles_lst_head, &cb_data->handles_lst);
+	cb_data->callback = callback;
+	cb_data->user_data = user_data;
+
+	if (iface)
 		cb_data->ifspec = g_strdup_printf ("if!%s", iface);
-		cb_data->simple = simple;
-		if (priv->response)
-			cb_data->response = g_strdup (priv->response);
-		else
-			cb_data->response = g_strdup (NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE);
-
-		curl_easy_setopt (ehandle, CURLOPT_URL, priv->uri);
-		curl_easy_setopt (ehandle, CURLOPT_WRITEFUNCTION, easy_write_cb);
-		curl_easy_setopt (ehandle, CURLOPT_WRITEDATA, cb_data);
-		curl_easy_setopt (ehandle, CURLOPT_HEADERFUNCTION, easy_header_cb);
-		curl_easy_setopt (ehandle, CURLOPT_HEADERDATA, cb_data);
-		curl_easy_setopt (ehandle, CURLOPT_PRIVATE, cb_data);
-		curl_easy_setopt (ehandle, CURLOPT_HTTPHEADER, cb_data->request_headers);
-		curl_easy_setopt (ehandle, CURLOPT_INTERFACE, cb_data->ifspec);
-		curl_multi_add_handle (priv->curl_mhandle, ehandle);
-
-		cb_data->timeout_id = g_timeout_add_seconds (30, timeout_cb, cb_data);
-
-		_LOG2D ("sending request to '%s'", priv->uri);
-		return;
-	} else {
-		_LOGD ("(%s) faking request. Connectivity check disabled", iface);
+
+#if WITH_CONCHECK
+	if (iface) {
+		CURL *ehandle;
+
+		if (   priv->enabled
+		    && (ehandle = curl_easy_init ())) {
+
+			cb_data->concheck.response = g_strdup (priv->response);
+			cb_data->concheck.curl_ehandle = ehandle;
+			cb_data->concheck.request_headers = curl_slist_append (NULL, "Connection: close");
+			curl_easy_setopt (ehandle, CURLOPT_URL, priv->uri);
+			curl_easy_setopt (ehandle, CURLOPT_WRITEFUNCTION, easy_write_cb);
+			curl_easy_setopt (ehandle, CURLOPT_WRITEDATA, cb_data);
+			curl_easy_setopt (ehandle, CURLOPT_HEADERFUNCTION, easy_header_cb);
+			curl_easy_setopt (ehandle, CURLOPT_HEADERDATA, cb_data);
+			curl_easy_setopt (ehandle, CURLOPT_PRIVATE, cb_data);
+			curl_easy_setopt (ehandle, CURLOPT_HTTPHEADER, cb_data->concheck.request_headers);
+			curl_easy_setopt (ehandle, CURLOPT_INTERFACE, cb_data->ifspec);
+			curl_multi_add_handle (priv->concheck.curl_mhandle, ehandle);
+
+			cb_data->timeout_id = g_timeout_add_seconds (20, _timeout_cb, cb_data);
+
+			_LOG2D ("start request to '%s'", priv->uri);
+			return cb_data;
+		}
 	}
+#endif
 
-	g_simple_async_result_set_op_res_gssize (simple, NM_CONNECTIVITY_UNKNOWN);
-	g_simple_async_result_complete_in_idle (simple);
-	g_object_unref (simple);
+	_LOG2D ("start fake request");
+	cb_data->timeout_id = g_idle_add (_idle_cb, cb_data);
+	return cb_data;
 }
 
-NMConnectivityState
-nm_connectivity_check_finish (NMConnectivity  *self,
-                              GAsyncResult    *result,
-                              GError         **error)
+void
+nm_connectivity_check_cancel (NMConnectivityCheckHandle *cb_data)
 {
-	GSimpleAsyncResult *simple;
+	NMConnectivity *self;
+	gs_free_error GError *error = NULL;
+
+	g_return_if_fail (cb_data);
+
+	self = cb_data->self;
 
-	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), nm_connectivity_check_async), NM_CONNECTIVITY_UNKNOWN);
+	g_return_if_fail (NM_IS_CONNECTIVITY (self));
+	g_return_if_fail (!c_list_is_empty (&cb_data->handles_lst));
+	g_return_if_fail (cb_data->callback);
+
+	nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (self)->handles_lst_head, &cb_data->handles_lst));
+
+	nm_utils_error_set_cancelled (&error, FALSE, "NMConnectivity");
 
-	simple = G_SIMPLE_ASYNC_RESULT (result);
-	if (g_simple_async_result_propagate_error (simple, error))
-		return NM_CONNECTIVITY_UNKNOWN;
-	return (NMConnectivityState) g_simple_async_result_get_op_res_gssize (simple);
+	cb_data_free (cb_data, NM_CONNECTIVITY_ERROR, error, "cancelled");
 }
 
+/*****************************************************************************/
+
 gboolean
 nm_connectivity_check_enabled (NMConnectivity *self)
 {
-	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	g_return_val_if_fail (NM_IS_CONNECTIVITY (self), FALSE);
 
-	return priv->enabled;
+	return NM_CONNECTIVITY_GET_PRIVATE (self)->enabled;
 }
 
 /*****************************************************************************/
 
-static gboolean
-periodic_check (gpointer user_data)
+guint
+nm_connectivity_get_interval (NMConnectivity *self)
 {
-	g_signal_emit (NM_CONNECTIVITY (user_data), signals[PERIODIC_CHECK], 0);
-	return G_SOURCE_CONTINUE;
+	return nm_connectivity_check_enabled (self)
+	       ? NM_CONNECTIVITY_GET_PRIVATE (self)->interval
+	       : 0;
 }
 
 static void
@@ -452,18 +590,22 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 
 	/* Set the interval. */
 	interval = nm_config_data_get_connectivity_interval (config_data);
+	interval = MIN (interval, (7 * 24 * 3600));
 	if (priv->interval != interval) {
 		priv->interval = interval;
 		changed = TRUE;
 	}
 
-	/* Set enabled flag. */
-	enabled = nm_config_data_get_connectivity_enabled (config_data);
+	enabled = FALSE;
+#if WITH_CONCHECK
 	/* connectivity checking also requires a valid URI, interval and
 	 * curl_mhandle */
-	if (!(priv->uri && priv->interval && priv->curl_mhandle)) {
-		enabled = FALSE;
-	}
+	if (   priv->uri
+	    && priv->interval
+	    && priv->concheck.curl_mhandle)
+		enabled = nm_config_data_get_connectivity_enabled (config_data);
+#endif
+
 	if (priv->enabled != enabled) {
 		priv->enabled = enabled;
 		changed = TRUE;
@@ -471,7 +613,7 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 
 	/* Set the response. */
 	response = nm_config_data_get_connectivity_response (config_data);
-	if (g_strcmp0 (response, priv->response) != 0) {
+	if (!nm_streq0 (response, priv->response)) {
 		/* a response %NULL means, NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE. Any other response
 		 * (including "") is accepted. */
 		g_free (priv->response);
@@ -479,11 +621,8 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 		changed = TRUE;
 	}
 
-	if (changed) {
-		nm_clear_g_source (&priv->periodic_check_id);
-		if (nm_connectivity_check_enabled (self))
-			priv->periodic_check_id = g_timeout_add_seconds (priv->interval, periodic_check, self);
-	}
+	if (changed)
+		g_signal_emit (self, signals[CONFIG_CHANGED], 0);
 }
 
 static void
@@ -496,34 +635,37 @@ config_changed_cb (NMConfig *config,
 	update_config (self, config_data);
 }
 
+/*****************************************************************************/
+
 static void
 nm_connectivity_init (NMConnectivity *self)
 {
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-	CURLcode retv;
 
-	retv = curl_global_init (CURL_GLOBAL_ALL);
-	if (retv == CURLE_OK)
-		priv->curl_mhandle = curl_multi_init ();
-
-	if (!priv->curl_mhandle)
-		 _LOGE ("unable to init cURL, connectivity check will not work");
-	else {
-		curl_multi_setopt (priv->curl_mhandle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
-		curl_multi_setopt (priv->curl_mhandle, CURLMOPT_SOCKETDATA, self);
-		curl_multi_setopt (priv->curl_mhandle, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
-		curl_multi_setopt (priv->curl_mhandle, CURLMOPT_TIMERDATA, self);
-		curl_multi_setopt (priv->curl_mhandle, CURLOPT_VERBOSE, 1);
-	}
+	c_list_init (&priv->handles_lst_head);
 
 	priv->config = g_object_ref (nm_config_get ());
-
-	update_config (self, nm_config_get_data (priv->config));
 	g_signal_connect (G_OBJECT (priv->config),
 	                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
 	                  G_CALLBACK (config_changed_cb),
 	                  self);
 
+#if WITH_CONCHECK
+	if (curl_global_init (CURL_GLOBAL_ALL) == CURLE_OK)
+		priv->concheck.curl_mhandle = curl_multi_init ();
+
+	if (!priv->concheck.curl_mhandle)
+		 _LOGE ("unable to init cURL, connectivity check will not work");
+	else {
+		curl_multi_setopt (priv->concheck.curl_mhandle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
+		curl_multi_setopt (priv->concheck.curl_mhandle, CURLMOPT_SOCKETDATA, self);
+		curl_multi_setopt (priv->concheck.curl_mhandle, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
+		curl_multi_setopt (priv->concheck.curl_mhandle, CURLMOPT_TIMERDATA, self);
+		curl_multi_setopt (priv->concheck.curl_mhandle, CURLOPT_VERBOSE, 1);
+	}
+#endif
+
+	update_config (self, nm_config_get_data (priv->config));
 }
 
 static void
@@ -531,19 +673,33 @@ dispose (GObject *object)
 {
 	NMConnectivity *self = NM_CONNECTIVITY (object);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	NMConnectivityCheckHandle *cb_data;
+	GError *error = NULL;
+
+again:
+	c_list_for_each_entry (cb_data, &priv->handles_lst_head, handles_lst) {
+		if (!error)
+			nm_utils_error_set_cancelled (&error, TRUE, "NMConnectivity");
+		cb_data_free (cb_data, NM_CONNECTIVITY_ERROR, error, "shutting down");
+		goto again;
+	}
+	g_clear_error (&error);
 
 	g_clear_pointer (&priv->uri, g_free);
 	g_clear_pointer (&priv->response, g_free);
 
+#if WITH_CONCHECK
+	nm_clear_g_source (&priv->concheck.curl_timer);
+
+	curl_multi_cleanup (priv->concheck.curl_mhandle);
+	curl_global_cleanup ();
+#endif
+
 	if (priv->config) {
 		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
 		g_clear_object (&priv->config);
 	}
 
-	curl_multi_cleanup (priv->curl_mhandle);
-	curl_global_cleanup ();
-	nm_clear_g_source (&priv->periodic_check_id);
-
 	G_OBJECT_CLASS (nm_connectivity_parent_class)->dispose (object);
 }
 
@@ -552,8 +708,8 @@ nm_connectivity_class_init (NMConnectivityClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-	signals[PERIODIC_CHECK] =
-	    g_signal_new (NM_CONNECTIVITY_PERIODIC_CHECK,
+	signals[CONFIG_CHANGED] =
+	    g_signal_new (NM_CONNECTIVITY_CONFIG_CHANGED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_FIRST,
 	                  0, NULL, NULL, NULL,
diff --git a/src/nm-connectivity.h b/src/nm-connectivity.h
index d9a9f233..df9295e0 100644
--- a/src/nm-connectivity.h
+++ b/src/nm-connectivity.h
@@ -24,6 +24,9 @@
 
 #include "nm-dbus-interface.h"
 
+#define NM_CONNECTIVITY_ERROR ((NMConnectivityState) -1)
+#define NM_CONNECTIVITY_FAKE  ((NMConnectivityState) -2)
+
 #define NM_TYPE_CONNECTIVITY            (nm_connectivity_get_type ())
 #define NM_CONNECTIVITY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_CONNECTIVITY, NMConnectivity))
 #define NM_CONNECTIVITY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_CONNECTIVITY, NMConnectivityClass))
@@ -31,7 +34,7 @@
 #define NM_IS_CONNECTIVITY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_CONNECTIVITY))
 #define NM_CONNECTIVITY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_CONNECTIVITY, NMConnectivityClass))
 
-#define NM_CONNECTIVITY_PERIODIC_CHECK  "nm-connectivity-periodic-check"
+#define NM_CONNECTIVITY_CONFIG_CHANGED  "config-changed"
 
 typedef struct _NMConnectivityClass NMConnectivityClass;
 
@@ -41,13 +44,23 @@ NMConnectivity *nm_connectivity_get (void);
 
 const char *nm_connectivity_state_to_string (NMConnectivityState state);
 
-void                 nm_connectivity_check_async  (NMConnectivity       *self,
-                                                   const char           *iface,
-                                                   GAsyncReadyCallback   callback,
-                                                   gpointer              user_data);
-NMConnectivityState  nm_connectivity_check_finish (NMConnectivity       *self,
-                                                   GAsyncResult         *result,
-                                                   GError              **error);
 gboolean nm_connectivity_check_enabled (NMConnectivity *self);
 
+guint nm_connectivity_get_interval (NMConnectivity *self);
+
+typedef struct _NMConnectivityCheckHandle NMConnectivityCheckHandle;
+
+typedef void (*NMConnectivityCheckCallback) (NMConnectivity *self,
+                                             NMConnectivityCheckHandle *handle,
+                                             NMConnectivityState state,
+                                             GError *error,
+                                             gpointer user_data);
+
+NMConnectivityCheckHandle *nm_connectivity_check_start (NMConnectivity *self,
+                                                        const char *iface,
+                                                        NMConnectivityCheckCallback callback,
+                                                        gpointer user_data);
+
+void nm_connectivity_check_cancel (NMConnectivityCheckHandle *handle);
+
 #endif /* __NETWORKMANAGER_CONNECTIVITY_H__ */
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index f6b33a14..b97be0b8 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -452,83 +452,6 @@ nm_utils_modprobe (GError **error, gboolean suppress_error_logging, const char *
 	return exit_status;
 }
 
-/**
- * nm_utils_get_start_time_for_pid:
- * @pid: the process identifier
- * @out_state: return the state character, like R, S, Z. See `man 5 proc`.
- * @out_ppid: parent process id
- *
- * Originally copied from polkit source (src/polkit/polkitunixprocess.c)
- * and adjusted.
- *
- * Returns: the timestamp when the process started (by parsing /proc/$PID/stat).
- * If an error occurs (e.g. the process does not exist), 0 is returned.
- *
- * The returned start time counts since boot, in the unit HZ (with HZ usually being (1/100) seconds)
- **/
-guint64
-nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid)
-{
-	guint64 start_time;
-	char filename[256];
-	gs_free gchar *contents = NULL;
-	size_t length;
-	gs_strfreev gchar **tokens = NULL;
-	guint num_tokens;
-	gchar *p;
-	char state = ' ';
-	gint64 ppid = 0;
-
-	start_time = 0;
-	contents = NULL;
-
-	g_return_val_if_fail (pid > 0, 0);
-
-	nm_sprintf_buf (filename, "/proc/%"G_GUINT64_FORMAT"/stat", (guint64) pid);
-
-	if (!g_file_get_contents (filename, &contents, &length, NULL))
-		goto fail;
-
-	/* start time is the token at index 19 after the '(process name)' entry - since only this
-	 * field can contain the ')' character, search backwards for this to avoid malicious
-	 * processes trying to fool us
-	 */
-	p = strrchr (contents, ')');
-	if (p == NULL)
-		goto fail;
-	p += 2; /* skip ') ' */
-	if (p - contents >= (int) length)
-		goto fail;
-
-	state = p[0];
-
-	tokens = g_strsplit (p, " ", 0);
-
-	num_tokens = g_strv_length (tokens);
-
-	if (num_tokens < 20)
-		goto fail;
-
-	if (out_ppid) {
-		ppid = _nm_utils_ascii_str_to_int64 (tokens[1], 10, 1, G_MAXINT, 0);
-		if (ppid == 0)
-			goto fail;
-	}
-
-	start_time = _nm_utils_ascii_str_to_int64 (tokens[19], 10, 1, G_MAXINT64, 0);
-	if (start_time == 0)
-		goto fail;
-
-	NM_SET_OUT (out_state, state);
-	NM_SET_OUT (out_ppid, ppid);
-	return start_time;
-
-fail:
-	NM_SET_OUT (out_state, ' ');
-	NM_SET_OUT (out_ppid, 0);
-	return 0;
-}
-
 /*****************************************************************************/
 
 typedef struct {
@@ -1246,8 +1169,8 @@ typedef struct {
 static gboolean
 match_device_s390_subchannels_parse (const char *s390_subchannels, guint32 *out_a, guint32 *out_b, guint32 *out_c)
 {
-	const int BUFSIZE = 30;
-	char buf[BUFSIZE + 1];
+	char buf[30 + 1];
+	const int BUFSIZE = G_N_ELEMENTS (buf) - 1;
 	guint i = 0;
 	char *pa = NULL, *pb = NULL, *pc = NULL;
 	gint64 a, b, c;
@@ -1877,110 +1800,6 @@ nm_utils_new_infiniband_name (char *name, const char *parent_name, int p_key)
 
 /*****************************************************************************/
 
-gboolean
-nm_utils_resolve_conf_parse (int addr_family,
-                             const char *rc_contents,
-                             GArray *nameservers,
-                             GPtrArray *dns_options)
-{
-	guint i;
-	gboolean changed = FALSE;
-	gs_free const char **lines = NULL;
-	gsize l;
-
-	g_return_val_if_fail (rc_contents, FALSE);
-	g_return_val_if_fail (nameservers, FALSE);
-	g_return_val_if_fail (   (   addr_family == AF_INET
-	                          && g_array_get_element_size (nameservers) == sizeof (in_addr_t))
-	                      || (   addr_family == AF_INET6
-	                          && g_array_get_element_size (nameservers) == sizeof (struct in6_addr)), FALSE);
-
-	lines = nm_utils_strsplit_set (rc_contents, "\r\n");
-	if (!lines)
-		return FALSE;
-
-/* like glibc's MATCH() macro in resolv/res_init.c. */
-#define RC_MATCH(line, option, out_arg) \
-    ({ \
-        const char *const _line = (line); \
-        gboolean _match = FALSE; \
-        \
-        if (   (strncmp (_line, option, NM_STRLEN (option)) == 0) \
-            && (NM_IN_SET (_line[NM_STRLEN (option)], ' ', '\t'))) { \
-            _match = TRUE;\
-            (out_arg) = &_line[NM_STRLEN (option) + 1]; \
-        } \
-        _match; \
-    })
-
-	for (l = 0; lines[l]; l++) {
-		const char *const line = lines[l];
-		const char *s = NULL;
-
-		if (RC_MATCH (line, "nameserver", s)) {
-			gs_free char *s_cpy = NULL;
-			NMIPAddr ns;
-
-			s = nm_strstrip_avoid_copy (s, &s_cpy);
-			if (inet_pton (addr_family, s, &ns) != 1)
-				continue;
-
-			if (addr_family == AF_INET) {
-				if (!ns.addr4)
-					continue;
-				for (i = 0; i < nameservers->len; i++) {
-					if (g_array_index (nameservers, guint32, i) == ns.addr4)
-						break;
-				}
-			} else {
-				if (IN6_IS_ADDR_UNSPECIFIED (&ns.addr6))
-					continue;
-				for (i = 0; i < nameservers->len; i++) {
-					struct in6_addr *t = &g_array_index (nameservers, struct in6_addr, i);
-
-					if (IN6_ARE_ADDR_EQUAL (t, &ns.addr6))
-						break;
-				}
-			}
-
-			if (i == nameservers->len) {
-				g_array_append_val (nameservers, ns);
-				changed = TRUE;
-			}
-			continue;
-		}
-
-		if (RC_MATCH (line, "options", s)) {
-			if (!dns_options)
-				continue;
-
-			s = nm_str_skip_leading_spaces (s);
-			if (s[0]) {
-				gs_free const char **tokens = NULL;
-				gsize i_tokens;
-
-				tokens = nm_utils_strsplit_set (s, " \t");
-				for (i_tokens = 0; tokens && tokens[i_tokens]; i_tokens++) {
-					gs_free char *t = g_strstrip (g_strdup (tokens[i_tokens]));
-
-					if (   _nm_utils_dns_option_validate (t, NULL, NULL,
-					                                      addr_family == AF_INET6,
-					                                      _nm_utils_dns_option_descs)
-					    && _nm_utils_dns_option_find_idx (dns_options, t) < 0) {
-						g_ptr_array_add (dns_options, g_steal_pointer (&t));
-						changed = TRUE;
-					}
-				}
-			}
-			continue;
-		}
-	}
-
-	return changed;
-}
-
-/*****************************************************************************/
-
 /**
  * nm_utils_cmp_connection_by_autoconnect_priority:
  * @a:
@@ -2338,7 +2157,13 @@ _log_connection_sort_names (LogConnectionSettingData *setting_data, GArray *sort
 }
 
 void
-nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base, guint32 level, guint64 domain, const char *name, const char *prefix)
+nm_utils_log_connection_diff (NMConnection *connection,
+                              NMConnection *diff_base,
+                              guint32 level,
+                              guint64 domain,
+                              const char *name,
+                              const char *prefix,
+                              const char *dbus_path)
 {
 	GHashTable *connection_diff = NULL;
 	GArray *sorted_hashes;
@@ -2414,7 +2239,6 @@ nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base,
 
 			if (print_header) {
 				GError *err_verify = NULL;
-				const char *path = nm_connection_get_path (connection);
 				const char *t1, *t2;
 
 				t1 = nm_connection_get_connection_type (connection);
@@ -2424,12 +2248,12 @@ nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base,
 					        prefix, name,
 					        connection, G_OBJECT_TYPE_NAME (connection), NM_PRINT_FMT_QUOTE_STRING (t1),
 					        diff_base, G_OBJECT_TYPE_NAME (diff_base), NM_PRINT_FMT_QUOTE_STRING (t2),
-					        NM_PRINT_FMT_QUOTED (path, " [", path, "]", ""));
+					        NM_PRINT_FMT_QUOTED (dbus_path, " [", dbus_path, "]", ""));
 				} else {
 					nm_log (level, domain, NULL, NULL, "%sconnection '%s' (%p/%s/%s%s%s):%s%s%s",
 					        prefix, name,
 					        connection, G_OBJECT_TYPE_NAME (connection), NM_PRINT_FMT_QUOTE_STRING (t1),
-					        NM_PRINT_FMT_QUOTED (path, " [", path, "]", ""));
+					        NM_PRINT_FMT_QUOTED (dbus_path, " [", dbus_path, "]", ""));
 				}
 				print_header = FALSE;
 
@@ -2747,7 +2571,7 @@ _get_contents_error (GError **error, int errsv, const char *format, ...)
 /**
  * nm_utils_fd_get_contents:
  * @fd: open file descriptor to read. The fd will not be closed,
- *   but don't rely on it's state afterwards.
+ *   but don't rely on its state afterwards.
  * @close_fd: if %TRUE, @fd will be closed by the function.
  *  Passing %TRUE here might safe a syscall for dup().
  * @max_length: allocate at most @max_length bytes. If the
@@ -3749,7 +3573,8 @@ nm_utils_setpgid (gpointer unused G_GNUC_UNUSED)
 /**
  * nm_utils_g_value_set_strv:
  * @value: a #GValue, initialized to store a #G_TYPE_STRV
- * @strings: a #GPtrArray of strings
+ * @strings: a #GPtrArray of strings. %NULL values are not
+ *   allowed.
  *
  * Converts @strings to a #GStrv and stores it in @value.
  */
@@ -3757,11 +3582,13 @@ void
 nm_utils_g_value_set_strv (GValue *value, GPtrArray *strings)
 {
 	char **strv;
-	int i;
+	guint i;
 
 	strv = g_new (char *, strings->len + 1);
-	for (i = 0; i < strings->len; i++)
+	for (i = 0; i < strings->len; i++) {
+		nm_assert (strings->pdata[i]);
 		strv[i] = g_strdup (strings->pdata[i]);
+	}
 	strv[i] = NULL;
 
 	g_value_take_boxed (value, strv);
@@ -3891,12 +3718,11 @@ nm_utils_lifetime_rebase_relative_time_on_now (guint32 timestamp,
 	return t;
 }
 
-gboolean
+guint32
 nm_utils_lifetime_get (guint32 timestamp,
                        guint32 lifetime,
                        guint32 preferred,
                        gint32 now,
-                       guint32 *out_lifetime,
                        guint32 *out_preferred)
 {
 	guint32 t_lifetime, t_preferred;
@@ -3904,38 +3730,39 @@ nm_utils_lifetime_get (guint32 timestamp,
 	nm_assert (now >= 0);
 
 	if (timestamp == 0 && lifetime == 0) {
-		/* We treat lifetime==0 && timestamp == 0 addresses as permanent addresses to allow easy
+		/* We treat lifetime==0 && timestamp==0 addresses as permanent addresses to allow easy
 		 * creation of such addresses (without requiring to set the lifetime fields to
 		 * NM_PLATFORM_LIFETIME_PERMANENT). The real lifetime==0 addresses (E.g. DHCP6 telling us
 		 * to drop an address will have timestamp set.
 		 */
-		*out_lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
-		*out_preferred = NM_PLATFORM_LIFETIME_PERMANENT;
-		g_return_val_if_fail (preferred == 0, TRUE);
-	} else {
-		if (now <= 0)
-			now = nm_utils_get_monotonic_timestamp_s ();
-		t_lifetime = nm_utils_lifetime_rebase_relative_time_on_now (timestamp, lifetime, now);
-		if (!t_lifetime) {
-			*out_lifetime = 0;
-			*out_preferred = 0;
-			return FALSE;
-		}
-		t_preferred = nm_utils_lifetime_rebase_relative_time_on_now (timestamp, preferred, now);
+		NM_SET_OUT (out_preferred, NM_PLATFORM_LIFETIME_PERMANENT);
+		g_return_val_if_fail (preferred == 0, NM_PLATFORM_LIFETIME_PERMANENT);
+		return NM_PLATFORM_LIFETIME_PERMANENT;
+	}
 
-		*out_lifetime = t_lifetime;
-		*out_preferred = MIN (t_preferred, t_lifetime);
+	if (now <= 0)
+		now = nm_utils_get_monotonic_timestamp_s ();
 
-		/* Assert that non-permanent addresses have a (positive) @timestamp. nm_utils_lifetime_rebase_relative_time_on_now()
-		 * treats addresses with timestamp 0 as *now*. Addresses passed to _address_get_lifetime() always
-		 * should have a valid @timestamp, otherwise on every re-sync, their lifetime will be extended anew.
-		 */
-		g_return_val_if_fail (   timestamp != 0
-		                      || (   lifetime  == NM_PLATFORM_LIFETIME_PERMANENT
-		                          && preferred == NM_PLATFORM_LIFETIME_PERMANENT), TRUE);
-		g_return_val_if_fail (t_preferred <= t_lifetime, TRUE);
+	t_lifetime = nm_utils_lifetime_rebase_relative_time_on_now (timestamp, lifetime, now);
+	if (!t_lifetime) {
+		NM_SET_OUT (out_preferred, 0);
+		return 0;
 	}
-	return TRUE;
+
+	t_preferred = nm_utils_lifetime_rebase_relative_time_on_now (timestamp, preferred, now);
+
+	NM_SET_OUT (out_preferred, MIN (t_preferred, t_lifetime));
+
+	/* Assert that non-permanent addresses have a (positive) @timestamp. nm_utils_lifetime_rebase_relative_time_on_now()
+	 * treats addresses with timestamp 0 as *now*. Addresses passed to _address_get_lifetime() always
+	 * should have a valid @timestamp, otherwise on every re-sync, their lifetime will be extended anew.
+	 */
+	g_return_val_if_fail (   timestamp != 0
+	                      || (   lifetime  == NM_PLATFORM_LIFETIME_PERMANENT
+	                          && preferred == NM_PLATFORM_LIFETIME_PERMANENT), t_lifetime);
+	g_return_val_if_fail (t_preferred <= t_lifetime, t_lifetime);
+
+	return t_lifetime;
 }
 
 const char *
@@ -4329,6 +4156,21 @@ nm_utils_format_con_diff_for_audit (GHashTable *diff)
 	return g_string_free (str, FALSE);
 }
 
+const char *
+nm_utils_parse_dns_domain (const char *domain, gboolean *is_routing)
+{
+	g_return_val_if_fail (domain, NULL);
+	g_return_val_if_fail (domain[0], NULL);
+
+	if (domain[0] == '~') {
+		domain++;
+		NM_SET_OUT (is_routing, TRUE);
+	} else
+		NM_SET_OUT (is_routing, FALSE);
+
+	return domain;
+}
+
 /*****************************************************************************/
 
 NM_UTILS_ENUM2STR_DEFINE (nm_icmpv6_router_pref_to_string, NMIcmpv6RouterPref,
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index cc784724..ec9e2947 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -25,8 +25,6 @@
 #include <stdio.h>
 #include <arpa/inet.h>
 
-#include "nm-utils/nm-hash-utils.h"
-
 #include "nm-connection.h"
 
 /*****************************************************************************/
@@ -180,8 +178,6 @@ nm_utils_ip_route_metric_penalize (int addr_family, guint32 metric, guint32 pena
 
 int nm_utils_modprobe (GError **error, gboolean suppress_error_loggin, const char *arg1, ...) G_GNUC_NULL_TERMINATED;
 
-guint64 nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid);
-
 void nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, guint64 log_domain,
                                  const char *log_name, guint32 wait_before_kill_msec,
                                  guint32 sleep_duration_msec, guint32 max_wait_msec);
@@ -231,14 +227,14 @@ gboolean nm_utils_connection_has_default_route (NMConnection *connection,
 char *nm_utils_new_vlan_name (const char *parent_iface, guint32 vlan_id);
 const char *nm_utils_new_infiniband_name (char *name, const char *parent_name, int p_key);
 
-gboolean nm_utils_resolve_conf_parse (int addr_family,
-                                      const char *rc_contents,
-                                      GArray *nameservers,
-                                      GPtrArray *dns_options);
-
 int nm_utils_cmp_connection_by_autoconnect_priority (NMConnection *a, NMConnection *b);
 
-void nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base, guint32 level, guint64 domain, const char *name, const char *prefix);
+void nm_utils_log_connection_diff (NMConnection *connection,
+                                   NMConnection *diff_base,
+                                   guint32 level, guint64 domain,
+                                   const char *name,
+                                   const char *prefix,
+                                   const char *dbus_path);
 
 gint64 nm_utils_get_monotonic_timestamp_ns (void);
 gint64 nm_utils_get_monotonic_timestamp_us (void);
@@ -246,6 +242,13 @@ gint64 nm_utils_get_monotonic_timestamp_ms (void);
 gint32 nm_utils_get_monotonic_timestamp_s (void);
 gint64 nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ticks_per_ns);
 
+static inline gint64
+nm_utils_get_monotonic_timestamp_ns_cached (gint64 *cache_now)
+{
+	return    (*cache_now)
+	       ?: (*cache_now = nm_utils_get_monotonic_timestamp_ns ());
+}
+
 gboolean    nm_utils_is_valid_path_component (const char *name);
 const char *NM_ASSERT_VALID_PATH_COMPONENT (const char *name);
 
@@ -410,12 +413,11 @@ guint32 nm_utils_lifetime_rebase_relative_time_on_now (guint32 timestamp,
                                                        guint32 duration,
                                                        gint32 now);
 
-gboolean nm_utils_lifetime_get (guint32 timestamp,
-                                guint32 lifetime,
-                                guint32 preferred,
-                                gint32 now,
-                                guint32 *out_lifetime,
-                                guint32 *out_preferred);
+guint32 nm_utils_lifetime_get (guint32 timestamp,
+                               guint32 lifetime,
+                               guint32 preferred,
+                               gint32 now,
+                               guint32 *out_preferred);
 
 gboolean nm_utils_ip4_address_is_link_local (in_addr_t addr);
 
@@ -450,4 +452,6 @@ const char *nm_activation_type_to_string (NMActivationType activation_type);
 
 /*****************************************************************************/
 
+const char *nm_utils_parse_dns_domain (const char *domain, gboolean *is_routing);
+
 #endif /* __NM_CORE_UTILS_H__ */
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c
new file mode 100644
index 00000000..3e369129
--- /dev/null
+++ b/src/nm-dbus-manager.c
@@ -0,0 +1,1626 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2013 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-dbus-manager.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <string.h>
+
+#include "c-list/src/c-list.h"
+#include "nm-dbus-interface.h"
+#include "nm-core-internal.h"
+#include "nm-dbus-compat.h"
+#include "nm-dbus-object.h"
+#include "NetworkManagerUtils.h"
+
+/* The base path for our GDBusObjectManagerServers.  They do not contain
+ * "NetworkManager" because GDBusObjectManagerServer requires that all
+ * exported objects be *below* the base path, and eg the Manager object
+ * is the base path already.
+ */
+#define OBJECT_MANAGER_SERVER_BASE_PATH "/org/freedesktop"
+
+/*****************************************************************************/
+
+typedef struct {
+	GVariant *value;
+} PropertyCacheData;
+
+typedef struct {
+	CList registration_lst;
+	NMDBusObject *obj;
+	NMDBusObjectClass *klass;
+	guint info_idx;
+	guint registration_id;
+	PropertyCacheData property_cache[];
+} RegistrationData;
+
+/* we require that @path is the first member of NMDBusManagerData
+ * because _objects_by_path_hash() requires that. */
+G_STATIC_ASSERT (G_STRUCT_OFFSET (struct _NMDBusObjectInternal, path) == 0);
+
+enum {
+	PRIVATE_CONNECTION_NEW,
+	PRIVATE_CONNECTION_DISCONNECTED,
+
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+typedef struct {
+	GHashTable *objects_by_path;
+	CList objects_lst_head;
+
+	CList private_servers_lst_head;
+
+	NMDBusManagerSetPropertyHandler set_property_handler;
+	gpointer set_property_handler_data;
+
+	GDBusConnection *connection;
+	GDBusProxy *proxy;
+	guint objmgr_registration_id;
+} NMDBusManagerPrivate;
+
+struct _NMDBusManager {
+	GObject parent;
+	NMDBusManagerPrivate _priv;
+};
+
+struct _NMDBusManagerClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE(NMDBusManager, nm_dbus_manager, G_TYPE_OBJECT)
+
+#define NM_DBUS_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDBusManager, NM_IS_DBUS_MANAGER)
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_CORE
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bus-manager", __VA_ARGS__)
+
+NM_DEFINE_SINGLETON_GETTER (NMDBusManager, nm_dbus_manager_get, NM_TYPE_DBUS_MANAGER);
+
+/*****************************************************************************/
+
+static const GDBusInterfaceInfo interface_info_objmgr;
+static const GDBusSignalInfo signal_info_objmgr_interfaces_added;
+static const GDBusSignalInfo signal_info_objmgr_interfaces_removed;
+static GVariantBuilder *_obj_collect_properties_all (NMDBusObject *obj,
+                                                     GVariantBuilder *builder);
+
+/*****************************************************************************/
+
+static guint
+_objects_by_path_hash (gconstpointer user_data)
+{
+	const char *const*p_data = user_data;
+
+	nm_assert (p_data);
+	nm_assert (*p_data);
+	nm_assert ((*p_data)[0] == '/');
+
+	return nm_hash_str (*p_data);
+}
+
+static gboolean
+_objects_by_path_equal (gconstpointer user_data_a, gconstpointer user_data_b)
+{
+	const char *const*p_data_a = user_data_a;
+	const char *const*p_data_b = user_data_b;
+
+	nm_assert (p_data_a);
+	nm_assert (*p_data_a);
+	nm_assert ((*p_data_a)[0] == '/');
+	nm_assert (p_data_b);
+	nm_assert (*p_data_b);
+	nm_assert ((*p_data_b)[0] == '/');
+
+	return nm_streq (*p_data_a, *p_data_b);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	CList private_servers_lst;
+
+	const char *tag;
+	GQuark detail;
+	char *address;
+	GDBusServer *server;
+
+	/* With peer bus connections, we'll get a new connection for each
+	 * client.  For each connection we create an ObjectManager for
+	 * that connection to handle exporting our objects.
+	 *
+	 * Note that even for connections that don't export any objects
+	 * we'll still create GDBusObjectManager since that's where we store
+	 * the pointer to the GDBusConnection.
+	 */
+	CList object_mgr_lst_head;
+
+	NMDBusManager *manager;
+} PrivateServer;
+
+typedef struct {
+	CList object_mgr_lst;
+	GDBusObjectManagerServer *manager;
+	char *fake_sender;
+} ObjectMgrData;
+
+typedef struct {
+	GDBusConnection *connection;
+	PrivateServer *server;
+	gboolean remote_peer_vanished;
+} CloseConnectionInfo;
+
+/*****************************************************************************/
+
+static void
+_object_mgr_data_free (ObjectMgrData *obj_mgr_data)
+{
+	GDBusConnection *connection;
+
+	c_list_unlink_stale (&obj_mgr_data->object_mgr_lst);
+
+	connection = g_dbus_object_manager_server_get_connection (obj_mgr_data->manager);
+	if (!g_dbus_connection_is_closed (connection))
+		g_dbus_connection_close (connection, NULL, NULL, NULL);
+	g_dbus_object_manager_server_set_connection (obj_mgr_data->manager, NULL);
+	g_object_unref (obj_mgr_data->manager);
+	g_object_unref (connection);
+
+	g_free (obj_mgr_data->fake_sender);
+
+	g_slice_free (ObjectMgrData, obj_mgr_data);
+}
+
+/*****************************************************************************/
+
+static gboolean
+close_connection_in_idle (gpointer user_data)
+{
+	CloseConnectionInfo *info = user_data;
+	PrivateServer *server = info->server;
+	ObjectMgrData *obj_mgr_data, *obj_mgr_data_safe;
+
+	/* Emit this for the manager */
+	g_signal_emit (server->manager,
+	               signals[PRIVATE_CONNECTION_DISCONNECTED],
+	               server->detail,
+	               info->connection);
+
+	/* FIXME: there's a bug (754730) in GLib for which the connection
+	 * is marked as closed when the remote peer vanishes but its
+	 * resources are not cleaned up.  Work around it by explicitly
+	 * closing the connection in that case. */
+	if (info->remote_peer_vanished)
+		g_dbus_connection_close (info->connection, NULL, NULL, NULL);
+
+	c_list_for_each_entry_safe (obj_mgr_data, obj_mgr_data_safe, &server->object_mgr_lst_head, object_mgr_lst) {
+		gs_unref_object GDBusConnection *connection = NULL;
+
+		connection = g_dbus_object_manager_server_get_connection (obj_mgr_data->manager);
+		if (connection == info->connection) {
+			_object_mgr_data_free (obj_mgr_data);
+			break;
+		}
+	}
+
+	g_object_unref (server->manager);
+	g_slice_free (CloseConnectionInfo, info);
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+private_server_closed_connection (GDBusConnection *conn,
+                                  gboolean remote_peer_vanished,
+                                  GError *error,
+                                  gpointer user_data)
+{
+	PrivateServer *s = user_data;
+	CloseConnectionInfo *info;
+
+	/* Clean up after the connection */
+	_LOGD ("(%s) closed connection %p on private socket", s->tag, conn);
+
+	info = g_slice_new0 (CloseConnectionInfo);
+	info->connection = conn;
+	info->server = s;
+	info->remote_peer_vanished = remote_peer_vanished;
+
+	g_object_ref (s->manager);
+
+	/* Delay the close of connection to ensure that D-Bus signals
+	 * are handled */
+	g_idle_add (close_connection_in_idle, info);
+}
+
+static gboolean
+private_server_new_connection (GDBusServer *server,
+                               GDBusConnection *conn,
+                               gpointer user_data)
+{
+	PrivateServer *s = user_data;
+	ObjectMgrData *obj_mgr_data;
+	static guint32 counter = 0;
+	GDBusObjectManagerServer *manager;
+	char *sender;
+
+	g_signal_connect (conn, "closed", G_CALLBACK (private_server_closed_connection), s);
+
+	/* Fake a sender since private connections don't have one */
+	sender = g_strdup_printf ("x:y:%d", counter++);
+
+	manager = g_dbus_object_manager_server_new (OBJECT_MANAGER_SERVER_BASE_PATH);
+	g_dbus_object_manager_server_set_connection (manager, conn);
+
+	obj_mgr_data = g_slice_new (ObjectMgrData);
+	obj_mgr_data->manager = manager;
+	obj_mgr_data->fake_sender = sender;
+	c_list_link_tail (&s->object_mgr_lst_head, &obj_mgr_data->object_mgr_lst);
+
+	_LOGD ("(%s) accepted connection %p on private socket", s->tag, conn);
+
+	/* Emit this for the manager.
+	 *
+	 * It is essential to do this from the "new-connection" signal handler, as
+	 * at that point no messages from the connection are yet processed
+	 * (which avoids races with registering objects). */
+	g_signal_emit (s->manager,
+	               signals[PRIVATE_CONNECTION_NEW],
+	               s->detail,
+	               conn,
+	               manager);
+	return TRUE;
+}
+
+static gboolean
+private_server_authorize (GDBusAuthObserver *observer,
+                          GIOStream         *stream,
+                          GCredentials      *credentials,
+                          gpointer           user_data)
+{
+	return g_credentials_get_unix_user (credentials, NULL) == 0;
+}
+
+static gboolean
+private_server_allow_mechanism (GDBusAuthObserver *observer,
+                                const char *mechanism,
+                                gpointer user_data)
+{
+	return NM_IN_STRSET (mechanism, "EXTERNAL");
+}
+
+static void
+private_server_free (gpointer ptr)
+{
+	PrivateServer *s = ptr;
+	ObjectMgrData *obj_mgr_data, *obj_mgr_data_safe;
+
+	c_list_unlink_stale (&s->private_servers_lst);
+
+	unlink (s->address);
+	g_free (s->address);
+
+	c_list_for_each_entry_safe (obj_mgr_data, obj_mgr_data_safe, &s->object_mgr_lst_head, object_mgr_lst)
+		_object_mgr_data_free (obj_mgr_data);
+
+	g_dbus_server_stop (s->server);
+
+	g_signal_handlers_disconnect_by_func (s->server, G_CALLBACK (private_server_new_connection), s);
+
+	g_object_unref (s->server);
+
+	g_slice_free (PrivateServer, s);
+}
+
+void
+nm_dbus_manager_private_server_register (NMDBusManager *self,
+                                         const char *path,
+                                         const char *tag)
+{
+	NMDBusManagerPrivate *priv;
+	PrivateServer *s;
+	gs_unref_object GDBusAuthObserver *auth_observer = NULL;
+	GDBusServer *server;
+	GError *error = NULL;
+	gs_free char *address = NULL;
+	gs_free char *guid = NULL;
+
+	g_return_if_fail (NM_IS_DBUS_MANAGER (self));
+	g_return_if_fail (path);
+	g_return_if_fail (tag);
+
+	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+
+	/* Only one instance per tag; but don't warn */
+	c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) {
+		if (nm_streq0 (tag, s->tag))
+			return;
+	}
+
+	unlink (path);
+	address = g_strdup_printf ("unix:path=%s", path);
+
+	_LOGD ("(%s) creating private socket %s", tag, address);
+
+	guid = g_dbus_generate_guid ();
+	auth_observer = g_dbus_auth_observer_new ();
+	g_signal_connect (auth_observer, "authorize-authenticated-peer",
+	                  G_CALLBACK (private_server_authorize), NULL);
+	g_signal_connect (auth_observer, "allow-mechanism",
+	                  G_CALLBACK (private_server_allow_mechanism), NULL);
+	server = g_dbus_server_new_sync (address,
+	                                 G_DBUS_SERVER_FLAGS_NONE,
+	                                 guid,
+	                                 auth_observer,
+	                                 NULL, &error);
+
+	if (!server) {
+		_LOGW ("(%s) failed to set up private socket %s: %s",
+		       tag, address, error->message);
+		g_error_free (error);
+		return;
+	}
+
+	s = g_slice_new0 (PrivateServer);
+	s->address = g_steal_pointer (&address);
+	s->server = server;
+	g_signal_connect (server, "new-connection",
+	                  G_CALLBACK (private_server_new_connection), s);
+
+	c_list_init (&s->object_mgr_lst_head);
+
+	s->manager = self;
+	s->detail = g_quark_from_string (tag);
+	s->tag = g_quark_to_string (s->detail);
+
+	c_list_link_tail (&priv->private_servers_lst_head, &s->private_servers_lst);
+
+	g_dbus_server_start (server);
+}
+
+static const char *
+private_server_get_connection_owner (PrivateServer *s, GDBusConnection *connection)
+{
+	ObjectMgrData *obj_mgr_data;
+
+	nm_assert (s);
+	nm_assert (G_IS_DBUS_CONNECTION (connection));
+
+	c_list_for_each_entry (obj_mgr_data, &s->object_mgr_lst_head, object_mgr_lst) {
+		gs_unref_object GDBusConnection *c = NULL;
+
+		c = g_dbus_object_manager_server_get_connection (obj_mgr_data->manager);
+		if (c == connection)
+			return obj_mgr_data->fake_sender;
+	}
+	return NULL;
+}
+
+static GDBusConnection *
+private_server_get_connection_by_owner (PrivateServer *s, const char *owner)
+{
+	ObjectMgrData *obj_mgr_data;
+
+	nm_assert (s);
+	nm_assert (owner);
+
+	c_list_for_each_entry (obj_mgr_data, &s->object_mgr_lst_head, object_mgr_lst) {
+		if (nm_streq (owner, obj_mgr_data->fake_sender))
+			return g_dbus_object_manager_server_get_connection (obj_mgr_data->manager);
+	}
+	return NULL;
+}
+
+/*****************************************************************************/
+
+static gboolean
+_bus_get_unix_pid (NMDBusManager *self,
+                   const char *sender,
+                   gulong *out_pid,
+                   GError **error)
+{
+	guint32 unix_pid = G_MAXUINT32;
+	gs_unref_variant GVariant *ret = NULL;
+
+	ret = _nm_dbus_proxy_call_sync (NM_DBUS_MANAGER_GET_PRIVATE (self)->proxy,
+	                                "GetConnectionUnixProcessID",
+	                                g_variant_new ("(s)", sender),
+	                                G_VARIANT_TYPE ("(u)"),
+	                                G_DBUS_CALL_FLAGS_NONE, 2000,
+	                                NULL, error);
+	if (!ret)
+		return FALSE;
+
+	g_variant_get (ret, "(u)", &unix_pid);
+
+	*out_pid = (gulong) unix_pid;
+	return TRUE;
+}
+
+static gboolean
+_bus_get_unix_user (NMDBusManager *self,
+                    const char *sender,
+                    gulong *out_user,
+                    GError **error)
+{
+	guint32 unix_uid = G_MAXUINT32;
+	gs_unref_variant GVariant *ret = NULL;
+
+	ret = _nm_dbus_proxy_call_sync (NM_DBUS_MANAGER_GET_PRIVATE (self)->proxy,
+	                                "GetConnectionUnixUser",
+	                                g_variant_new ("(s)", sender),
+	                                G_VARIANT_TYPE ("(u)"),
+	                                G_DBUS_CALL_FLAGS_NONE, 2000,
+	                                NULL, error);
+	if (!ret)
+		return FALSE;
+
+	g_variant_get (ret, "(u)", &unix_uid);
+
+	*out_user = (gulong) unix_uid;
+	return TRUE;
+}
+
+/**
+ * _get_caller_info():
+ *
+ * Given a GDBus method invocation, or a GDBusConnection + GDBusMessage,
+ * return the sender and the UID of the sender.
+ */
+static gboolean
+_get_caller_info (NMDBusManager *self,
+                  GDBusMethodInvocation *context,
+                  GDBusConnection *connection,
+                  GDBusMessage *message,
+                  char **out_sender,
+                  gulong *out_uid,
+                  gulong *out_pid)
+{
+	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+	const char *sender;
+
+	if (context) {
+		connection = g_dbus_method_invocation_get_connection (context);
+
+		/* only bus connections will have a sender */
+		sender = g_dbus_method_invocation_get_sender (context);
+	} else {
+		g_assert (message);
+		sender = g_dbus_message_get_sender (message);
+	}
+	g_assert (connection);
+
+	if (!sender) {
+		PrivateServer *s;
+
+		/* Might be a private connection, for which we fake a sender */
+		c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) {
+			sender = private_server_get_connection_owner (s, connection);
+			if (sender) {
+				if (out_uid)
+					*out_uid = 0;
+				if (out_sender)
+					*out_sender = g_strdup (sender);
+				if (out_pid) {
+					GCredentials *creds;
+
+					creds = g_dbus_connection_get_peer_credentials (connection);
+					if (creds) {
+						pid_t pid;
+
+						pid = g_credentials_get_unix_pid (creds, NULL);
+						if (pid == -1)
+							*out_pid = G_MAXULONG;
+						else
+							*out_pid = pid;
+					} else
+						*out_pid = G_MAXULONG;
+				}
+				return TRUE;
+			}
+		}
+		return FALSE;
+	}
+
+	/* Bus connections always have a sender */
+	g_assert (sender);
+	if (out_uid) {
+		if (!_bus_get_unix_user (self, sender, out_uid, NULL)) {
+			*out_uid = G_MAXULONG;
+			return FALSE;
+		}
+	}
+
+	if (out_pid) {
+		if (!_bus_get_unix_pid (self, sender, out_pid, NULL)) {
+			*out_pid = G_MAXULONG;
+			return FALSE;
+		}
+	}
+
+	if (out_sender)
+		*out_sender = g_strdup (sender);
+
+	return TRUE;
+}
+
+gboolean
+nm_dbus_manager_get_caller_info (NMDBusManager *self,
+                                 GDBusMethodInvocation *context,
+                                 char **out_sender,
+                                 gulong *out_uid,
+                                 gulong *out_pid)
+{
+	return _get_caller_info (self, context, NULL, NULL, out_sender, out_uid, out_pid);
+}
+
+gboolean
+nm_dbus_manager_get_caller_info_from_message (NMDBusManager *self,
+                                              GDBusConnection *connection,
+                                              GDBusMessage *message,
+                                              char **out_sender,
+                                              gulong *out_uid,
+                                              gulong *out_pid)
+{
+	return _get_caller_info (self, NULL, connection, message, out_sender, out_uid, out_pid);
+}
+
+/**
+ * nm_dbus_manager_ensure_uid:
+ *
+ * @self: bus manager instance
+ * @context: D-Bus method invocation
+ * @uid: a user-id
+ * @error_domain: error domain to return on failure
+ * @error_code: error code to return on failure
+ *
+ * Retrieves the uid of the D-Bus method caller and
+ * checks that it matches @uid, unless @uid is G_MAXULONG.
+ * In case of failure the function returns FALSE and finishes
+ * handling the D-Bus method with an error.
+ *
+ * Returns: %TRUE if the check succeeded, %FALSE otherwise
+ */
+gboolean
+nm_dbus_manager_ensure_uid (NMDBusManager          *self,
+                            GDBusMethodInvocation *context,
+                            gulong uid,
+                            GQuark error_domain,
+                            int error_code)
+{
+	gulong caller_uid;
+	GError *error = NULL;
+
+	g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), FALSE);
+	g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (context), FALSE);
+
+	if (!nm_dbus_manager_get_caller_info (self, context, NULL, &caller_uid, NULL)) {
+		error = g_error_new_literal (error_domain,
+		                             error_code,
+		                             "Unable to determine request UID.");
+		g_dbus_method_invocation_take_error (context, error);
+		return FALSE;
+	}
+
+	if (uid != G_MAXULONG && caller_uid != uid) {
+		error = g_error_new_literal (error_domain,
+		                             error_code,
+		                             "Permission denied");
+		g_dbus_method_invocation_take_error (context, error);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean
+nm_dbus_manager_get_unix_user (NMDBusManager *self,
+                               const char *sender,
+                               gulong *out_uid)
+{
+	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+	PrivateServer *s;
+	GError *error = NULL;
+
+	g_return_val_if_fail (sender != NULL, FALSE);
+	g_return_val_if_fail (out_uid != NULL, FALSE);
+
+	/* Check if it's a private connection sender, which we fake */
+	c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) {
+		gs_unref_object GDBusConnection *connection = NULL;
+
+		connection = private_server_get_connection_by_owner (s, sender);
+		if (connection) {
+			*out_uid = 0;
+			return TRUE;
+		}
+	}
+
+	/* Otherwise, a bus connection */
+	if (!_bus_get_unix_user (self, sender, out_uid, &error)) {
+		_LOGW ("failed to get unix user for dbus sender '%s': %s",
+		       sender, error->message);
+		g_error_free (error);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+const char *
+nm_dbus_manager_connection_get_private_name (NMDBusManager *self,
+                                             GDBusConnection *connection)
+{
+	NMDBusManagerPrivate *priv;
+	PrivateServer *s;
+	const char *owner;
+
+	g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), FALSE);
+	g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+
+	if (g_dbus_connection_get_unique_name (connection)) {
+		/* Shortcut. The connection is not a private connection. */
+		return NULL;
+	}
+
+	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+	c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) {
+		if ((owner = private_server_get_connection_owner (s, connection)))
+			return owner;
+	}
+	g_return_val_if_reached (NULL);
+}
+
+/**
+ * nm_dbus_manager_new_proxy:
+ * @self: the #NMDBusManager
+ * @connection: the GDBusConnection for which this connection should be created
+ * @proxy_type: the type of #GDBusProxy to create
+ * @name: any name on the message bus
+ * @path: name of the object instance to call methods on
+ * @iface: name of the interface to call methods on
+ *
+ * Creates a new proxy (of type @proxy_type) for a name on a given bus.  Since
+ * the process which called the D-Bus method could be coming from a private
+ * connection or the system bus connection, different proxies must be created
+ * for each case.  This function abstracts that.
+ *
+ * Returns: a #GDBusProxy capable of calling D-Bus methods of the calling process
+ */
+GDBusProxy *
+nm_dbus_manager_new_proxy (NMDBusManager *self,
+                           GDBusConnection *connection,
+                           GType proxy_type,
+                           const char *name,
+                           const char *path,
+                           const char *iface)
+{
+	const char *owner;
+	GDBusProxy *proxy;
+	GError *error = NULL;
+
+	g_return_val_if_fail (g_type_is_a (proxy_type, G_TYPE_DBUS_PROXY), NULL);
+	g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+
+	/* Might be a private connection, for which @name is fake */
+	owner = nm_dbus_manager_connection_get_private_name (self, connection);
+	if (owner) {
+		g_return_val_if_fail (!g_strcmp0 (owner, name), NULL);
+		name = NULL;
+	}
+
+	proxy = g_initable_new (proxy_type, NULL, &error,
+	                        "g-connection", connection,
+	                        "g-flags", (G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+	                                    G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
+	                        "g-name", name,
+	                        "g-object-path", path,
+	                        "g-interface-name", iface,
+	                        NULL);
+	if (!proxy) {
+		_LOGW ("could not create proxy for %s on connection %s: %s",
+		       iface, name, error->message);
+		g_error_free (error);
+	}
+	return proxy;
+}
+
+/*****************************************************************************/
+
+GDBusConnection *
+nm_dbus_manager_get_connection (NMDBusManager *self)
+{
+	g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), NULL);
+
+	return NM_DBUS_MANAGER_GET_PRIVATE (self)->connection;
+}
+
+/*****************************************************************************/
+
+static const NMDBusInterfaceInfoExtended *
+_reg_data_get_interface_info (RegistrationData *reg_data)
+{
+	nm_assert (reg_data);
+
+	return reg_data->klass->interface_infos[reg_data->info_idx];
+}
+
+/*****************************************************************************/
+
+static void
+dbus_vtable_method_call (GDBusConnection *connection,
+                         const char *sender,
+                         const char *object_path,
+                         const char *interface_name,
+                         const char *method_name,
+                         GVariant *parameters,
+                         GDBusMethodInvocation *invocation,
+                         gpointer user_data)
+{
+	RegistrationData *reg_data = user_data;
+	NMDBusObject *obj = reg_data->obj;
+	const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data);
+	const NMDBusMethodInfoExtended *method_info = NULL;
+	gboolean on_same_interface;
+
+	on_same_interface = nm_streq (interface_info->parent.name, interface_name);
+
+	/* handle property setter first... */
+	if (   !on_same_interface
+	    && nm_streq (interface_name, DBUS_INTERFACE_PROPERTIES)
+	    && nm_streq (method_name, "Set")) {
+		NMDBusManager *self = nm_dbus_object_get_manager (obj);
+		NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+		const NMDBusPropertyInfoExtended *property_info = NULL;
+		const char *property_interface;
+		const char *property_name;
+		gs_unref_variant GVariant *value = NULL;
+
+		g_variant_get (parameters, "(&s&sv)", &property_interface, &property_name, &value);
+
+		nm_assert (nm_streq (property_interface, interface_info->parent.name));
+
+		property_info = (const NMDBusPropertyInfoExtended *) nm_dbus_utils_interface_info_lookup_property (&interface_info->parent,
+		                                                                                                   property_name,
+		                                                                                                   NULL);
+		if (   !property_info
+		    || !NM_FLAGS_HAS (property_info->parent.flags, G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE))
+			g_return_if_reached ();
+
+		if (!priv->set_property_handler) {
+			g_dbus_method_invocation_return_error (invocation,
+			                                       G_DBUS_ERROR,
+			                                       G_DBUS_ERROR_AUTH_FAILED,
+			                                       "Cannot authenticate setting property %s",
+			                                       property_name);
+			return;
+		}
+
+		priv->set_property_handler (obj,
+		                            interface_info,
+		                            property_info,
+		                            connection,
+		                            sender,
+		                            invocation,
+		                            value,
+		                            priv->set_property_handler_data);
+		return;
+	}
+
+	if (on_same_interface) {
+		method_info = (const NMDBusMethodInfoExtended *) nm_dbus_utils_interface_info_lookup_method (&interface_info->parent,
+		                                                                                             method_name);
+	}
+	if (!method_info) {
+		g_dbus_method_invocation_return_error (invocation,
+		                                       G_DBUS_ERROR,
+		                                       G_DBUS_ERROR_UNKNOWN_METHOD,
+		                                       "Unknown method %s",
+		                                       method_name);
+		return;
+	}
+
+	method_info->handle (reg_data->obj,
+	                     interface_info,
+	                     method_info,
+	                     connection,
+	                     sender,
+	                     invocation,
+	                     parameters);
+}
+
+static GVariant *
+_obj_get_property (RegistrationData *reg_data,
+                   guint property_idx,
+                   gboolean refetch)
+{
+	const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data);
+	const NMDBusPropertyInfoExtended *property_info;
+	GVariant *value;
+
+	property_info = (const NMDBusPropertyInfoExtended *) (interface_info->parent.properties[property_idx]);
+
+	if (refetch)
+		nm_clear_g_variant (&reg_data->property_cache[property_idx].value);
+	else {
+		value = reg_data->property_cache[property_idx].value;
+		if (value)
+			goto out;
+	}
+
+	value = nm_dbus_utils_get_property (G_OBJECT (reg_data->obj),
+	                                    property_info->parent.signature,
+	                                    property_info->property_name);
+	reg_data->property_cache[property_idx].value = value;
+out:
+	return g_variant_ref (value);
+}
+
+static GVariant *
+dbus_vtable_get_property (GDBusConnection *connection,
+                          const char *sender,
+                          const char *object_path,
+                          const char *interface_name,
+                          const char *property_name,
+                          GError **error,
+                          gpointer user_data)
+{
+	RegistrationData *reg_data = user_data;
+	const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data);
+	guint property_idx;
+
+	if (!nm_dbus_utils_interface_info_lookup_property (&interface_info->parent,
+	                                                   property_name,
+	                                                   &property_idx))
+		g_return_val_if_reached (NULL);
+
+	return _obj_get_property (reg_data, property_idx, FALSE);
+}
+
+static const GDBusInterfaceVTable dbus_vtable = {
+	.method_call = dbus_vtable_method_call,
+	.get_property = dbus_vtable_get_property,
+
+	/* set_property is handled via method_call as well. We need to authenticate
+	 * which requires an asynchronous handler. */
+	.set_property = NULL,
+};
+
+static void
+_obj_register (NMDBusManager *self,
+               NMDBusObject *obj)
+{
+	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+	guint i, k;
+	guint n_klasses;
+	GType gtype;
+	NMDBusObjectClass *klasses[10];
+	const NMDBusInterfaceInfoExtended *const*prev_interface_infos = NULL;
+	GVariantBuilder builder;
+
+	nm_assert (c_list_is_empty (&obj->internal.registration_lst_head));
+	nm_assert (priv->connection);
+
+	n_klasses = 0;
+	gtype = G_OBJECT_TYPE (obj);
+	while (gtype != NM_TYPE_DBUS_OBJECT) {
+		nm_assert (n_klasses < G_N_ELEMENTS (klasses));
+		klasses[n_klasses++] = g_type_class_ref (gtype);
+		gtype = g_type_parent (gtype);
+	}
+
+	for (k = n_klasses; k > 0; ) {
+		NMDBusObjectClass *klass = NM_DBUS_OBJECT_CLASS (klasses[--k]);
+
+		if (!klass->interface_infos)
+			continue;
+
+		if (prev_interface_infos == klass->interface_infos) {
+			/* derived classes inherrit the interface-infos from the parent class.
+			 * For convenience, we allow the subclass to leave interface-infos untouched,
+			 * but it means we must ignore the parent's interface, because we already
+			 * handled it.
+			 *
+			 * Note that the loop goes from the parent classes to child classes */
+			continue;
+		}
+		prev_interface_infos = klass->interface_infos;
+
+		for (i = 0; klass->interface_infos[i]; i++) {
+			const NMDBusInterfaceInfoExtended *interface_info = klass->interface_infos[i];
+			RegistrationData *reg_data;
+			gs_free_error GError *error = NULL;
+			guint registration_id;
+			guint prop_len = NM_PTRARRAY_LEN (interface_info->parent.properties);
+
+			reg_data = g_malloc0 (sizeof (RegistrationData) + (sizeof (PropertyCacheData) * prop_len));
+
+			registration_id = g_dbus_connection_register_object (priv->connection,
+			                                                     obj->internal.path,
+			                                                     NM_UNCONST_PTR (GDBusInterfaceInfo, &interface_info->parent),
+			                                                     &dbus_vtable,
+			                                                     reg_data,
+			                                                     NULL,
+			                                                     &error);
+			if (!registration_id) {
+				_LOGE ("failure to register object %s: %s", obj->internal.path, error->message);
+				g_free (reg_data);
+				continue;
+			}
+
+			reg_data->obj = obj;
+			reg_data->klass = g_type_class_ref (G_TYPE_FROM_CLASS (klass));
+			reg_data->info_idx = i;
+			reg_data->registration_id = registration_id;
+			c_list_link_tail (&obj->internal.registration_lst_head, &reg_data->registration_lst);
+		}
+	}
+
+	for (k = 0; k < n_klasses; k++)
+		g_type_class_unref (klasses[k]);
+
+	nm_assert (!c_list_is_empty (&obj->internal.registration_lst_head));
+
+	/* Currently the interfaces of an object do not changed and strictly depend on the object glib type.
+	 * We don't need more flixibility, and it simplifies the code. Hence, now emit interface-added
+	 * signal for the new object.
+	 *
+	 * Warning: note that if @obj's notify signal is currently blocked via g_object_freeze_notify(),
+	 * we might emit properties with an inconsistent (internal) state. There is no easy solution,
+	 * because we have to emit the signal now, and we don't know what the correct desired state
+	 * of the properties is.
+	 * Another problem is, upon unfreezing the signals, we immediately send PropertiesChanged
+	 * notifications out. Which is a bit odd, as we just export the object.
+	 *
+	 * In general, it's ok to export an object with frozen signals. But you better make sure
+	 * that all properties are in a self-consistent state when exporting the object. */
+	g_dbus_connection_emit_signal (priv->connection,
+	                               NULL,
+	                               OBJECT_MANAGER_SERVER_BASE_PATH,
+	                               interface_info_objmgr.name,
+	                               signal_info_objmgr_interfaces_added.name,
+	                               g_variant_new ("(oa{sa{sv}})",
+	                                              obj->internal.path,
+	                                              _obj_collect_properties_all (obj, &builder)),
+	                               NULL);
+}
+
+static void
+_obj_unregister (NMDBusManager *self,
+                 NMDBusObject *obj)
+{
+	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+	RegistrationData *reg_data;
+	GVariantBuilder builder;
+
+	nm_assert (NM_IS_DBUS_OBJECT (obj));
+
+	if (!priv->connection) {
+		/* nothing to do for the moment. */
+		nm_assert (c_list_is_empty (&obj->internal.registration_lst_head));
+		return;
+	}
+
+	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"));
+
+	while ((reg_data = c_list_last_entry (&obj->internal.registration_lst_head, RegistrationData, registration_lst))) {
+		const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data);
+		guint i;
+
+		g_variant_builder_add (&builder,
+		                       "s",
+		                       interface_info->parent.name);
+		c_list_unlink_stale (&reg_data->registration_lst);
+		if (!g_dbus_connection_unregister_object (priv->connection, reg_data->registration_id))
+			nm_assert_not_reached ();
+
+		if (interface_info->parent.properties) {
+			for (i = 0; interface_info->parent.properties[i]; i++)
+				nm_clear_g_variant (&reg_data->property_cache[i].value);
+		}
+
+		g_type_class_unref (reg_data->klass);
+		g_free (reg_data);
+	}
+
+	g_dbus_connection_emit_signal (priv->connection,
+	                               NULL,
+	                               OBJECT_MANAGER_SERVER_BASE_PATH,
+	                               interface_info_objmgr.name,
+	                               signal_info_objmgr_interfaces_removed.name,
+	                               g_variant_new ("(oas)",
+	                                              obj->internal.path,
+	                                              &builder),
+	                               NULL);
+}
+
+NMDBusObject *
+nm_dbus_manager_lookup_object (NMDBusManager *self, const char *path)
+{
+	NMDBusManagerPrivate *priv;
+	gpointer ptr;
+	NMDBusObject *obj;
+
+	g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), NULL);
+	g_return_val_if_fail (path, NULL);
+
+	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+
+	ptr = g_hash_table_lookup (priv->objects_by_path, &path);
+	if (!ptr)
+		return NULL;
+
+	obj = (NMDBusObject *) (((char *) ptr) - G_STRUCT_OFFSET (NMDBusObject, internal));
+	nm_assert (NM_IS_DBUS_OBJECT (obj));
+	return obj;
+}
+
+void
+_nm_dbus_manager_obj_export (NMDBusObject *obj)
+{
+	NMDBusManager *self;
+	NMDBusManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_DBUS_OBJECT (obj));
+	g_return_if_fail (obj->internal.path);
+	g_return_if_fail (NM_IS_DBUS_MANAGER (obj->internal.bus_manager));
+	g_return_if_fail (c_list_is_empty (&obj->internal.objects_lst));
+	nm_assert (c_list_is_empty (&obj->internal.registration_lst_head));
+
+	self = obj->internal.bus_manager;
+	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+
+	if (!g_hash_table_add (priv->objects_by_path, &obj->internal))
+		nm_assert_not_reached ();
+	c_list_link_tail (&priv->objects_lst_head, &obj->internal.objects_lst);
+
+	if (priv->connection)
+		_obj_register (self, obj);
+}
+
+void
+_nm_dbus_manager_obj_unexport (NMDBusObject *obj)
+{
+	NMDBusManager *self;
+	NMDBusManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_DBUS_OBJECT (obj));
+	g_return_if_fail (obj->internal.path);
+	g_return_if_fail (NM_IS_DBUS_MANAGER (obj->internal.bus_manager));
+	g_return_if_fail (!c_list_is_empty (&obj->internal.objects_lst));
+
+	self = obj->internal.bus_manager;
+	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+
+	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 (!g_hash_table_remove (priv->objects_by_path, &obj->internal))
+		nm_assert_not_reached ();
+	c_list_unlink (&obj->internal.objects_lst);
+}
+
+void
+_nm_dbus_manager_obj_notify (NMDBusObject *obj,
+                             guint n_pspecs,
+                             const GParamSpec *const*pspecs)
+{
+	NMDBusManager *self;
+	NMDBusManagerPrivate *priv;
+	RegistrationData *reg_data;
+	guint i, p;
+	gboolean any_legacy_signals = FALSE;
+	gboolean any_legacy_properties = FALSE;
+	GVariantBuilder legacy_builder;
+	GVariant *device_statistics_args = NULL;
+
+	nm_assert (NM_IS_DBUS_OBJECT (obj));
+	nm_assert (obj->internal.path);
+	nm_assert (NM_IS_DBUS_MANAGER (obj->internal.bus_manager));
+	nm_assert (!c_list_is_empty (&obj->internal.objects_lst));
+
+	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;
+			break;
+		}
+	}
+
+	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
+	 * (interaces 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.
+	 *
+	 * The nice part of this implementation is however, that the order in which properties
+	 * are added to the GVariant is strictly defined to be the order in which the D-Bus property-info
+	 * is declared. Getting a defined ordering with some smart lookup would be hard. */
+	c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) {
+		const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data);
+		gboolean has_properties = FALSE;
+		GVariantBuilder builder;
+		GVariantBuilder invalidated_builder;
+		GVariant *args;
+
+		if (!interface_info->parent.properties)
+			continue;
+
+		for (i = 0; interface_info->parent.properties[i]; i++) {
+			const NMDBusPropertyInfoExtended *property_info = (const NMDBusPropertyInfoExtended *) interface_info->parent.properties[i];
+
+			for (p = 0; p < n_pspecs; p++) {
+				const GParamSpec *pspec = pspecs[p];
+				gs_unref_variant GVariant *value = NULL;
+
+				if (!nm_streq (property_info->property_name, pspec->name))
+					continue;
+
+				value = _obj_get_property (reg_data, i, TRUE);
+
+				if (   property_info->include_in_legacy_property_changed
+				    && any_legacy_signals) {
+					/* also track the value in the legacy_builder to emit legacy signals below. */
+					if (!any_legacy_properties) {
+						any_legacy_properties = TRUE;
+						g_variant_builder_init (&legacy_builder, G_VARIANT_TYPE ("a{sv}"));
+					}
+					g_variant_builder_add (&legacy_builder, "{sv}", property_info->parent.name, value);
+				}
+
+				if (!has_properties) {
+					has_properties = TRUE;
+					g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+				}
+				g_variant_builder_add (&builder, "{sv}", property_info->parent.name, value);
+			}
+		}
+
+		if (!has_properties)
+			continue;
+
+		args = g_variant_builder_end (&builder);
+
+		if (G_UNLIKELY (interface_info == &nm_interface_info_device_statistics)) {
+			/* we treat the Device.Statistics signal special, because we need to
+			 * emit a signal also for it (below). */
+			nm_assert (!device_statistics_args);
+			device_statistics_args = g_variant_ref_sink (args);
+		}
+
+		g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
+		g_dbus_connection_emit_signal (priv->connection,
+		                               NULL,
+		                               obj->internal.path,
+		                               "org.freedesktop.DBus.Properties",
+		                               "PropertiesChanged",
+		                               g_variant_new ("(s@a{sv}as)",
+		                                              interface_info->parent.name,
+		                                              args,
+		                                              &invalidated_builder),
+		                               NULL);
+	}
+
+	if (G_UNLIKELY (device_statistics_args)) {
+		/* this is a special interface: it has a legacy PropertiesChanged signal,
+		 * however, contrary to other interfaces with ~regular~ legacy signals,
+		 * we only notify about properties that actually belong to this interface. */
+		g_dbus_connection_emit_signal (priv->connection,
+		                               NULL,
+		                               obj->internal.path,
+		                               nm_interface_info_device_statistics.parent.name,
+		                               "PropertiesChanged",
+		                               g_variant_new ("(@a{sv})",
+		                                              device_statistics_args),
+		                               NULL);
+		g_variant_unref (device_statistics_args);
+	}
+
+	if (any_legacy_properties) {
+		gs_unref_variant GVariant *args = NULL;
+
+		/* The legacy PropertyChanged signal on the NetworkManager D-Bus interface is
+		 * deprecated for the standard signal on org.freedesktop.DBus.Properties. However,
+		 * for backward compatibility, we still need to emit it.
+		 *
+		 * Due to a bug in dbus-glib in NetworkManager <= 1.0, the signal would
+		 * not only notify about properties that were actually on the corresponding
+		 * D-Bus interface. Instead, it would notify about all relevant properties
+		 * on all interfaces that had such a signal.
+		 *
+		 * For example, "HwAddress" gets emitted both on "fdo.NM.Device.Ethernet"
+		 * and "fdo.NM.Device.Veth" for veth interfaces, although only the former
+		 * actually has such a property.
+		 * Also note that "fdo.NM.Device" interface has no legacy signal. All notifications
+		 * about its properties are instead emitted on the interfaces of the subtypes.
+		 *
+		 * See bgo#770629 and commit bef26a2e69f51259095fa080221db73de09fd38d.
+		 */
+		args = g_variant_ref_sink (g_variant_new ("(a{sv})",
+		                                          &legacy_builder));
+		c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) {
+			const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data);
+
+			if (interface_info->legacy_property_changed) {
+				g_dbus_connection_emit_signal (priv->connection,
+				                               NULL,
+				                               obj->internal.path,
+				                               interface_info->parent.name,
+				                               "PropertiesChanged",
+				                               args,
+				                               NULL);
+			}
+		}
+	}
+}
+
+void
+_nm_dbus_manager_obj_emit_signal (NMDBusObject *obj,
+                                  const NMDBusInterfaceInfoExtended *interface_info,
+                                  const GDBusSignalInfo *signal_info,
+                                  GVariant *args)
+{
+	NMDBusManager *self;
+	NMDBusManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_DBUS_OBJECT (obj));
+	g_return_if_fail (obj->internal.path);
+	g_return_if_fail (NM_IS_DBUS_MANAGER (obj->internal.bus_manager));
+	g_return_if_fail (!c_list_is_empty (&obj->internal.objects_lst));
+
+	self = obj->internal.bus_manager;
+	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+
+	if (!priv->connection) {
+		nm_g_variant_unref_floating (args);
+		return;
+	}
+
+	g_dbus_connection_emit_signal (priv->connection,
+	                               NULL,
+	                               obj->internal.path,
+	                               interface_info->parent.name,
+	                               signal_info->name,
+	                               args,
+	                               NULL);
+}
+
+/*****************************************************************************/
+
+static GVariantBuilder *
+_obj_collect_properties_per_interface (NMDBusObject *obj,
+                                       RegistrationData *reg_data,
+                                       GVariantBuilder *builder)
+{
+	const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data);
+	guint i;
+
+	g_variant_builder_init (builder, G_VARIANT_TYPE ("a{sv}"));
+	if (interface_info->parent.properties) {
+		for (i = 0; interface_info->parent.properties[i]; i++) {
+			const NMDBusPropertyInfoExtended *property_info = (const NMDBusPropertyInfoExtended *) interface_info->parent.properties[i];
+			gs_unref_variant GVariant *variant = NULL;
+
+			variant = _obj_get_property (reg_data, i, FALSE);
+			g_variant_builder_add (builder,
+			                       "{sv}",
+			                       property_info->parent.name,
+			                       variant);
+		}
+	}
+	return builder;
+}
+
+static GVariantBuilder *
+_obj_collect_properties_all (NMDBusObject *obj,
+                             GVariantBuilder *builder)
+{
+	RegistrationData *reg_data;
+
+	g_variant_builder_init (builder, G_VARIANT_TYPE ("a{sa{sv}}"));
+
+	c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) {
+		GVariantBuilder properties_builder;
+
+		g_variant_builder_add (builder,
+		                       "{sa{sv}}",
+		                       _reg_data_get_interface_info (reg_data)->parent.name,
+		                       _obj_collect_properties_per_interface (obj,
+		                                                              reg_data,
+		                                                              &properties_builder));
+	}
+
+	return builder;
+}
+
+static void
+dbus_vtable_objmgr_method_call (GDBusConnection *connection,
+                                const char *sender,
+                                const char *object_path,
+                                const char *interface_name,
+                                const char *method_name,
+                                GVariant *parameters,
+                                GDBusMethodInvocation *invocation,
+                                gpointer user_data)
+{
+	NMDBusManager *self = user_data;
+	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+	GVariantBuilder array_builder;
+	NMDBusObject *obj;
+
+	nm_assert (nm_streq0 (object_path, OBJECT_MANAGER_SERVER_BASE_PATH));
+
+	if (   !nm_streq (method_name, "GetManagedObjects")
+	    || !nm_streq (interface_name, interface_info_objmgr.name)) {
+		g_dbus_method_invocation_return_error (invocation,
+		                                       G_DBUS_ERROR,
+		                                       G_DBUS_ERROR_UNKNOWN_METHOD,
+		                                       "Unknown method %s - only GetManagedObjects() is supported",
+		                                       method_name);
+		return;
+	}
+
+	g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("a{oa{sa{sv}}}"));
+	c_list_for_each_entry (obj, &priv->objects_lst_head, internal.objects_lst) {
+		GVariantBuilder interfaces_builder;
+
+		/* note that we are called on an idle handler. Hence, all properties are
+		 * supposed to be in a consistent state. That is true, if you always
+		 * g_object_thaw_notify() before returning to the mainloop. Keeping
+		 * signals frozen between while returning from the current call stack
+		 * is anyway a very fragile thing, easy to get wrong. Don't do that. */
+		g_variant_builder_add (&array_builder,
+		                       "{oa{sa{sv}}}",
+		                       obj->internal.path,
+		                       _obj_collect_properties_all (obj,
+		                                                    &interfaces_builder));
+	}
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new ("(a{oa{sa{sv}}})",
+	                                                      &array_builder));
+}
+
+static const GDBusInterfaceVTable dbus_vtable_objmgr = {
+	.method_call = dbus_vtable_objmgr_method_call
+};
+
+static const GDBusSignalInfo signal_info_objmgr_interfaces_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"InterfacesAdded",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("object_path",               "o"),
+		NM_DEFINE_GDBUS_ARG_INFO ("interfaces_and_properties", "a{sa{sv}}"),
+	),
+);
+
+static const GDBusSignalInfo signal_info_objmgr_interfaces_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"InterfacesRemoved",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("object_path", "o"),
+		NM_DEFINE_GDBUS_ARG_INFO ("interfaces",  "as"),
+	),
+);
+
+static const GDBusInterfaceInfo interface_info_objmgr = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+	"org.freedesktop.DBus.ObjectManager",
+	.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+		NM_DEFINE_GDBUS_METHOD_INFO (
+			"GetManagedObjects",
+			.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+				NM_DEFINE_GDBUS_ARG_INFO ("object_paths_interfaces_and_properties", "a{oa{sa{sv}}}"),
+			),
+		),
+	),
+	.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+		&signal_info_objmgr_interfaces_added,
+		&signal_info_objmgr_interfaces_removed,
+	),
+);
+
+/*****************************************************************************/
+
+gboolean
+nm_dbus_manager_start (NMDBusManager *self,
+                       NMDBusManagerSetPropertyHandler set_property_handler,
+                       gpointer set_property_handler_data)
+{
+	NMDBusManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *ret = NULL;
+	gs_unref_object GDBusConnection *connection = NULL;
+	gs_unref_object GDBusProxy *proxy = NULL;
+	guint32 result;
+	guint registration_id;
+	NMDBusObject *obj;
+
+	g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), FALSE);
+
+	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+
+	priv->set_property_handler = set_property_handler;
+	priv->set_property_handler_data = set_property_handler_data;
+
+	g_return_val_if_fail (!priv->connection, FALSE);
+
+	/* we will create the D-Bus connection and registering the name synchronously.
+	 * The reason why that is necessary is because:
+	 *  (1) if we are unable to create a D-Bus connection, it means D-Bus is not
+	 *    available and we run in D-Bus less mode. We do not support creating
+	 *    a D-Bus connection later on. This disconnected mode is useful for initrd
+	 *    (well, currently not yet, but will be).
+	 *  (2) if we are able to create the connection and register the name,
+	 *    all is good and we run with D-Bus. Note that D-Bus disconnects
+	 *    from D-Bus are ignored. Essentially, we do not support restarting
+	 *    D-Bus.
+	 *  (3) if we are able to create the connection but registration fails,
+	 *    it means that something is borked. Quite possibly another NetworkManager
+	 *    instance is running. We need to exit right away.
+	 * To appease (1) and (3), we cannot initalize synchronously, because we need
+	 * to know right away whether another NetworkManager instance is running (3).
+	 **/
+
+	connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
+	                             NULL,
+	                             &error);
+	if (!connection) {
+		_LOGI ("cannot connect to D-Bus and proceed without (%s)", error->message);
+		return TRUE;
+	}
+
+	g_dbus_connection_set_exit_on_close (connection, FALSE);
+
+	proxy = g_dbus_proxy_new_sync (connection,
+	                                 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+	                               | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+	                               NULL,
+	                               DBUS_SERVICE_DBUS,
+	                               DBUS_PATH_DBUS,
+	                               DBUS_INTERFACE_DBUS,
+	                               NULL,
+	                               &error);
+	if (!proxy) {
+		_LOGE ("fatal failure to initialize D-Bus: %s", error->message);
+		return FALSE;
+	}
+
+	ret = _nm_dbus_proxy_call_sync (proxy,
+	                                "RequestName",
+	                                g_variant_new ("(su)",
+	                                               NM_DBUS_SERVICE,
+	                                               DBUS_NAME_FLAG_DO_NOT_QUEUE),
+	                                G_VARIANT_TYPE ("(u)"),
+	                                G_DBUS_CALL_FLAGS_NONE, -1,
+	                                NULL,
+	                                &error);
+	if (!ret) {
+		_LOGE ("fatal failure to aquire D-Bus service \"%s"": %s",
+		       NM_DBUS_SERVICE, error->message);
+		return FALSE;
+	}
+
+	g_variant_get (ret, "(u)", &result);
+	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);
+		return FALSE;
+	}
+
+	registration_id = g_dbus_connection_register_object (connection,
+	                                                     OBJECT_MANAGER_SERVER_BASE_PATH,
+	                                                     NM_UNCONST_PTR (GDBusInterfaceInfo, &interface_info_objmgr),
+	                                                     &dbus_vtable_objmgr,
+	                                                     self,
+	                                                     NULL,
+	                                                     &error);
+	if (!registration_id) {
+		_LOGE ("failure to register object manager: %s", error->message);
+		return FALSE;
+	}
+
+	priv->objmgr_registration_id = registration_id;
+	priv->connection = g_steal_pointer (&connection);
+	priv->proxy = g_steal_pointer (&proxy);
+
+	_LOGI ("aquired D-Bus service \"%s\"", NM_DBUS_SERVICE);
+
+	c_list_for_each_entry (obj, &priv->objects_lst_head, internal.objects_lst)
+		_obj_register (self, obj);
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+nm_dbus_manager_init (NMDBusManager *self)
+{
+	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (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);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDBusManager *self = NM_DBUS_MANAGER (object);
+	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+	PrivateServer *s, *s_safe;
+
+	/* All exported NMDBusObject instances keep the manager alive, so we don't
+	 * expect any remaining objects. */
+	nm_assert (!priv->objects_by_path || g_hash_table_size (priv->objects_by_path) == 0);
+	nm_assert (c_list_is_empty (&priv->objects_lst_head));
+
+	g_clear_pointer (&priv->objects_by_path, g_hash_table_destroy);
+
+	c_list_for_each_entry_safe (s, s_safe, &priv->private_servers_lst_head, private_servers_lst)
+		private_server_free (s);
+
+	if (priv->objmgr_registration_id) {
+		g_dbus_connection_unregister_object (priv->connection,
+		                                     nm_steal_int (&priv->objmgr_registration_id));
+	}
+
+	g_clear_object (&priv->proxy);
+	g_clear_object (&priv->connection);
+
+	G_OBJECT_CLASS (nm_dbus_manager_parent_class)->dispose (object);
+}
+
+static void
+nm_dbus_manager_class_init (NMDBusManagerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	signals[PRIVATE_CONNECTION_NEW] =
+	    g_signal_new (NM_DBUS_MANAGER_PRIVATE_CONNECTION_NEW,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+	                  0, NULL, NULL, NULL,
+	                  G_TYPE_NONE, 2, G_TYPE_DBUS_CONNECTION, G_TYPE_DBUS_OBJECT_MANAGER_SERVER);
+
+	signals[PRIVATE_CONNECTION_DISCONNECTED] =
+	    g_signal_new (NM_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+	                  0, NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
diff --git a/src/nm-dbus-manager.h b/src/nm-dbus-manager.h
new file mode 100644
index 00000000..617f8a67
--- /dev/null
+++ b/src/nm-dbus-manager.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2008 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#ifndef __NM_DBUS_MANAGER_H__
+#define __NM_DBUS_MANAGER_H__
+
+#include "nm-dbus-utils.h"
+
+#define NM_TYPE_DBUS_MANAGER (nm_dbus_manager_get_type ())
+#define NM_DBUS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NM_TYPE_DBUS_MANAGER, NMDBusManager))
+#define NM_DBUS_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), NM_TYPE_DBUS_MANAGER, NMDBusManagerClass))
+#define NM_IS_DBUS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NM_TYPE_DBUS_MANAGER))
+#define NM_IS_DBUS_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), NM_TYPE_DBUS_MANAGER))
+#define NM_DBUS_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NM_TYPE_DBUS_MANAGER, NMDBusManagerClass))
+
+#define NM_DBUS_MANAGER_PRIVATE_CONNECTION_NEW           "private-connection-new"
+#define NM_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED  "private-connection-disconnected"
+
+typedef struct _NMDBusManagerClass NMDBusManagerClass;
+
+GType nm_dbus_manager_get_type (void);
+
+NMDBusManager *nm_dbus_manager_get (void);
+
+typedef void (*NMDBusManagerSetPropertyHandler) (NMDBusObject *obj,
+                                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                                 const NMDBusPropertyInfoExtended *property_info,
+                                                 GDBusConnection *connection,
+                                                 const char *sender,
+                                                 GDBusMethodInvocation *invocation,
+                                                 GVariant *value,
+                                                 gpointer user_data);
+
+gboolean nm_dbus_manager_start (NMDBusManager *self,
+                                NMDBusManagerSetPropertyHandler handler,
+                                gpointer handler_data);
+
+GDBusConnection *nm_dbus_manager_get_connection (NMDBusManager *self);
+
+NMDBusObject *nm_dbus_manager_lookup_object (NMDBusManager *self, const char *path);
+
+void _nm_dbus_manager_obj_export (NMDBusObject *obj);
+void _nm_dbus_manager_obj_unexport (NMDBusObject *obj);
+void _nm_dbus_manager_obj_notify (NMDBusObject *obj,
+                                  guint n_pspecs,
+                                  const GParamSpec *const*pspecs);
+void _nm_dbus_manager_obj_emit_signal (NMDBusObject *obj,
+                                       const NMDBusInterfaceInfoExtended *interface_info,
+                                       const GDBusSignalInfo *signal_info,
+                                       GVariant *args);
+
+gboolean nm_dbus_manager_get_caller_info (NMDBusManager *self,
+                                          GDBusMethodInvocation *context,
+                                          char **out_sender,
+                                          gulong *out_uid,
+                                          gulong *out_pid);
+
+gboolean nm_dbus_manager_ensure_uid (NMDBusManager          *self,
+                                     GDBusMethodInvocation *context,
+                                     gulong uid,
+                                     GQuark error_domain,
+                                     int error_code);
+
+const char *nm_dbus_manager_connection_get_private_name (NMDBusManager *self,
+                                                         GDBusConnection *connection);
+
+gboolean nm_dbus_manager_get_unix_user (NMDBusManager *self,
+                                        const char *sender,
+                                        gulong *out_uid);
+
+gboolean nm_dbus_manager_get_caller_info_from_message (NMDBusManager *self,
+                                                       GDBusConnection *connection,
+                                                       GDBusMessage *message,
+                                                       char **out_sender,
+                                                       gulong *out_uid,
+                                                       gulong *out_pid);
+
+void nm_dbus_manager_private_server_register (NMDBusManager *self,
+                                              const char *path,
+                                              const char *tag);
+
+GDBusProxy *nm_dbus_manager_new_proxy (NMDBusManager *self,
+                                       GDBusConnection *connection,
+                                       GType proxy_type,
+                                       const char *name,
+                                       const char *path,
+                                       const char *iface);
+
+#endif /* __NM_DBUS_MANAGER_H__ */
diff --git a/src/nm-dbus-object.c b/src/nm-dbus-object.c
new file mode 100644
index 00000000..514fda49
--- /dev/null
+++ b/src/nm-dbus-object.c
@@ -0,0 +1,320 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-dbus-object.h"
+
+#include "nm-dbus-manager.h"
+
+/*****************************************************************************/
+
+static gboolean quitting = FALSE;
+
+void
+nm_dbus_object_set_quitting (void)
+{
+	nm_assert (!quitting);
+	quitting = TRUE;
+}
+
+/*****************************************************************************/
+
+enum {
+	EXPORTED_CHANGED,
+
+	LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_ABSTRACT_TYPE (NMDBusObject, nm_dbus_object, G_TYPE_OBJECT);
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_CORE
+#define _NMLOG(level, ...) __NMLOG_DEFAULT_WITH_ADDR (level, _NMLOG_DOMAIN, "dbus-object", __VA_ARGS__)
+
+#define _NMLOG2_DOMAIN      LOGD_DBUS_PROPS
+#define _NMLOG2(level, ...) __NMLOG_DEFAULT_WITH_ADDR (level, _NMLOG2_DOMAIN, "properties-changed", __VA_ARGS__)
+
+/*****************************************************************************/
+
+static void
+_emit_exported_changed (NMDBusObject *self)
+{
+	g_signal_emit (self, signals[EXPORTED_CHANGED], 0);
+}
+
+static char *
+_create_export_path (NMDBusObjectClass *klass)
+{
+	nm_assert (NM_IS_DBUS_OBJECT_CLASS (klass));
+	nm_assert (klass->export_path.path);
+
+#if NM_MORE_ASSERTS
+	{
+		const char *p;
+
+		p = strchr (klass->export_path.path, '%');
+		if (klass->export_path.int_counter) {
+			nm_assert (p);
+			nm_assert (p[1] == 'l');
+			nm_assert (p[2] == 'l');
+			nm_assert (p[3] == 'u');
+			nm_assert (p[4] == '\0');
+		} else
+			nm_assert (!p);
+	}
+#endif
+
+	if (klass->export_path.int_counter) {
+		NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral")
+		return g_strdup_printf (klass->export_path.path,
+		                        ++(*klass->export_path.int_counter));
+		NM_PRAGMA_WARNING_REENABLE
+	}
+	return g_strdup (klass->export_path.path);
+}
+
+/**
+ * nm_dbus_object_export:
+ * @self: an #NMDBusObject
+ *
+ * Exports @self on all active and future D-Bus connections.
+ *
+ * The path to export @self on is taken from its #NMObjectClass's %export_path
+ * member. If the %export_path contains "%u", then it will be replaced with a
+ * monotonically increasing integer ID (with each distinct %export_path having
+ * its own counter). Otherwise, %export_path will be used literally (implying
+ * that @self must be a singleton).
+ *
+ * Returns: the path @self was exported under
+ */
+const char *
+nm_dbus_object_export (NMDBusObject *self)
+{
+	static guint64 id_counter = 0;
+
+	g_return_val_if_fail (NM_IS_DBUS_OBJECT (self), NULL);
+
+	g_return_val_if_fail (!self->internal.path, self->internal.path);
+
+	nm_assert (!self->internal.is_unexporting);
+
+	self->internal.path = _create_export_path (NM_DBUS_OBJECT_GET_CLASS (self));
+
+	self->internal.export_version_id = ++id_counter;
+
+	_LOGT ("export: \"%s\"", self->internal.path);
+
+	_nm_dbus_manager_obj_export (self);
+
+	_emit_exported_changed (self);
+	return self->internal.path;
+}
+
+/**
+ * nm_dbus_object_unexport:
+ * @self: an #NMDBusObject
+ *
+ * Unexports @self on all active D-Bus connections (and prevents it from being
+ * auto-exported on future connections).
+ */
+void
+nm_dbus_object_unexport (NMDBusObject *self)
+{
+	g_return_if_fail (NM_IS_DBUS_OBJECT (self));
+
+	g_return_if_fail (self->internal.path);
+
+	_LOGT ("unexport: \"%s\"", self->internal.path);
+
+	/* note that we emit the signal *before* actually unexporting the object.
+	 * The reason is, that listeners want to use this signal to know that
+	 * the object goes away, and clear their D-Bus path to this object.
+	 *
+	 * But this must happen before we actually unregister the object, so
+	 * that we first emit a D-Bus signal that other objects no longer
+	 * reference this object, before finally unregistering the object itself.
+	 *
+	 * The inconvenient part is, that at this point nm_dbus_object_get_path()
+	 * still returns the path. So, the callee needs to handle that. Possibly
+	 * by using "nm_dbus_object_get_path_still_exported()". */
+	self->internal.is_unexporting = TRUE;
+
+	_emit_exported_changed (self);
+
+	_nm_dbus_manager_obj_unexport (self);
+
+	g_clear_pointer (&self->internal.path, g_free);
+	self->internal.export_version_id = 0;
+
+	self->internal.is_unexporting = FALSE;
+}
+
+/*****************************************************************************/
+
+void
+_nm_dbus_object_clear_and_unexport (NMDBusObject **location)
+{
+	NMDBusObject *self;
+
+	g_return_if_fail (location);
+	if (!*location)
+		return;
+
+	self = g_steal_pointer (location);
+
+	g_return_if_fail (NM_IS_DBUS_OBJECT (self));
+
+	if (self->internal.path)
+		nm_dbus_object_unexport (self);
+
+	g_object_unref (self);
+}
+
+/*****************************************************************************/
+
+void
+nm_dbus_object_emit_signal_variant (NMDBusObject *self,
+                                    const NMDBusInterfaceInfoExtended *interface_info,
+                                    const GDBusSignalInfo *signal_info,
+                                    GVariant *args)
+{
+	if (!self->internal.path) {
+		nm_g_variant_unref_floating (args);
+		return;
+	}
+	_nm_dbus_manager_obj_emit_signal (self, interface_info, signal_info, args);
+}
+
+void
+nm_dbus_object_emit_signal (NMDBusObject *self,
+                            const NMDBusInterfaceInfoExtended *interface_info,
+                            const GDBusSignalInfo *signal_info,
+                            const char *format,
+                            ...)
+{
+	va_list ap;
+
+	nm_assert (NM_IS_DBUS_OBJECT (self));
+	nm_assert (format);
+
+	if (!self->internal.path)
+		return;
+
+	va_start (ap, format);
+	_nm_dbus_manager_obj_emit_signal (self,
+	                                 interface_info,
+	                                 signal_info,
+	                                 g_variant_new_va (format, NULL, &ap));
+	va_end (ap);
+}
+
+/*****************************************************************************/
+
+static void
+dispatch_properties_changed (GObject *object,
+                             guint n_pspecs,
+                             GParamSpec **pspecs)
+{
+	NMDBusObject *self = NM_DBUS_OBJECT (object);
+
+	if (self->internal.path)
+		_nm_dbus_manager_obj_notify (self, n_pspecs, (const GParamSpec *const*) pspecs);
+
+	G_OBJECT_CLASS (nm_dbus_object_parent_class)->dispatch_properties_changed (object, n_pspecs, pspecs);
+}
+
+/*****************************************************************************/
+
+static void
+nm_dbus_object_init (NMDBusObject *self)
+{
+	c_list_init (&self->internal.objects_lst);
+	c_list_init (&self->internal.registration_lst_head);
+	self->internal.bus_manager = nm_g_object_ref (nm_dbus_manager_get ());
+}
+
+static void
+constructed (GObject *object)
+{
+	NMDBusObjectClass *klass;
+
+	G_OBJECT_CLASS (nm_dbus_object_parent_class)->constructed (object);
+
+	klass = NM_DBUS_OBJECT_GET_CLASS (object);
+
+	if (klass->export_on_construction)
+		nm_dbus_object_export ((NMDBusObject *) object);
+
+	/* NMDBusObject types should be very careful when overwriting notify().
+	 * It is possible to do, but this is a reminder that it's probably not
+	 * a good idea.
+	 *
+	 * It's not a good idea, because NMDBusObject uses dispatch_properties_changed()
+	 * to emit signals about a bunch of property changes. So, we want to make
+	 * use of g_object_freeze_notify() / g_object_thaw_notify() to combine multiple
+	 * property changes in one signal on D-Bus. Note that notify() is not invoked
+	 * while the signal is frozen, that means, whatever you do inside notify()
+	 * will not make it into the same batch of PropertiesChanged signal. That is
+	 * confusing, and probably not what you want.
+	 *
+	 * Simple solution: don't overwrite notify(). */
+	nm_assert (!G_OBJECT_CLASS (klass)->notify);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDBusObject *self = NM_DBUS_OBJECT (object);
+
+	/* Objects should have already been unexported by their owner, unless
+	 * we are quitting, where many objects stick around until exit.
+	 */
+	if (self->internal.path) {
+		if (!quitting)
+			g_warn_if_reached ();
+		nm_dbus_object_unexport (self);
+	}
+
+	G_OBJECT_CLASS (nm_dbus_object_parent_class)->dispose (object);
+
+	g_clear_object (&self->internal.bus_manager);
+}
+
+static void
+nm_dbus_object_class_init (NMDBusObjectClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->constructed = constructed;
+	object_class->dispose = dispose;
+	object_class->dispatch_properties_changed = dispatch_properties_changed;
+
+	signals[EXPORTED_CHANGED] =
+	    g_signal_new (NM_DBUS_OBJECT_EXPORTED_CHANGED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL,
+	                  g_cclosure_marshal_VOID__VOID,
+	                  G_TYPE_NONE, 0);
+}
diff --git a/src/nm-dbus-object.h b/src/nm-dbus-object.h
new file mode 100644
index 00000000..43613630
--- /dev/null
+++ b/src/nm-dbus-object.h
@@ -0,0 +1,202 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_DBUS_OBJECT_H__
+#define __NM_DBUS_OBJECT_H__
+
+/*****************************************************************************/
+
+#include "c-list/src/c-list.h"
+#include "nm-dbus-utils.h"
+
+/*****************************************************************************/
+
+void nm_dbus_object_set_quitting (void);
+
+/*****************************************************************************/
+
+typedef struct {
+	const char *path;
+
+	/* if path is of type NM_DBUS_EXPORT_PATH_NUMBERED(), we need a
+	 * per-class counter when generating a new numbered path.
+	 *
+	 * Each NMDBusObjectClass instance has a shallow clone of the NMDBusObjectClass parent
+	 * instance in every derived type. Hence we cannot embed the counter there directly,
+	 * because it must be shared, e.g. between NMDeviceBond and NMDeviceEthernet.
+	 * Make int_counter a pointer to the actual counter that is used by ever sibling
+	 * class. */
+	long long unsigned *int_counter;
+} NMDBusExportPath;
+
+#define NM_DBUS_EXPORT_PATH_STATIC(basepath) \
+	({ \
+		((NMDBusExportPath) { \
+			.path = ""basepath"", \
+		}); \
+	})
+
+#define NM_DBUS_EXPORT_PATH_NUMBERED(basepath) \
+	({ \
+		static long long unsigned _int_counter = 0; \
+		((NMDBusExportPath) { \
+			.path = ""basepath"/%llu", \
+			.int_counter = &_int_counter, \
+		}); \
+	})
+
+/*****************************************************************************/
+
+/* "org.freedesktop.NetworkManager.Device.Statistics" is a special interface,
+ * because although it has a legacy PropertiesChanged signal, it only notifies
+ * about properties that actually exist on that interface. That is, because it
+ * was added with 1.4.0 release, and thus didn't have the broken behavior like
+ * other legacy interfaces. Those notify about *all* properties, even if they
+ * are not part of that D-Bus interface. See also "include_in_legacy_property_changed"
+ * and "legacy_property_changed". */
+extern const NMDBusInterfaceInfoExtended nm_interface_info_device_statistics;
+
+/*****************************************************************************/
+
+#define NM_TYPE_DBUS_OBJECT            (nm_dbus_object_get_type ())
+#define NM_DBUS_OBJECT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DBUS_OBJECT, NMDBusObject))
+#define NM_DBUS_OBJECT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DBUS_OBJECT, NMDBusObjectClass))
+#define NM_IS_DBUS_OBJECT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DBUS_OBJECT))
+#define NM_IS_DBUS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DBUS_OBJECT))
+#define NM_DBUS_OBJECT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DBUS_OBJECT, NMDBusObjectClass))
+
+#define NM_DBUS_OBJECT_EXPORTED_CHANGED "exported-changed"
+
+/* NMDBusObject and NMDBusManager cooperate strongly. Hence, there is an
+ * internal data structure attached to the NMDBusObject accessible to both of them. */
+struct _NMDBusObjectInternal {
+	char *path;
+	NMDBusManager *bus_manager;
+	CList objects_lst;
+	CList registration_lst_head;
+
+	/* we perform asynchronous operation on exported objects. For example, we receive
+	 * a Set property call, and asynchronously validate the operation. We must make
+	 * sure that when the authentication is complete, that we are still looking at
+	 * the same (exported) object. In the meantime, the object could have been
+	 * unexported, or even re-exported afterwards. If that happens, we want
+	 * to fail the request. For that, we keep track of a version id.  */
+	guint64 export_version_id;
+	bool is_unexporting:1;
+};
+
+struct _NMDBusObject {
+	GObject parent;
+	struct _NMDBusObjectInternal internal;
+};
+
+#define NM_DEFINE_DBUS_INTERFACE_INFO(...) \
+	((NMDBusInterfaceInfo *) (&((const NMDBusInterfaceInfo) { \
+		__VA_ARGS__ \
+	})))
+
+typedef struct {
+	GObjectClass parent;
+
+	NMDBusExportPath export_path;
+
+	const NMDBusInterfaceInfoExtended *const*interface_infos;
+
+	bool export_on_construction;
+} NMDBusObjectClass;
+
+GType nm_dbus_object_get_type (void);
+
+static inline NMDBusManager *
+nm_dbus_object_get_manager (NMDBusObject *obj)
+{
+	nm_assert (NM_IS_DBUS_OBJECT (obj));
+
+	return obj->internal.bus_manager;
+}
+
+static inline guint64
+nm_dbus_object_get_export_version_id (NMDBusObject *obj)
+{
+	nm_assert (NM_IS_DBUS_OBJECT (obj));
+
+	return obj->internal.export_version_id;
+}
+
+/**
+ * nm_dbus_object_get_path:
+ * @self: an #NMDBusObject
+ *
+ * Gets @self's D-Bus path.
+ *
+ * Returns: @self's D-Bus path, or %NULL if @self is not exported.
+ */
+static inline const char *
+nm_dbus_object_get_path (NMDBusObject *self)
+{
+	g_return_val_if_fail (NM_IS_DBUS_OBJECT (self), NULL);
+
+	return self->internal.path;
+}
+
+/**
+ * nm_dbus_object_is_exported:
+ * @self: an #NMDBusObject
+ *
+ * Checks if @self is exported
+ *
+ * Returns: %TRUE if @self is exported
+ */
+static inline gboolean
+nm_dbus_object_is_exported (NMDBusObject *self)
+{
+	return !!nm_dbus_object_get_path (self);
+}
+
+static inline const char *
+nm_dbus_object_get_path_still_exported (NMDBusObject *self)
+{
+	g_return_val_if_fail (NM_IS_DBUS_OBJECT (self), NULL);
+
+	/* like nm_dbus_object_get_path(), however, while unexporting
+	 * (exported-changed signal), returns %NULL instead of the path. */
+	return self->internal.is_unexporting
+	       ? NULL
+	       : self->internal.path;
+}
+
+const char *nm_dbus_object_export      (NMDBusObject *self);
+void        nm_dbus_object_unexport    (NMDBusObject *self);
+
+void        _nm_dbus_object_clear_and_unexport (NMDBusObject **location);
+#define nm_dbus_object_clear_and_unexport(location) _nm_dbus_object_clear_and_unexport ((NMDBusObject **) (location))
+
+void        nm_dbus_object_emit_signal_variant (NMDBusObject *self,
+                                                const NMDBusInterfaceInfoExtended *interface_info,
+                                                const GDBusSignalInfo *signal_info,
+                                                GVariant *args);
+
+void        nm_dbus_object_emit_signal (NMDBusObject *self,
+                                        const NMDBusInterfaceInfoExtended *interface_info,
+                                        const GDBusSignalInfo *signal_info,
+                                        const char *format,
+                                        ...);
+
+#endif /* __NM_DBUS_OBJECT_H__ */
diff --git a/src/nm-dbus-utils.c b/src/nm-dbus-utils.c
new file mode 100644
index 00000000..8e7dd122
--- /dev/null
+++ b/src/nm-dbus-utils.c
@@ -0,0 +1,312 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-dbus-utils.h"
+
+#include "nm-dbus-object.h"
+
+/*****************************************************************************/
+
+const GDBusSignalInfo nm_signal_info_property_changed_legacy = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"PropertiesChanged",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("properties", "a{sv}"),
+	),
+);
+
+GDBusPropertyInfo *
+nm_dbus_utils_interface_info_lookup_property (const GDBusInterfaceInfo *interface_info,
+                                              const char *property_name,
+                                              guint *property_idx)
+{
+	guint i;
+
+	nm_assert (interface_info);
+	nm_assert (property_name);
+
+	/* there is also g_dbus_interface_info_lookup_property(), however that makes use
+	 * of a global cache. */
+	if (interface_info->properties) {
+		for (i = 0; interface_info->properties[i]; i++) {
+			GDBusPropertyInfo *info = interface_info->properties[i];
+
+			if (nm_streq (info->name, property_name)) {
+				NM_SET_OUT (property_idx, i);
+				return info;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+GDBusMethodInfo *
+nm_dbus_utils_interface_info_lookup_method (const GDBusInterfaceInfo *interface_info,
+                                            const char *method_name)
+{
+	guint i;
+
+	nm_assert (interface_info);
+	nm_assert (method_name);
+
+	/* there is also g_dbus_interface_info_lookup_property(), however that makes use
+	 * of a global cache. */
+	if (interface_info->methods) {
+		for (i = 0; interface_info->methods[i]; i++) {
+			GDBusMethodInfo *info = interface_info->methods[i];
+
+			if (nm_streq (info->name, method_name))
+				return info;
+		}
+	}
+
+	return NULL;
+}
+
+GVariant *
+nm_dbus_utils_get_property (GObject *obj,
+                            const char *signature,
+                            const char *property_name)
+{
+	GParamSpec *pspec;
+	nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
+
+	nm_assert (G_IS_OBJECT (obj));
+	nm_assert (g_variant_type_string_is_valid (signature));
+	nm_assert (property_name && property_name[0]);
+
+	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (obj), property_name);
+	if (!pspec)
+		g_return_val_if_reached (NULL);
+
+	g_value_init (&value, pspec->value_type);
+	g_object_get_property (obj, property_name, &value);
+	/* returns never-floating variant */
+	return g_dbus_gvalue_to_gvariant (&value, G_VARIANT_TYPE (signature));
+}
+
+/*****************************************************************************/
+
+void
+nm_dbus_utils_g_value_set_object_path (GValue *value, gpointer object)
+{
+	const char *path;
+
+	g_return_if_fail (!object || NM_IS_DBUS_OBJECT (object));
+
+	if (   object
+	    && (path = nm_dbus_object_get_path (object)))
+		g_value_set_string (value, path);
+	else
+		g_value_set_string (value, NULL);
+}
+
+void
+nm_dbus_utils_g_value_set_object_path_still_exported (GValue *value, gpointer object)
+{
+	const char *path;
+
+	g_return_if_fail (!object || NM_IS_DBUS_OBJECT (object));
+
+	if (   object
+	    && (path = nm_dbus_object_get_path_still_exported (object)))
+		g_value_set_string (value, path);
+	else
+		g_value_set_string (value, "/");
+}
+
+void
+nm_dbus_utils_g_value_set_object_path_from_hash (GValue *value,
+                                                 GHashTable *hash /* has keys of NMDBusObject type. */,
+                                                 gboolean expect_all_exported)
+{
+	NMDBusObject *obj;
+	char **strv;
+	guint i, n;
+	GHashTableIter iter;
+
+	nm_assert (value);
+	nm_assert (hash);
+
+	n = g_hash_table_size (hash);
+	strv = g_new (char *, n + 1);
+	i = 0;
+	g_hash_table_iter_init (&iter, hash);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) {
+		const char *path;
+
+		path = nm_dbus_object_get_path (obj);
+		if (!path) {
+			nm_assert (!expect_all_exported);
+			continue;
+		}
+		strv[i++] = g_strdup (path);
+	}
+	nm_assert (i <= n);
+	strv[i] = NULL;
+
+	/* sort the names, to give a well-defined, stable order. */
+	nm_utils_strv_sort (strv, i);
+
+	g_value_take_boxed (value, strv);
+}
+
+const char **
+nm_dbus_utils_get_paths_for_clist (const CList *lst_head,
+                                   gssize lst_len,
+                                   guint member_offset,
+                                   gboolean expect_all_exported)
+{
+	const CList *iter;
+	const char **strv;
+	const char *path;
+	gsize i, n;
+
+	nm_assert (lst_head);
+
+	if (lst_len < 0)
+		n = c_list_length (lst_head);
+	else {
+		n = lst_len;
+		nm_assert (n == c_list_length (lst_head));
+	}
+
+	i = 0;
+	strv = g_new (const char *, n + 1);
+	c_list_for_each (iter, lst_head) {
+		NMDBusObject *obj = (NMDBusObject *) (((const char *) iter) - member_offset);
+
+		path = nm_dbus_object_get_path (obj);
+		if (!path) {
+			nm_assert (expect_all_exported);
+			continue;
+		}
+
+		nm_assert (i < n);
+		strv[i++] = path;
+	}
+	nm_assert (i <= n);
+	strv[i] = NULL;
+
+	return strv;
+}
+
+/*****************************************************************************/
+
+void
+nm_dbus_track_obj_path_init (NMDBusTrackObjPath *track,
+                             GObject *target,
+                             const GParamSpec *pspec)
+{
+	nm_assert (track);
+	nm_assert (G_IS_OBJECT (target));
+	nm_assert (G_IS_PARAM_SPEC (pspec));
+
+	track->_obj = NULL;
+	track->_notify_target = target;
+	track->_notify_pspec = pspec;
+	track->_notify_signal_id = 0;
+	track->_visible = FALSE;
+}
+
+void
+nm_dbus_track_obj_path_deinit (NMDBusTrackObjPath *track)
+{
+	/* we allow deinit() to be called multiple times (e.g. from
+	 * dispose(), which must be re-entrant). */
+	nm_assert (track);
+	nm_assert (!track->_notify_target || G_IS_OBJECT (track->_notify_target));
+
+	nm_clear_g_signal_handler (track->obj, &track->_notify_signal_id);
+	track->_notify_target = NULL;
+	track->_notify_pspec = NULL;
+	track->_visible = FALSE;
+	nm_clear_g_object (&track->_obj);
+}
+
+void
+nm_dbus_track_obj_path_notify (const NMDBusTrackObjPath *track)
+{
+	nm_assert (track);
+	nm_assert (G_IS_OBJECT (track->_notify_target));
+	nm_assert (G_IS_PARAM_SPEC (track->_notify_pspec));
+
+	g_object_notify_by_pspec (track->_notify_target,
+	                          (GParamSpec *) track->_notify_pspec);
+}
+
+const char *
+nm_dbus_track_obj_path_get (const NMDBusTrackObjPath *track)
+{
+	nm_assert (track);
+	nm_assert (G_IS_OBJECT (track->_notify_target));
+
+	return track->obj && track->visible
+	       ? nm_dbus_object_get_path_still_exported (track->obj)
+	       : NULL;
+}
+
+static void
+_track_obj_exported_changed (NMDBusObject *obj,
+                             NMDBusTrackObjPath *track)
+{
+	nm_dbus_track_obj_path_notify (track);
+}
+
+void
+nm_dbus_track_obj_path_set (NMDBusTrackObjPath *track,
+                            gpointer obj,
+                            gboolean visible)
+{
+	gs_unref_object NMDBusObject *old_obj = NULL;
+	const char *old_path;
+
+	nm_assert (track);
+	nm_assert (G_IS_OBJECT (track->_notify_target));
+
+	g_return_if_fail (!obj || NM_IS_DBUS_OBJECT (obj));
+
+	if (   track->obj == obj
+	    && track->visible == !!visible)
+		return;
+
+	old_path = nm_dbus_track_obj_path_get (track);
+
+	track->_visible = visible;
+
+	if (track->obj != obj) {
+		nm_clear_g_signal_handler (track->obj, &track->_notify_signal_id);
+
+		old_obj = track->obj;
+		track->_obj = nm_g_object_ref (obj);
+
+		if (obj) {
+			track->_notify_signal_id = g_signal_connect (obj,
+			                                             NM_DBUS_OBJECT_EXPORTED_CHANGED,
+			                                             G_CALLBACK (_track_obj_exported_changed),
+			                                             track);
+		}
+	}
+
+	if (!nm_streq0 (old_path, nm_dbus_track_obj_path_get (track)))
+		nm_dbus_track_obj_path_notify (track);
+}
diff --git a/src/nm-dbus-utils.h b/src/nm-dbus-utils.h
new file mode 100644
index 00000000..e7e930e9
--- /dev/null
+++ b/src/nm-dbus-utils.h
@@ -0,0 +1,212 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_DBUS_UTILS_H__
+#define __NM_DBUS_UTILS_H__
+
+/*****************************************************************************/
+
+struct _NMDBusInterfaceInfoExtended;
+struct _NMDBusMethodInfoExtended;
+
+struct _NMDBusPropertyInfoExtendedBase {
+	GDBusPropertyInfo _parent;
+	const char *property_name;
+
+	/* Whether the properties needs to be notified on the legacy
+	 * PropertyChanged signal. This is only to preserve API, new
+	 * properties should not use this. */
+	bool include_in_legacy_property_changed;
+};
+
+struct _NMDBusPropertyInfoExtendedReadWritable {
+	struct _NMDBusPropertyInfoExtendedBase _base;
+
+	/* this is the polkit permission type for authenticating setting
+	 * the property. */
+	const char *permission;
+
+	/* this is the audit operation type for writing the property. */
+	const char *audit_op;
+};
+
+typedef struct {
+	union {
+
+		GDBusPropertyInfo _parent;
+		struct _NMDBusPropertyInfoExtendedBase _base;
+		struct _NMDBusPropertyInfoExtendedReadWritable writable;
+
+		/* duplicate the base structure in the union, so that the common fields
+		 * are accessible directly in the parent struct. */
+		struct {
+			GDBusPropertyInfo parent;
+			const char *property_name;
+
+			/* Whether the properties needs to be notified on the legacy
+			 * PropertyChanged signal. This is only to preserve API, new
+			 * properties should not use this. */
+			bool include_in_legacy_property_changed;
+		};
+	};
+} NMDBusPropertyInfoExtended;
+
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMDBusPropertyInfoExtended, property_name) == G_STRUCT_OFFSET (struct _NMDBusPropertyInfoExtendedBase, property_name));
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMDBusPropertyInfoExtended, include_in_legacy_property_changed) == G_STRUCT_OFFSET (struct _NMDBusPropertyInfoExtendedBase, include_in_legacy_property_changed));
+
+#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_FULL(m_name, m_signature, m_property_name, m_include_in_legacy_property_changed) \
+	((GDBusPropertyInfo *) &((const struct _NMDBusPropertyInfoExtendedBase) { \
+		._parent = { \
+			.ref_count = -1, \
+			.name = m_name, \
+			.signature = m_signature, \
+			.flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE, \
+		}, \
+		.property_name = m_property_name, \
+		.include_in_legacy_property_changed = m_include_in_legacy_property_changed, \
+	}))
+
+#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(m_name, m_signature, m_property_name) \
+	NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_FULL (m_name, m_signature, m_property_name, FALSE)
+
+/* define a legacy property. Do not use for new code. */
+#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L(m_name, m_signature, m_property_name) \
+	NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_FULL (m_name, m_signature, m_property_name, TRUE)
+
+#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_FULL(m_name, m_signature, m_property_name, m_permission, m_audit_op, m_include_in_legacy_property_changed) \
+	((GDBusPropertyInfo *) &((const struct _NMDBusPropertyInfoExtendedReadWritable) { \
+		._base = { \
+			._parent = { \
+				.ref_count = -1, \
+				.name = m_name, \
+				.signature = m_signature, \
+				.flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, \
+			}, \
+			.property_name = m_property_name, \
+			.include_in_legacy_property_changed = m_include_in_legacy_property_changed, \
+		}, \
+		.permission = m_permission, \
+		.audit_op = m_audit_op, \
+	}))
+
+#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE(m_name, m_signature, m_property_name, m_permission, m_audit_op) \
+	NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_FULL (m_name, m_signature, m_property_name, m_permission, m_audit_op, FALSE)
+
+/* define a legacy property. Do not use for new code. */
+#define NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L(m_name, m_signature, m_property_name, m_permission, m_audit_op) \
+	NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_FULL (m_name, m_signature, m_property_name, m_permission, m_audit_op, TRUE)
+
+typedef struct _NMDBusMethodInfoExtended {
+	GDBusMethodInfo parent;
+	void (*handle) (NMDBusObject *obj,
+	                const struct _NMDBusInterfaceInfoExtended *interface_info,
+	                const struct _NMDBusMethodInfoExtended *method_info,
+	                GDBusConnection *connection,
+	                const char *sender,
+	                GDBusMethodInvocation *invocation,
+	                GVariant *parameters);
+} NMDBusMethodInfoExtended;
+
+#define NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(parent_, ...) \
+	((GDBusMethodInfo *) (&((const NMDBusMethodInfoExtended) { \
+		.parent = parent_, \
+		__VA_ARGS__ \
+	})))
+
+typedef struct _NMDBusInterfaceInfoExtended {
+	GDBusInterfaceInfo parent;
+
+	/* Whether the interface has a legacy property changed signal (@nm_signal_info_property_changed_legacy).
+	 * New interfaces should not use this. */
+	bool legacy_property_changed:1;
+} NMDBusInterfaceInfoExtended;
+
+extern const GDBusSignalInfo nm_signal_info_property_changed_legacy;
+
+#define NM_DBUS_INTERFACE_INFOS(...) \
+	({ \
+		static const NMDBusInterfaceInfoExtended *const _interface_infos[] = { \
+			__VA_ARGS__, \
+			NULL, \
+		}; \
+		_interface_infos; \
+	});
+
+/*****************************************************************************/
+
+GDBusPropertyInfo *nm_dbus_utils_interface_info_lookup_property (const GDBusInterfaceInfo *interface_info,
+                                                                 const char *property_name,
+                                                                 guint *property_idx);
+
+GDBusMethodInfo *nm_dbus_utils_interface_info_lookup_method (const GDBusInterfaceInfo *interface_info,
+                                                             const char *method_name);
+
+GVariant *nm_dbus_utils_get_property (GObject *obj,
+                                      const char *signature,
+                                      const char *property_name);
+
+/*****************************************************************************/
+
+struct CList;
+
+const char **nm_dbus_utils_get_paths_for_clist (const struct CList *lst_head,
+                                                gssize lst_len,
+                                                guint member_offset,
+                                                gboolean expect_all_exported);
+
+void nm_dbus_utils_g_value_set_object_path (GValue *value, gpointer object);
+
+void nm_dbus_utils_g_value_set_object_path_still_exported (GValue *value, gpointer object);
+
+void nm_dbus_utils_g_value_set_object_path_from_hash (GValue *value,
+                                                      GHashTable *hash,
+                                                      gboolean expect_all_exported);
+
+/*****************************************************************************/
+
+typedef struct {
+	union {
+		gpointer const obj;
+		gpointer _obj;
+	};
+	GObject *_notify_target;
+	const GParamSpec *_notify_pspec;
+	gulong _notify_signal_id;
+	union {
+		const bool visible;
+		bool _visible;
+	};
+} NMDBusTrackObjPath;
+
+void nm_dbus_track_obj_path_init (NMDBusTrackObjPath *track,
+                                  GObject *target,
+                                  const GParamSpec *pspec);
+
+void nm_dbus_track_obj_path_deinit (NMDBusTrackObjPath *track);
+
+void nm_dbus_track_obj_path_notify (const NMDBusTrackObjPath *track);
+
+const char *nm_dbus_track_obj_path_get (const NMDBusTrackObjPath *track);
+
+void nm_dbus_track_obj_path_set (NMDBusTrackObjPath *track,
+                                 gpointer obj,
+                                 gboolean visible);
+
+#endif /* __NM_DBUS_UTILS_H__ */
diff --git a/src/nm-dhcp4-config.c b/src/nm-dhcp4-config.c
index 10f58bc6..08becf3c 100644
--- a/src/nm-dhcp4-config.c
+++ b/src/nm-dhcp4-config.c
@@ -26,9 +26,7 @@
 
 #include "nm-dbus-interface.h"
 #include "nm-utils.h"
-#include "nm-exported-object.h"
-
-#include "introspection/org.freedesktop.NetworkManager.DHCP4Config.h"
+#include "nm-dbus-object.h"
 
 /*****************************************************************************/
 
@@ -41,15 +39,15 @@ typedef struct {
 } NMDhcp4ConfigPrivate;
 
 struct _NMDhcp4Config {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	NMDhcp4ConfigPrivate _priv;
 };
 
 struct _NMDhcp4ConfigClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 };
 
-G_DEFINE_TYPE (NMDhcp4Config, nm_dhcp4_config, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMDhcp4Config, nm_dhcp4_config, NM_TYPE_DBUS_OBJECT)
 
 #define NM_DHCP4_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcp4Config, NM_IS_DHCP4_CONFIG)
 
@@ -147,17 +145,31 @@ finalize (GObject *object)
 	G_OBJECT_CLASS (nm_dhcp4_config_parent_class)->finalize (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_dhcp4_config = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DHCP4_CONFIG,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Options", "a{sv}",  NM_DHCP4_CONFIG_OPTIONS),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_dhcp4_config_class_init (NMDhcp4ConfigClass *config_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (config_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (config_class);
 
 	object_class->get_property = get_property;
 	object_class->finalize = finalize;
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/DHCP4Config");
-	exported_object_class->export_on_construction = TRUE;
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/DHCP4Config");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_dhcp4_config);
+	dbus_object_class->export_on_construction = TRUE;
 
 	obj_properties[PROP_OPTIONS] =
 	     g_param_spec_variant (NM_DHCP4_CONFIG_OPTIONS, "", "",
@@ -167,8 +179,4 @@ nm_dhcp4_config_class_init (NMDhcp4ConfigClass *config_class)
 	                           G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (config_class),
-	                                        NMDBUS_TYPE_DHCP4_CONFIG_SKELETON,
-	                                        NULL);
 }
diff --git a/src/nm-dhcp6-config.c b/src/nm-dhcp6-config.c
index d51d77ef..5bb6c740 100644
--- a/src/nm-dhcp6-config.c
+++ b/src/nm-dhcp6-config.c
@@ -26,9 +26,7 @@
 
 #include "nm-dbus-interface.h"
 #include "nm-utils.h"
-#include "nm-exported-object.h"
-
-#include "introspection/org.freedesktop.NetworkManager.DHCP6Config.h"
+#include "nm-dbus-object.h"
 
 /*****************************************************************************/
 
@@ -41,15 +39,15 @@ typedef struct {
 } NMDhcp6ConfigPrivate;
 
 struct _NMDhcp6Config {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	NMDhcp6ConfigPrivate _priv;
 };
 
 struct _NMDhcp6ConfigClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 };
 
-G_DEFINE_TYPE (NMDhcp6Config, nm_dhcp6_config, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMDhcp6Config, nm_dhcp6_config, NM_TYPE_DBUS_OBJECT)
 
 #define NM_DHCP6_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcp6Config, NM_IS_DHCP6_CONFIG)
 
@@ -145,17 +143,31 @@ finalize (GObject *object)
 	G_OBJECT_CLASS (nm_dhcp6_config_parent_class)->finalize (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_dhcp6_config = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DHCP6_CONFIG,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Options", "a{sv}",  NM_DHCP6_CONFIG_OPTIONS),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_dhcp6_config_class_init (NMDhcp6ConfigClass *config_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (config_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (config_class);
 
 	object_class->get_property = get_property;
 	object_class->finalize = finalize;
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/DHCP6Config");
-	exported_object_class->export_on_construction = TRUE;
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/DHCP6Config");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_dhcp6_config);
+	dbus_object_class->export_on_construction = TRUE;
 
 	obj_properties[PROP_OPTIONS] =
 	     g_param_spec_variant (NM_DHCP6_CONFIG_OPTIONS, "", "",
@@ -165,8 +177,4 @@ nm_dhcp6_config_class_init (NMDhcp6ConfigClass *config_class)
 	                           G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (config_class),
-	                                        NMDBUS_TYPE_DHCP6_CONFIG_SKELETON,
-	                                        NULL);
 }
diff --git a/src/nm-dispatcher.c b/src/nm-dispatcher.c
index 237afdef..235134c8 100644
--- a/src/nm-dispatcher.c
+++ b/src/nm-dispatcher.c
@@ -284,9 +284,10 @@ fill_device_props (NMDevice *device,
 	                       g_variant_new_uint32 (nm_device_get_device_type (device)));
 	g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_STATE,
 	                       g_variant_new_uint32 (nm_device_get_state (device)));
-	if (nm_exported_object_is_exported (NM_EXPORTED_OBJECT (device)))
+	if (nm_dbus_object_is_exported (NM_DBUS_OBJECT (device))) {
 		g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_PATH,
-		                       g_variant_new_object_path (nm_exported_object_get_path (NM_EXPORTED_OBJECT (device))));
+		                       g_variant_new_object_path (nm_dbus_object_get_path (NM_DBUS_OBJECT (device))));
+	}
 
 	proxy_config = nm_device_get_proxy_config (device);
 	if (proxy_config)
@@ -345,8 +346,8 @@ static void
 _ensure_requests (void)
 {
 	if (G_UNLIKELY (requests == NULL)) {
-		requests = g_hash_table_new_full (g_direct_hash,
-		                                  g_direct_equal,
+		requests = g_hash_table_new_full (nm_direct_hash,
+		                                  NULL,
 		                                  NULL,
 		                                  (GDestroyNotify) dispatcher_info_free);
 	}
@@ -578,7 +579,7 @@ _dispatcher_call (NMDispatcherAction action,
 		const char *connection_path;
 		const char *filename;
 
-		connection_path = nm_connection_get_path (NM_CONNECTION (settings_connection));
+		connection_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (settings_connection));
 		if (connection_path) {
 			g_variant_builder_add (&connection_props, "{sv}",
 			                       NMD_CONNECTION_PROPS_PATH,
@@ -630,9 +631,7 @@ _dispatcher_call (NMDispatcherAction action,
 	if (!device_dhcp6_props)
 		device_dhcp6_props = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
 
-#if WITH_CONCHECK
 	connectivity_state_string = nm_connectivity_state_to_string (connectivity_state);
-#endif
 
 	/* Send the action to the dispatcher */
 	if (blocking) {
diff --git a/src/nm-exported-object.c b/src/nm-exported-object.c
deleted file mode 100644
index 94264caa..00000000
--- a/src/nm-exported-object.c
+++ /dev/null
@@ -1,1055 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2014-2016 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-exported-object.h"
-
-#include <stdarg.h>
-#include <string.h>
-
-#include "nm-bus-manager.h"
-
-#include "devices/nm-device.h"
-#include "nm-active-connection.h"
-#include "introspection/org.freedesktop.NetworkManager.Device.Statistics.h"
-
-#if NM_MORE_ASSERTS >= 2
-#define _ASSERT_NO_EARLY_EXPORT
-#endif
-
-/*****************************************************************************/
-
-static gboolean quitting = FALSE;
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE (NMExportedObject,
-	PROP_PATH,
-);
-
-typedef struct {
-	GDBusInterfaceSkeleton *interface;
-	guint property_changed_signal_id;
-	GHashTable *pending_notifies;
-} InterfaceData;
-
-typedef struct _NMExportedObjectPrivate {
-	NMBusManager *bus_mgr;
-	char *path;
-
-	InterfaceData *interfaces;
-	guint num_interfaces;
-
-	guint notify_idle_id;
-
-#ifdef _ASSERT_NO_EARLY_EXPORT
-	bool _constructed:1;
-#endif
-} NMExportedObjectPrivate;
-
-G_DEFINE_ABSTRACT_TYPE (NMExportedObject, nm_exported_object, G_TYPE_DBUS_OBJECT_SKELETON);
-
-#define NM_EXPORTED_OBJECT_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMExportedObject, NM_IS_EXPORTED_OBJECT)
-
-/*****************************************************************************/
-
-typedef struct {
-	GHashTable *properties;
-	GSList *skeleton_types;
-	GArray *methods;
-} NMExportedObjectClassInfo;
-
-static NM_CACHED_QUARK_FCN ("NMExportedObjectClassInfo", nm_exported_object_class_info_quark)
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN      LOGD_CORE
-#define _NMLOG(level, ...) __NMLOG_DEFAULT_WITH_ADDR (level, _NMLOG_DOMAIN, "exported-object", __VA_ARGS__)
-
-#define _NMLOG2_DOMAIN      LOGD_DBUS_PROPS
-#define _NMLOG2(level, ...) __NMLOG_DEFAULT_WITH_ADDR (level, _NMLOG2_DOMAIN, "properties-changed", __VA_ARGS__)
-
-/*****************************************************************************/
-
-/* "AddConnectionUnsaved" -> "handle-add-connection-unsaved" */
-char *
-nm_exported_object_skeletonify_method_name (const char *dbus_method_name)
-{
-	GString *out;
-	const char *p;
-
-	out = g_string_new ("handle");
-	for (p = dbus_method_name; *p; p++) {
-		if (g_ascii_isupper (*p) || p == dbus_method_name) {
-			g_string_append_c (out, '-');
-			g_string_append_c (out, g_ascii_tolower (*p));
-		} else
-			g_string_append_c (out, *p);
-	}
-
-	return g_string_free (out, FALSE);
-}
-
-/* "can-modify" -> "CanModify" */
-static char *
-dbusify_name (const char *gobject_name)
-{
-	GString *out;
-	const char *p;
-	gboolean capitalize = TRUE;
-
-	out = g_string_new ("");
-	for (p = gobject_name; *p; p++) {
-		if (capitalize) {
-			g_string_append_c (out, g_ascii_toupper (*p));
-			capitalize = FALSE;
-		} else if (*p == '-')
-			capitalize = TRUE;
-		else
-			g_string_append_c (out, *p);
-	}
-
-	return g_string_free (out, FALSE);
-}
-
-/* "can_modify" -> "can-modify". Returns %NULL if @gobject_name contains no underscores */
-static char *
-hyphenify_name (const char *gobject_name)
-{
-	char *hyphen_name, *p;
-
-	if (!strchr (gobject_name, '_'))
-		return NULL;
-
-	hyphen_name = g_strdup (gobject_name);
-	for (p = hyphen_name; *p; p++) {
-		if (*p == '_')
-			*p = '-';
-	}
-	return hyphen_name;
-}
-
-/* Called when an #NMExportedObject emits a signal that corresponds to a D-Bus
- * signal, and re-emits that signal on the correct skeleton object as well.
- */
-static gboolean
-nm_exported_object_signal_hook (GSignalInvocationHint *ihint,
-                                guint                  n_param_values,
-                                const GValue          *param_values,
-                                gpointer               data)
-{
-	NMExportedObject *self = g_value_get_object (&param_values[0]);
-	NMExportedObjectPrivate *priv;
-	GSignalQuery *signal_info = data;
-	GDBusInterfaceSkeleton *interface = NULL;
-	GValue *dbus_param_values;
-	guint i;
-
-	priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
-	if (!priv->path)
-		return TRUE;
-
-	for (i = 0; i < priv->num_interfaces; i++) {
-		InterfaceData *ifdata = &priv->interfaces[i];
-
-		if (g_type_is_a (G_OBJECT_TYPE (ifdata->interface), signal_info->itype)) {
-			interface = ifdata->interface;
-			break;
-		}
-	}
-	g_return_val_if_fail (interface != NULL, TRUE);
-
-	dbus_param_values = g_newa (GValue, n_param_values);
-	memset (dbus_param_values, 0, sizeof (GValue) * n_param_values);
-	g_value_init (&dbus_param_values[0], G_OBJECT_TYPE (interface));
-	g_value_set_object (&dbus_param_values[0], interface);
-	for (i = 1; i < n_param_values; i++) {
-		if (g_type_is_a (param_values[i].g_type, NM_TYPE_EXPORTED_OBJECT)) {
-			NMExportedObject *arg = g_value_get_object (&param_values[i]);
-
-			g_value_init (&dbus_param_values[i], G_TYPE_STRING);
-			if (arg && nm_exported_object_is_exported (arg))
-				g_value_set_string (&dbus_param_values[i], nm_exported_object_get_path (arg));
-			else
-				g_value_set_string (&dbus_param_values[i], "/");
-		} else {
-			g_value_init (&dbus_param_values[i], param_values[i].g_type);
-			g_value_copy (&param_values[i], &dbus_param_values[i]);
-		}
-	}
-
-	g_signal_emitv (dbus_param_values, signal_info->signal_id, 0, NULL);
-
-	for (i = 0; i < n_param_values; i++)
-		g_value_unset (&dbus_param_values[i]);
-
-	return TRUE;
-}
-
-/**
- * nm_exported_object_class_add_interface:
- * @object_class: an #NMExportedObjectClass
- * @dbus_skeleton_type: the type of the #GDBusInterfaceSkeleton to add
- * @...: method name / handler pairs, %NULL-terminated
- *
- * Adds @dbus_skeleton_type to the list of D-Bus interfaces implemented by
- * @object_class. Instances of @object_class will automatically have a skeleton
- * of that type created, which will be exported when you call
- * nm_exported_object_export().
- *
- * The skeleton's properties will be initialized from the #NMExportedObject's,
- * and bidirectional bindings will be set up between them. When exported
- * properties change, both the org.freedesktop.DBus.Properties.PropertiesChanged
- * signal and the traditional NetworkManager PropertiesChanged signal will be
- * emitted.
- *
- * When a signal is emitted on an #NMExportedObject that has the same name as a
- * signal on @dbus_skeleton_type, it will automatically be emitted on the
- * skeleton as well; #NMExportedObject arguments in the signal will be converted
- * to D-Bus object paths in the skeleton signal.
- *
- * The arguments after @dbus_skeleton_type are pairs of D-Bus method names (in
- * CamelCase), and the corresponding handlers for them (which must have the same
- * prototype as the corresponding "handle-..." signal on @dbus_skeleton_type,
- * except with no return value, and with the first argument being an object of
- * @object_class's type, not of @dbus_skeleton_type).
- *
- * It is a programmer error if:
- *   - @object_class does not define a property of the same name and type as
- *     each of @dbus_skeleton_type's properties.
- *   - @object_class does not define a signal with the same name and arguments
- *     as each of @dbus_skeleton_type's signals.
- *   - the list of method names includes any names that do not correspond to
- *     "handle-" signals on @dbus_skeleton_type.
- *   - the list of method names does not include every method defined by
- *     @dbus_skeleton_type.
- */
-void
-nm_exported_object_class_add_interface (NMExportedObjectClass *object_class,
-                                        GType                  dbus_skeleton_type,
-                                        ...)
-{
-	NMExportedObjectClassInfo *classinfo;
-	NMExportedObjectDBusMethodImpl method;
-	va_list ap;
-	const char *method_name;
-	GCallback impl;
-	gs_free GType *interfaces = NULL;
-	guint n_interfaces;
-	guint n_signals, n_method_signals;
-	guint object_signal_id;
-	GSignalQuery query;
-	int i, s;
-	GObjectClass *dbus_object_class;
-	gs_free GParamSpec **dbus_properties = NULL;
-	GParamSpec *object_property;
-	guint n_dbus_properties;
-
-	g_return_if_fail (NM_IS_EXPORTED_OBJECT_CLASS (object_class));
-	g_return_if_fail (g_type_is_a (dbus_skeleton_type, G_TYPE_DBUS_INTERFACE_SKELETON));
-
-	classinfo = g_type_get_qdata (G_TYPE_FROM_CLASS (object_class),
-	                              nm_exported_object_class_info_quark ());
-	if (!classinfo) {
-		classinfo = g_slice_new (NMExportedObjectClassInfo);
-		classinfo->skeleton_types = NULL;
-		classinfo->methods = g_array_new (FALSE, FALSE, sizeof (NMExportedObjectDBusMethodImpl));
-		classinfo->properties = g_hash_table_new (nm_str_hash, g_str_equal);
-		g_type_set_qdata (G_TYPE_FROM_CLASS (object_class),
-		                  nm_exported_object_class_info_quark (), classinfo);
-	}
-
-	classinfo->skeleton_types = g_slist_prepend (classinfo->skeleton_types,
-	                                             GSIZE_TO_POINTER (dbus_skeleton_type));
-
-	/* Ensure @dbus_skeleton_type's class_init has run, so its signals/properties
-	 * will be defined.
-	 */
-	dbus_object_class = g_type_class_ref (dbus_skeleton_type);
-
-	/* Add method implementations from the varargs */
-	va_start (ap, dbus_skeleton_type);
-	while ((method_name = va_arg (ap, const char *)) && (impl = va_arg (ap, GCallback))) {
-		method.dbus_skeleton_type = dbus_skeleton_type;
-		method.method_name = nm_exported_object_skeletonify_method_name (method_name);
-		g_assert (g_signal_lookup (method.method_name, dbus_skeleton_type) != 0);
-		method.impl = impl;
-
-		g_array_append_val (classinfo->methods, method);
-	}
-	va_end (ap);
-
-	/* Properties */
-	dbus_properties = g_object_class_list_properties (dbus_object_class, &n_dbus_properties);
-	for (i = 0; i < n_dbus_properties; i++) {
-		char *hyphen_name;
-
-		if (g_str_has_prefix (dbus_properties[i]->name, "g-"))
-			continue;
-
-		object_property = g_object_class_find_property (G_OBJECT_CLASS (object_class),
-		                                                dbus_properties[i]->name);
-		g_assert (object_property != NULL);
-		g_assert (object_property->value_type == dbus_properties[i]->value_type);
-
-		g_assert (!g_hash_table_contains (classinfo->properties, dbus_properties[i]->name));
-		g_hash_table_insert (classinfo->properties,
-		                     g_strdup (dbus_properties[i]->name),
-		                     dbusify_name (dbus_properties[i]->name));
-		hyphen_name = hyphenify_name (dbus_properties[i]->name);
-		if (hyphen_name) {
-			g_assert (!g_hash_table_contains (classinfo->properties, hyphen_name));
-			g_hash_table_insert (classinfo->properties,
-			                     hyphen_name,
-			                     dbusify_name (dbus_properties[i]->name));
-		}
-	}
-
-	/* Signals. Unlike g_object_class_list_properties(), g_signal_list_ids() is
-	 * "shallow", so we need to query each implemented gdbus-generated interface
-	 * separately.
-	 */
-	interfaces = g_type_interfaces (dbus_skeleton_type, &n_interfaces);
-	n_method_signals = 0;
-	for (i = 0; i < n_interfaces; i++) {
-		gs_free guint *dbus_signals = NULL;
-
-		dbus_signals = g_signal_list_ids (interfaces[i], &n_signals);
-		for (s = 0; s < n_signals; s++) {
-			g_signal_query (dbus_signals[s], &query);
-
-			/* PropertiesChanged is handled specially */
-			if (!strcmp (query.signal_name, "properties-changed"))
-				continue;
-
-			if (g_str_has_prefix (query.signal_name, "handle-")) {
-				n_method_signals++;
-				continue;
-			}
-
-			object_signal_id = g_signal_lookup (query.signal_name, G_TYPE_FROM_CLASS (object_class));
-			g_assert (object_signal_id != 0);
-
-			g_signal_add_emission_hook (object_signal_id, 0,
-			                            nm_exported_object_signal_hook,
-			                            g_memdup (&query, sizeof (query)),
-			                            g_free);
-		}
-	}
-
-	g_type_class_unref (dbus_object_class);
-}
-
-/*****************************************************************************/
-
-/* "meta-marshaller" that receives the skeleton "handle-foo" signal, replaces
- * the skeleton object with an #NMExportedObject in the parameters, drops the
- * user_data parameter, and adds a "TRUE" return value (indicating to gdbus that
- * the signal was handled).
- */
-static void
-nm_exported_object_meta_marshal (GClosure *closure, GValue *return_value,
-                                 guint n_param_values, const GValue *param_values,
-                                 gpointer invocation_hint, gpointer marshal_data)
-{
-	GValue *local_param_values;
-
-	local_param_values = g_new0 (GValue, n_param_values);
-	g_value_init (&local_param_values[0], G_TYPE_POINTER);
-	g_value_set_pointer (&local_param_values[0], closure->data);
-	memcpy (local_param_values + 1, param_values + 1, (n_param_values - 1) * sizeof (GValue));
-
-	g_cclosure_marshal_generic (closure, NULL,
-	                            n_param_values, local_param_values,
-	                            invocation_hint,
-	                            ((GCClosure *)closure)->callback);
-	g_value_set_boolean (return_value, TRUE);
-
-	g_value_unset (&local_param_values[0]);
-	g_free (local_param_values);
-}
-
-static NM_CACHED_QUARK_FCN ("skeleton-data", _skeleton_data_quark)
-
-typedef struct {
-	GBinding **prop_bindings;
-	gulong *method_signals;
-} SkeletonData;
-
-GDBusInterfaceSkeleton *
-nm_exported_object_skeleton_create (GType dbus_skeleton_type,
-                                    GObjectClass *object_class,
-                                    const NMExportedObjectDBusMethodImpl *methods,
-                                    guint methods_len,
-                                    GObject *target)
-{
-	GDBusInterfaceSkeleton *interface;
-	gs_free GParamSpec **properties = NULL;
-	SkeletonData *skeleton_data;
-	guint n_properties;
-	guint i, j;
-
-	interface = G_DBUS_INTERFACE_SKELETON (g_object_new (dbus_skeleton_type, NULL));
-
-	skeleton_data = g_slice_new (SkeletonData);
-
-	/* Bind properties */
-	properties = g_object_class_list_properties (G_OBJECT_GET_CLASS (interface), &n_properties);
-	skeleton_data->prop_bindings = g_new (GBinding *, n_properties + 1);
-	for (i = 0, j = 0; i < n_properties; i++) {
-		GParamSpec *nm_property;
-		GBindingFlags flags;
-		GBinding *prop_binding;
-
-		nm_property = g_object_class_find_property (object_class, properties[i]->name);
-		if (!nm_property)
-			continue;
-
-		flags = G_BINDING_SYNC_CREATE;
-		if (   (nm_property->flags & G_PARAM_WRITABLE)
-			&& !(nm_property->flags & G_PARAM_CONSTRUCT_ONLY))
-			flags |= G_BINDING_BIDIRECTIONAL;
-		prop_binding = g_object_bind_property (target, properties[i]->name,
-		                                       interface, properties[i]->name,
-		                                       flags);
-		if (prop_binding)
-			skeleton_data->prop_bindings[j++] = prop_binding;
-	}
-	skeleton_data->prop_bindings[j++] = NULL;
-
-	/* Bind methods */
-	skeleton_data->method_signals = g_new (gulong, methods_len + 1);
-	for (i = 0, j = 0; i < methods_len; i++) {
-		const NMExportedObjectDBusMethodImpl *method = &methods[i];
-		GClosure *closure;
-		gulong method_signal;
-
-		/* ignore methods that are for a different skeleton-type. */
-		if (   method->dbus_skeleton_type
-		    && method->dbus_skeleton_type != dbus_skeleton_type)
-			continue;
-
-		closure = g_cclosure_new_swap (method->impl, target, NULL);
-		g_closure_set_meta_marshal (closure, NULL, nm_exported_object_meta_marshal);
-		method_signal = g_signal_connect_closure (interface, method->method_name, closure, FALSE);
-
-		if (method_signal != 0)
-			skeleton_data->method_signals[j++] = method_signal;
-	}
-	skeleton_data->method_signals[j++] = 0;
-
-	g_object_set_qdata ((GObject *) interface, _skeleton_data_quark (), skeleton_data);
-
-	return interface;
-}
-
-static void
-nm_exported_object_create_skeletons (NMExportedObject *self,
-                                     GType object_type)
-{
-	NMExportedObjectPrivate *priv;
-	GObjectClass *object_class;
-	NMExportedObjectClassInfo *classinfo;
-	GSList *iter;
-	const NMExportedObjectDBusMethodImpl *methods;
-	guint i, methods_len;
-	guint num_interfaces;
-	InterfaceData *interfaces;
-
-	classinfo = g_type_get_qdata (object_type, nm_exported_object_class_info_quark ());
-	if (!classinfo)
-		return;
-
-	object_class = g_type_class_peek (object_type);
-	priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
-
-	methods = classinfo->methods->len ? &g_array_index (classinfo->methods, NMExportedObjectDBusMethodImpl, 0) : NULL;
-	methods_len = classinfo->methods->len;
-
-	num_interfaces = g_slist_length (classinfo->skeleton_types);
-	g_return_if_fail (num_interfaces > 0);
-
-	interfaces = g_slice_alloc (sizeof (InterfaceData) * (num_interfaces + priv->num_interfaces));
-
-	for (i = num_interfaces, iter = classinfo->skeleton_types; iter; iter = iter->next) {
-		InterfaceData *ifdata = &interfaces[--i];
-
-		ifdata->interface = nm_exported_object_skeleton_create (GPOINTER_TO_SIZE (iter->data),
-		                                                        object_class,
-		                                                        methods,
-		                                                        methods_len,
-		                                                        (GObject *) self);
-		g_dbus_object_skeleton_add_interface ((GDBusObjectSkeleton *) self, ifdata->interface);
-
-		ifdata->property_changed_signal_id = g_signal_lookup ("properties-changed", G_OBJECT_TYPE (ifdata->interface));
-
-		ifdata->pending_notifies = g_hash_table_new_full (g_direct_hash,
-		                                                  g_direct_equal,
-		                                                  NULL,
-		                                                  (GDestroyNotify) g_variant_unref);
-	}
-	nm_assert (i == 0);
-
-	/* The list of interfaces priv->interfaces is to be sorted from parent-class to derived-class.
-	 * On the other hand, if one class defines multiple interfaces, the interfaces are sorted in
-	 * the order of calls to nm_exported_object_class_add_interface(). */
-	if (priv->num_interfaces > 0) {
-		memcpy (&interfaces[num_interfaces], priv->interfaces, sizeof (InterfaceData) * priv->num_interfaces);
-		g_slice_free1 (sizeof (InterfaceData) * priv->num_interfaces, priv->interfaces);
-	}
-
-	priv->num_interfaces = num_interfaces + priv->num_interfaces;
-	priv->interfaces = interfaces;
-}
-
-void
-nm_exported_object_skeleton_release (GDBusInterfaceSkeleton *interface)
-{
-	SkeletonData *skeleton_data;
-	guint j;
-
-	g_return_if_fail (G_IS_DBUS_INTERFACE_SKELETON (interface));
-
-	skeleton_data = g_object_steal_qdata ((GObject *) interface, _skeleton_data_quark ());
-
-	for (j = 0; skeleton_data->prop_bindings[j]; j++)
-		g_object_unref (skeleton_data->prop_bindings[j]);
-	for (j = 0; skeleton_data->method_signals[j]; j++)
-		g_signal_handler_disconnect (interface, skeleton_data->method_signals[j]);
-
-	g_free (skeleton_data->prop_bindings);
-	g_free (skeleton_data->method_signals);
-	g_slice_free (SkeletonData, skeleton_data);
-
-	g_object_unref (interface);
-}
-
-static void
-nm_exported_object_destroy_skeletons (NMExportedObject *self)
-{
-	NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
-	guint n;
-
-	g_return_if_fail (priv->num_interfaces > 0);
-	nm_assert (priv->interfaces);
-
-	n = priv->num_interfaces;
-
-	while (priv->num_interfaces > 0) {
-		InterfaceData *ifdata = &priv->interfaces[--priv->num_interfaces];
-
-		g_dbus_object_skeleton_remove_interface ((GDBusObjectSkeleton *) self, ifdata->interface);
-		nm_exported_object_skeleton_release (ifdata->interface);
-		g_hash_table_destroy (ifdata->pending_notifies);
-	}
-
-	g_slice_free1 (sizeof (InterfaceData) * n, priv->interfaces);
-	priv->interfaces = NULL;
-}
-
-static char *
-_create_export_path (NMExportedObjectClass *klass)
-{
-	const char *class_export_path, *p;
-	static GHashTable *prefix_counters;
-	guint64 *counter;
-
-	class_export_path = klass->export_path;
-
-	nm_assert (class_export_path);
-
-	p = strchr (class_export_path, '%');
-	if (p) {
-		if (G_UNLIKELY (!prefix_counters))
-			prefix_counters = g_hash_table_new (nm_str_hash, g_str_equal);
-
-		nm_assert (p[1] == 'l');
-		nm_assert (p[2] == 'l');
-		nm_assert (p[3] == 'u');
-		nm_assert (p[4] == '\0');
-
-		counter = g_hash_table_lookup (prefix_counters, class_export_path);
-		if (!counter) {
-			counter = g_slice_new0 (guint64);
-			g_hash_table_insert (prefix_counters, (char *) class_export_path, counter);
-		}
-
-		NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral")
-		return g_strdup_printf (class_export_path, (unsigned long long) (++(*counter)));
-		NM_PRAGMA_WARNING_REENABLE
-	}
-
-	return g_strdup (class_export_path);
-}
-
-/**
- * nm_exported_object_get_path:
- * @self: an #NMExportedObject
- *
- * Gets @self's D-Bus path.
- *
- * Returns: @self's D-Bus path, or %NULL if @self is not exported.
- */
-const char *
-nm_exported_object_get_path (NMExportedObject *self)
-{
-	g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), NULL);
-
-	return NM_EXPORTED_OBJECT_GET_PRIVATE (self)->path;
-}
-
-/**
- * nm_exported_object_is_exported:
- * @self: an #NMExportedObject
- *
- * Checks if @self is exported
- *
- * Returns: %TRUE if @self is exported
- */
-gboolean
-nm_exported_object_is_exported (NMExportedObject *self)
-{
-	g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), FALSE);
-
-	return NM_EXPORTED_OBJECT_GET_PRIVATE (self)->path != NULL;
-}
-
-/**
- * nm_exported_object_export:
- * @self: an #NMExportedObject
- *
- * Exports @self on all active and future D-Bus connections.
- *
- * The path to export @self on is taken from its #NMObjectClass's %export_path
- * member. If the %export_path contains "%u", then it will be replaced with a
- * monotonically increasing integer ID (with each distinct %export_path having
- * its own counter). Otherwise, %export_path will be used literally (implying
- * that @self must be a singleton).
- *
- * Returns: the path @self was exported under
- */
-const char *
-nm_exported_object_export (NMExportedObject *self)
-{
-	NMExportedObjectPrivate *priv;
-	GType type;
-
-	g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), NULL);
-	priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
-
-	g_return_val_if_fail (!priv->path, priv->path);
-	g_return_val_if_fail (!priv->bus_mgr, priv->path);
-
-#ifdef _ASSERT_NO_EARLY_EXPORT
-	nm_assert (priv->_constructed);
-#endif
-
-	priv->bus_mgr = nm_bus_manager_get ();
-	if (!priv->bus_mgr)
-		g_return_val_if_reached (NULL);
-	g_object_add_weak_pointer ((GObject *) priv->bus_mgr, (gpointer *) &priv->bus_mgr);
-
-	type = G_OBJECT_TYPE (self);
-	while (type != NM_TYPE_EXPORTED_OBJECT) {
-		nm_exported_object_create_skeletons (self, type);
-		type = g_type_parent (type);
-	}
-
-	priv->path = _create_export_path (NM_EXPORTED_OBJECT_GET_CLASS (self));
-
-	_LOGT ("export: \"%s\"", priv->path);
-	g_dbus_object_skeleton_set_object_path (G_DBUS_OBJECT_SKELETON (self), priv->path);
-
-	/* Important: priv->path and priv->interfaces must not change while
-	 * the object is registered. */
-
-	nm_bus_manager_register_object (priv->bus_mgr, (GDBusObjectSkeleton *) self);
-
-	_notify (self, PROP_PATH);
-
-	return priv->path;
-}
-
-/**
- * nm_exported_object_unexport:
- * @self: an #NMExportedObject
- *
- * Unexports @self on all active D-Bus connections (and prevents it from being
- * auto-exported on future connections).
- */
-void
-nm_exported_object_unexport (NMExportedObject *self)
-{
-	NMExportedObjectPrivate *priv;
-
-	g_return_if_fail (NM_IS_EXPORTED_OBJECT (self));
-	priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
-
-	g_return_if_fail (priv->path);
-
-	/* Important: priv->path and priv->interfaces must not change while
-	 * the object is registered. */
-
-	_LOGT ("unexport: \"%s\"", priv->path);
-
-	if (priv->bus_mgr) {
-		nm_bus_manager_unregister_object (priv->bus_mgr, (GDBusObjectSkeleton *) self);
-		g_object_remove_weak_pointer ((GObject *) priv->bus_mgr, (gpointer *) &priv->bus_mgr);
-		priv->bus_mgr = NULL;
-	}
-
-	nm_exported_object_destroy_skeletons (self);
-
-	g_dbus_object_skeleton_set_object_path ((GDBusObjectSkeleton *) self, NULL);
-
-	g_clear_pointer (&priv->path, g_free);
-
-	nm_clear_g_source (&priv->notify_idle_id);
-
-	_notify (self, PROP_PATH);
-}
-
-/*****************************************************************************/
-
-void
-_nm_exported_object_clear_and_unexport (NMExportedObject **location)
-{
-	NMExportedObject *self;
-	NMExportedObjectPrivate *priv;
-
-	if (!location || !*location)
-		return;
-
-	self = *location;
-	*location = NULL;
-
-	g_return_if_fail (NM_IS_EXPORTED_OBJECT (self));
-
-	priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
-
-	if (priv->path)
-		nm_exported_object_unexport (self);
-
-	g_object_unref (self);
-}
-
-/*****************************************************************************/
-
-GDBusInterfaceSkeleton *
-nm_exported_object_get_interface_by_type (NMExportedObject *self, GType interface_type)
-{
-	NMExportedObjectPrivate *priv;
-	guint i;
-
-	g_return_val_if_fail (NM_IS_EXPORTED_OBJECT (self), NULL);
-
-	priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
-
-	g_return_val_if_fail (priv->path, NULL);
-	g_return_val_if_fail (priv->num_interfaces > 0, NULL);
-
-	nm_assert (priv->interfaces);
-
-	for (i = 0; i < priv->num_interfaces; i++) {
-		InterfaceData *ifdata = &priv->interfaces[i];
-
-		if (G_TYPE_CHECK_INSTANCE_TYPE (ifdata->interface, interface_type))
-			return ifdata->interface;
-	}
-	return NULL;
-}
-
-/*****************************************************************************/
-
-void
-nm_exported_object_class_set_quitting (void)
-{
-	quitting = TRUE;
-}
-
-/*****************************************************************************/
-
-typedef struct {
-	const char *property_name;
-	GVariant *variant;
-} PendingNotifiesItem;
-
-static int
-_sort_pending_notifies (gconstpointer a, gconstpointer b, gpointer       user_data)
-{
-	return strcmp (((const PendingNotifiesItem *) a)->property_name,
-	               ((const PendingNotifiesItem *) b)->property_name);
-}
-
-static gboolean
-idle_emit_properties_changed (gpointer self)
-{
-	NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (NM_EXPORTED_OBJECT (self));
-	guint k;
-
-	priv->notify_idle_id = 0;
-
-	for (k = 0; k < priv->num_interfaces; k++) {
-		InterfaceData *ifdata = &priv->interfaces[k];
-		gs_unref_variant GVariant *variant = NULL;
-		PendingNotifiesItem *values;
-		GVariantBuilder notifies;
-		GHashTableIter hash_iter;
-		guint i, n;
-
-		n = g_hash_table_size (ifdata->pending_notifies);
-		if (n == 0)
-			continue;
-
-		nm_assert (ifdata->property_changed_signal_id);
-
-		/* We use here alloca in a loop, something that is usually avoided.
-		 * But the number of interfaces "priv->num_interfaces" is small (determined by
-		 * the depth of the type inheritance) and the number of possible pending_notifies
-		 * "n" is small (determined by the number of GObject properties). */
-		values = g_alloca (sizeof (values[0]) * n);
-
-		i = 0;
-		g_hash_table_iter_init (&hash_iter, ifdata->pending_notifies);
-		while (g_hash_table_iter_next (&hash_iter, (gpointer) &values[i].property_name, (gpointer) &values[i].variant))
-			i++;
-		nm_assert (i == n);
-
-		g_qsort_with_data (values, n, sizeof (values[0]), _sort_pending_notifies, NULL);
-
-		g_variant_builder_init (&notifies, G_VARIANT_TYPE_VARDICT);
-		for (i = 0; i < n; i++)
-			g_variant_builder_add (&notifies, "{sv}", values[i].property_name, values[i].variant);
-		variant = g_variant_ref_sink (g_variant_builder_end (&notifies));
-
-
-		if (_LOG2D_ENABLED ()) {
-			gs_free char *notification = g_variant_print (variant, TRUE);
-
-			_LOG2D ("type %s, iface %s: %s",
-			        G_OBJECT_TYPE_NAME (self), G_OBJECT_TYPE_NAME (ifdata->interface),
-			        notification);
-		}
-
-		g_signal_emit (ifdata->interface, ifdata->property_changed_signal_id, 0, variant);
-
-		g_hash_table_remove_all (ifdata->pending_notifies);
-	}
-
-	return G_SOURCE_REMOVE;
-}
-
-static void
-nm_exported_object_notify (GObject *object, GParamSpec *pspec)
-{
-	NMExportedObject *self = (NMExportedObject *) object;
-	NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
-	NMExportedObjectClassInfo *classinfo;
-	GType type;
-	const char *dbus_property_name = NULL;
-	GValue value = G_VALUE_INIT;
-	GVariant *value_variant;
-	InterfaceData *ifdata = NULL;
-	const GVariantType *vtype;
-	guint i, j;
-
-	/* Hook to emit deprecated "PropertiesChanged" signal on NetworkManager interfaces.
-	 * This is to preserve deprecated D-Bus API, nowadays we use instead
-	 * the "PropertiesChanged" signal of "org.freedesktop.DBus.Properties". */
-
-	if (priv->num_interfaces == 0)
-		return;
-
-	for (type = G_OBJECT_TYPE (self); type; type = g_type_parent (type)) {
-		classinfo = g_type_get_qdata (type, nm_exported_object_class_info_quark ());
-		if (!classinfo)
-			continue;
-
-		dbus_property_name = g_hash_table_lookup (classinfo->properties, pspec->name);
-		if (dbus_property_name)
-			break;
-	}
-	if (!dbus_property_name) {
-		_LOG2T ("ignoring notification for prop %s on type %s",
-		        pspec->name, G_OBJECT_TYPE_NAME (self));
-		return;
-	}
-
-	for (i = 0; i < priv->num_interfaces; i++) {
-		GDBusInterfaceInfo *iinfo;
-
-		ifdata = &priv->interfaces[i];
-		iinfo = g_dbus_interface_skeleton_get_info (ifdata->interface);
-		for (j = 0; iinfo->properties[j]; j++) {
-			if (nm_streq (iinfo->properties[j]->name, dbus_property_name)) {
-				vtype = G_VARIANT_TYPE (iinfo->properties[j]->signature);
-				goto vtype_found;
-			}
-		}
-	}
-	g_return_if_reached ();
-
-vtype_found:
-	g_value_init (&value, pspec->value_type);
-	g_object_get_property ((GObject *) self, pspec->name, &value);
-	value_variant = g_dbus_gvalue_to_gvariant (&value, vtype);
-	g_value_unset (&value);
-
-	if (   (   NM_IS_DEVICE (self)
-	        && !NMDBUS_IS_DEVICE_STATISTICS_SKELETON (ifdata->interface))
-	    || NM_IS_ACTIVE_CONNECTION (self)) {
-		/* This PropertiesChanged signal is nodaways deprecated in favor
-		 * of "org.freedesktop.DBus.Properties"'s PropertiesChanged signal.
-		 * This function solely exists to raise the NM version of PropertiesChanged.
-		 *
-		 * With types exported on D-Bus that are implemented as derived
-		 * types in glib (NMDevice and NMActiveConnection), multiple types
-		 * in the inheritance tree define a "PropertiesChanged" signal.
-		 *
-		 * In 1.0.0 and earlier, the signal was emitted once for every interface
-		 * that had a "PropertiesChanged" signal. For example:
-		 *   - NMDeviceEthernet.HwAddress was emitted on "fdo.NM.Device.Ethernet"
-		 *     and "fdo.NM.Device.Veth" (if the device was of type NMDeviceVeth).
-		 *   - NMVpnConnection.VpnState was emitted on "fdo.NM.Connecion.Active"
-		 *     and "fdo.NM.VPN.Connection".
-		 *
-		 * NMDevice is special in that it didn't have a "PropertiesChanged" signal.
-		 * Thus, a change to "NMDevice.StateReason" would be emitted on "fdo.NM.Device.Ethernet"
-		 * and also on "fdo.NM.Device.Veth" (in case of a device of type NMDeviceVeth).
-		 *
-		 * The releases of 1.2.0 and 1.4.0 failed to realize above and broke this behavior.
-		 * This special handling here is to bring back the 1.0.0 behavior.
-		 *
-		 * The Device.Statistics signal is special, because it was only added with 1.4.0
-		 * and didn't have above behavior. So let's save the overhead of emitting multiple
-		 * deprecated signals for wrong interfaces. */
-		for (i = 0, j = 0; i < priv->num_interfaces; i++) {
-			ifdata = &priv->interfaces[i];
-			if (   ifdata->property_changed_signal_id
-			    && !NMDBUS_IS_DEVICE_STATISTICS_SKELETON (ifdata->interface)) {
-				j++;
-				g_hash_table_insert (ifdata->pending_notifies,
-				                     (gpointer) dbus_property_name,
-				                     g_variant_ref (value_variant));
-			}
-		}
-		nm_assert (j > 0);
-		g_variant_unref (value_variant);
-	} else if (ifdata->property_changed_signal_id) {
-		/* @dbus_property_name is inside classinfo and never freed, thus we don't clone it.
-		 * Also, we do a pointer, not string comparison. */
-		g_hash_table_insert (ifdata->pending_notifies,
-		                     (gpointer) dbus_property_name,
-		                     value_variant);
-	} else
-		g_variant_unref (value_variant);
-
-	if (!priv->notify_idle_id)
-		priv->notify_idle_id = g_idle_add (idle_emit_properties_changed, self);
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
-{
-	NMExportedObject *self = NM_EXPORTED_OBJECT (object);
-	NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
-
-	switch (prop_id) {
-	case PROP_PATH:
-		g_value_set_string (value, priv->path);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-nm_exported_object_init (NMExportedObject *self)
-{
-	NMExportedObjectPrivate *priv;
-
-	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_EXPORTED_OBJECT, NMExportedObjectPrivate);
-	self->_priv = priv;
-}
-
-static void
-constructed (GObject *object)
-{
-	NMExportedObjectClass *klass;
-
-	G_OBJECT_CLASS (nm_exported_object_parent_class)->constructed (object);
-
-#ifdef _ASSERT_NO_EARLY_EXPORT
-	NM_EXPORTED_OBJECT_GET_PRIVATE (NM_EXPORTED_OBJECT (object))->_constructed = TRUE;
-#endif
-
-	klass = NM_EXPORTED_OBJECT_GET_CLASS (object);
-
-	if (klass->export_on_construction)
-		nm_exported_object_export ((NMExportedObject *) object);
-}
-
-static void
-dispose (GObject *object)
-{
-	NMExportedObject *self = NM_EXPORTED_OBJECT (object);
-	NMExportedObjectPrivate *priv = NM_EXPORTED_OBJECT_GET_PRIVATE (self);
-
-	/* Objects should have already been unexported by their owner, unless
-	 * we are quitting, where many objects stick around until exit.
-	 */
-	if (!quitting) {
-		if (priv->path) {
-			g_warn_if_reached ();
-			nm_exported_object_unexport (self);
-		}
-	} else if (nm_clear_g_free (&priv->path))
-		_notify (self, PROP_PATH);
-
-	nm_clear_g_source (&priv->notify_idle_id);
-
-	G_OBJECT_CLASS (nm_exported_object_parent_class)->dispose (object);
-}
-
-static void
-nm_exported_object_class_init (NMExportedObjectClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	g_type_class_add_private (object_class, sizeof (NMExportedObjectPrivate));
-
-	object_class->constructed = constructed;
-	object_class->notify = nm_exported_object_notify;
-	object_class->dispose = dispose;
-	object_class->get_property = get_property;
-
-	obj_properties[PROP_PATH] =
-	    g_param_spec_string (NM_EXPORTED_OBJECT_PATH, "", "",
-	                         NULL,
-	                         G_PARAM_READABLE |
-	                         G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-}
diff --git a/src/nm-exported-object.h b/src/nm-exported-object.h
deleted file mode 100644
index 47559a20..00000000
--- a/src/nm-exported-object.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2014 Red Hat, Inc.
- */
-
-#ifndef NM_EXPORTED_OBJECT_H
-#define NM_EXPORTED_OBJECT_H
-
-/*****************************************************************************/
-
-#define NM_EXPORT_PATH_NUMBERED(basepath) ""basepath"/%llu"
-
-char *nm_exported_object_skeletonify_method_name (const char *dbus_method_name);
-
-typedef struct {
-	GType dbus_skeleton_type;
-	char *method_name;
-	GCallback impl;
-} NMExportedObjectDBusMethodImpl;
-
-GDBusInterfaceSkeleton *nm_exported_object_skeleton_create (GType dbus_skeleton_type,
-                                                            GObjectClass *object_class,
-                                                            const NMExportedObjectDBusMethodImpl *methods,
-                                                            guint methods_len,
-                                                            GObject *target);
-void nm_exported_object_skeleton_release (GDBusInterfaceSkeleton *interface);
-
-/*****************************************************************************/
-
-#define NM_TYPE_EXPORTED_OBJECT            (nm_exported_object_get_type ())
-#define NM_EXPORTED_OBJECT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_EXPORTED_OBJECT, NMExportedObject))
-#define NM_EXPORTED_OBJECT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_EXPORTED_OBJECT, NMExportedObjectClass))
-#define NM_IS_EXPORTED_OBJECT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_EXPORTED_OBJECT))
-#define NM_IS_EXPORTED_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_EXPORTED_OBJECT))
-#define NM_EXPORTED_OBJECT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_EXPORTED_OBJECT, NMExportedObjectClass))
-
-#define NM_EXPORTED_OBJECT_PATH "path"
-
-struct _NMExportedObjectPrivate;
-
-struct _NMExportedObject {
-	GDBusObjectSkeleton parent;
-	struct _NMExportedObjectPrivate *_priv;
-};
-
-typedef struct {
-	GDBusObjectSkeletonClass parent;
-
-	const char *export_path;
-	char export_on_construction;
-} NMExportedObjectClass;
-
-GType nm_exported_object_get_type (void);
-
-void nm_exported_object_class_set_quitting  (void);
-
-void nm_exported_object_class_add_interface (NMExportedObjectClass *object_class,
-                                             GType                  dbus_skeleton_type,
-                                             ...) G_GNUC_NULL_TERMINATED;
-
-const char *nm_exported_object_export      (NMExportedObject *self);
-const char *nm_exported_object_get_path    (NMExportedObject *self);
-gboolean    nm_exported_object_is_exported (NMExportedObject *self);
-void        nm_exported_object_unexport    (NMExportedObject *self);
-GDBusInterfaceSkeleton *nm_exported_object_get_interface_by_type (NMExportedObject *self, GType interface_type);
-
-void        _nm_exported_object_clear_and_unexport (NMExportedObject **location);
-#define nm_exported_object_clear_and_unexport(location) _nm_exported_object_clear_and_unexport ((NMExportedObject **) (location))
-
-#endif /* NM_EXPORTED_OBJECT_H */
diff --git a/src/nm-firewall-manager.c b/src/nm-firewall-manager.c
index 216d5fc8..134a46f7 100644
--- a/src/nm-firewall-manager.c
+++ b/src/nm-firewall-manager.c
@@ -25,7 +25,7 @@
 #include <string.h>
 
 #include "NetworkManagerUtils.h"
-#include "nm-utils/c-list.h"
+#include "c-list/src/c-list.h"
 
 /*****************************************************************************/
 
diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c
index 1493ef19..601c72ac 100644
--- a/src/nm-iface-helper.c
+++ b/src/nm-iface-helper.c
@@ -33,6 +33,8 @@
 #include <signal.h>
 #include <linux/rtnetlink.h>
 
+#include "nm-utils/nm-c-list.h"
+
 #include "main-utils.h"
 #include "NetworkManagerUtils.h"
 #include "platform/nm-linux-platform.h"
@@ -55,6 +57,9 @@
 static struct {
 	GMainLoop *main_loop;
 	int ifindex;
+
+	guint dad_failed_id;
+	CList dad_failed_lst_head;
 } gl/*obal*/ = {
 	.ifindex = -1,
 };
@@ -120,7 +125,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 		g_assert (nm_ip4_config_get_ifindex (ip4_config) == gl.ifindex);
 
 		existing = nm_ip4_config_capture (nm_platform_get_multi_idx (NM_PLATFORM_GET),
-		                                  NM_PLATFORM_GET, gl.ifindex, FALSE);
+		                                  NM_PLATFORM_GET, gl.ifindex);
 		if (last_config)
 			nm_ip4_config_subtract (existing, last_config, 0);
 
@@ -166,7 +171,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 	NMIP6Config *existing;
 
 	existing = nm_ip6_config_capture (nm_platform_get_multi_idx (NM_PLATFORM_GET),
-	                                  NM_PLATFORM_GET, gl.ifindex, FALSE, global_opt.tempaddr);
+	                                  NM_PLATFORM_GET, gl.ifindex, global_opt.tempaddr);
 	if (ndisc_config)
 		nm_ip6_config_subtract (existing, ndisc_config, 0);
 	else {
@@ -316,19 +321,58 @@ do_early_setup (int *argc, char **argv[])
 	return TRUE;
 }
 
+typedef struct {
+	NMPlatform *platform;
+	NMNDisc *ndisc;
+} DadFailedHandleData;
+
+static gboolean
+dad_failed_handle_idle (gpointer user_data)
+{
+	DadFailedHandleData *data = user_data;
+	NMCListElem *elem;
+
+	while ((elem = c_list_first_entry (&gl.dad_failed_lst_head, NMCListElem, lst))) {
+		nm_auto_nmpobj const NMPObject *obj = elem->data;
+
+		nm_c_list_elem_free (elem);
+
+		if (nm_ndisc_dad_addr_is_fail_candidate (data->platform, obj)) {
+			nm_ndisc_dad_failed (data->ndisc,
+			                     &NMP_OBJECT_CAST_IP6_ADDRESS (obj)->address);
+		}
+	}
+
+	gl.dad_failed_id = 0;
+	return G_SOURCE_REMOVE;
+}
+
 static void
 ip6_address_changed (NMPlatform *platform,
                      int obj_type_i,
                      int iface,
-                     NMPlatformIP6Address *addr,
+                     const NMPlatformIP6Address *addr,
                      int change_type_i,
                      NMNDisc *ndisc)
 {
 	const NMPlatformSignalChangeType change_type = change_type_i;
-
-	if (   (change_type == NM_PLATFORM_SIGNAL_CHANGED && addr->n_ifa_flags & IFA_F_DADFAILED)
-	    || (change_type == NM_PLATFORM_SIGNAL_REMOVED && addr->n_ifa_flags & IFA_F_TENTATIVE))
-		nm_ndisc_dad_failed (ndisc, &addr->address);
+	DadFailedHandleData *data;
+
+	if (!nm_ndisc_dad_addr_is_fail_candidate_event (change_type, addr))
+		return;
+
+	c_list_link_tail (&gl.dad_failed_lst_head,
+	                  &nm_c_list_elem_new_stale ((gpointer) nmp_object_ref (NMP_OBJECT_UP_CAST (addr)))->lst);
+	if (gl.dad_failed_id)
+		return;
+
+	data = g_slice_new (DadFailedHandleData);
+	data->platform = platform;
+	data->ndisc = ndisc;
+	gl.dad_failed_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+	                                    dad_failed_handle_idle,
+	                                    data,
+	                                    nm_g_slice_free_fcn (DadFailedHandleData));
 }
 
 int
@@ -340,14 +384,13 @@ main (int argc, char *argv[])
 	gs_free char *pidfile = NULL;
 	gs_unref_object NMDhcpClient *dhcp4_client = NULL;
 	gs_unref_object NMNDisc *ndisc = NULL;
-	GByteArray *hwaddr = NULL;
-	size_t hwaddr_len = 0;
-	gconstpointer tmp;
+	gs_unref_bytes GBytes *hwaddr = NULL;
+	gs_unref_bytes GBytes *client_id = NULL;
 	gs_free NMUtilsIPv6IfaceId *iid = NULL;
 	guint sd_id;
 	char sysctl_path_buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 
-	nm_g_type_init ();
+	c_list_init (&gl.dad_failed_lst_head);
 
 	setpgid (getpid (), getpid ());
 
@@ -431,11 +474,7 @@ main (int argc, char *argv[])
 	/* Set up platform interaction layer */
 	nm_linux_platform_setup ();
 
-	tmp = nm_platform_link_get_address (NM_PLATFORM_GET, gl.ifindex, &hwaddr_len);
-	if (tmp) {
-		hwaddr = g_byte_array_sized_new (hwaddr_len);
-		g_byte_array_append (hwaddr, tmp, hwaddr_len);
-	}
+	hwaddr = nm_platform_link_get_address_as_bytes (NM_PLATFORM_GET, gl.ifindex);
 
 	if (global_opt.iid_str) {
 		GBytes *bytes;
@@ -449,6 +488,16 @@ main (int argc, char *argv[])
 		iid = g_bytes_unref_to_data (bytes, &ignored);
 	}
 
+	if (global_opt.dhcp4_clientid) {
+		/* this string is just a plain hex-string. Unlike ipv4.dhcp-client-id, which
+		 * is parsed via nm_dhcp_utils_client_id_string_to_bytes(). */
+		client_id = nm_utils_hexstr2bin (global_opt.dhcp4_clientid);
+		if (!client_id || g_bytes_get_size (client_id) < 2) {
+			fprintf (stderr, _("(%s): Invalid DHCP client-id %s\n"), global_opt.ifname, global_opt.dhcp4_clientid);
+			return 1;
+		}
+	}
+
 	if (global_opt.dhcp4_address) {
 		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, sysctl_path_buf, global_opt.ifname, "promote_secondaries")), "1");
 
@@ -463,7 +512,7 @@ main (int argc, char *argv[])
 		                                          !!global_opt.dhcp4_hostname,
 		                                          global_opt.dhcp4_hostname,
 		                                          global_opt.dhcp4_fqdn,
-		                                          global_opt.dhcp4_clientid,
+		                                          client_id,
 		                                          NM_DHCP_TIMEOUT_DEFAULT,
 		                                          NULL,
 		                                          global_opt.dhcp4_address);
@@ -523,7 +572,8 @@ main (int argc, char *argv[])
 
 	g_main_loop_run (gl.main_loop);
 
-	g_clear_pointer (&hwaddr, g_byte_array_unref);
+	nm_clear_g_source (&gl.dad_failed_id);
+	nm_c_list_elem_free_all (&gl.dad_failed_lst_head, (GDestroyNotify) nmp_object_unref);
 
 	if (pidfile && wrote_pidfile)
 		unlink (pidfile);
@@ -547,7 +597,7 @@ const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
 #include "nm-config.h"
 #include "devices/nm-device.h"
 #include "nm-active-connection.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
 
 void
 nm_main_config_reload (int signal)
@@ -579,21 +629,34 @@ nm_config_get_configure_and_quit (NMConfig *config)
 	return TRUE;
 }
 
-NMBusManager *
-nm_bus_manager_get (void)
+NMDBusManager *
+nm_dbus_manager_get (void)
+{
+	return NULL;
+}
+
+void
+_nm_dbus_manager_obj_export (NMDBusObject *obj)
+{
+}
+
+void
+_nm_dbus_manager_obj_unexport (NMDBusObject *obj)
 {
-	return GUINT_TO_POINTER (1);
 }
 
 void
-nm_bus_manager_register_object (NMBusManager *bus_manager,
-                                GDBusObjectSkeleton *object)
+_nm_dbus_manager_obj_notify (NMDBusObject *obj,
+                             guint n_pspecs,
+                             const GParamSpec *const*pspecs)
 {
 }
 
 void
-nm_bus_manager_unregister_object (NMBusManager *bus_manager,
-                                  GDBusObjectSkeleton *object)
+_nm_dbus_manager_obj_emit_signal (NMDBusObject *obj,
+                                  const NMDBusInterfaceInfoExtended *interface_info,
+                                  const GDBusSignalInfo *signal_info,
+                                  GVariant *args)
 {
 }
 
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 15157739..0a9591d2 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -36,8 +36,7 @@
 #include "platform/nm-platform-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
-
-#include "introspection/org.freedesktop.NetworkManager.IP4Config.h"
+#include "nm-dbus-object.h"
 
 /*****************************************************************************/
 
@@ -293,6 +292,7 @@ typedef struct {
 	int ifindex;
 	NMIPConfigSource mtu_source;
 	gint dns_priority;
+	NMSettingConnectionMdns mdns;
 	GArray *nameservers;
 	GPtrArray *domains;
 	GPtrArray *searches;
@@ -317,15 +317,15 @@ typedef struct {
 } NMIP4ConfigPrivate;
 
 struct _NMIP4Config {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	NMIP4ConfigPrivate _priv;
 };
 
 struct _NMIP4ConfigClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 };
 
-G_DEFINE_TYPE (NMIP4Config, nm_ip4_config, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMIP4Config, nm_ip4_config, NM_TYPE_DBUS_OBJECT)
 
 #define NM_IP4_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMIP4Config, NM_IS_IP4_CONFIG)
 
@@ -582,14 +582,24 @@ sort_captured_addresses (const CList *lst_a, const CList *lst_b, gconstpointer u
 }
 
 NMIP4Config *
-nm_ip4_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex, gboolean capture_resolv_conf)
+nm_ip4_config_clone (const NMIP4Config *self)
+{
+	NMIP4Config *copy;
+
+	copy = nm_ip4_config_new (nm_ip4_config_get_multi_idx (self), -1);
+	nm_ip4_config_replace (copy, self, NULL);
+
+	return copy;
+}
+
+NMIP4Config *
+nm_ip4_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex)
 {
 	NMIP4Config *self;
 	NMIP4ConfigPrivate *priv;
 	const NMDedupMultiHeadEntry *head_entry;
 	NMDedupMultiIter iter;
 	const NMPObject *plobj = NULL;
-	gboolean has_addresses = FALSE;
 
 	nm_assert (ifindex > 0);
 
@@ -621,7 +631,6 @@ nm_ip4_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int i
 		nm_dedup_multi_head_entry_sort (head_entry,
 		                                sort_captured_addresses,
 		                                NULL);
-		has_addresses = TRUE;
 		_notify_addresses (self);
 	}
 
@@ -633,23 +642,6 @@ nm_ip4_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 the interface has the default route, and has IPv4 addresses, capture
-	 * nameservers from /etc/resolv.conf.
-	 */
-	if (   has_addresses
-	    && priv->best_default_route
-	    && capture_resolv_conf) {
-		gs_free char *rc_contents = NULL;
-
-		if (g_file_get_contents (_PATH_RESCONF, &rc_contents, NULL, NULL)) {
-			if (nm_utils_resolve_conf_parse (AF_INET,
-			                                 rc_contents,
-			                                 priv->nameservers,
-			                                 priv->dns_options))
-				_notify (self, PROP_NAMESERVERS);
-		}
-	}
-
 	return self;
 }
 
@@ -659,7 +651,6 @@ nm_ip4_config_add_dependent_routes (NMIP4Config *self,
                                     guint32 route_metric,
                                     GPtrArray **out_ip4_dev_route_blacklist)
 {
-	const NMIP4ConfigPrivate *priv;
 	GPtrArray *ip4_dev_route_blacklist = NULL;
 	const NMPlatformIP4Address *my_addr;
 	const NMPlatformIP4Route *my_route;
@@ -668,8 +659,6 @@ nm_ip4_config_add_dependent_routes (NMIP4Config *self,
 
 	g_return_if_fail (NM_IS_IP4_CONFIG (self));
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
-
 	ifindex = nm_ip4_config_get_ifindex (self);
 	g_return_if_fail (ifindex > 0);
 
@@ -893,10 +882,10 @@ _nm_ip_config_merge_route_attributes (int addr_family,
 void
 nm_ip4_config_merge_setting (NMIP4Config *self,
                              NMSettingIPConfig *setting,
+                             NMSettingConnectionMdns mdns,
                              guint32 route_table,
                              guint32 route_metric)
 {
-	NMIP4ConfigPrivate *priv;
 	guint naddresses, nroutes, nnameservers, nsearches;
 	int i, priority;
 	const char *gateway_str;
@@ -907,8 +896,6 @@ nm_ip4_config_merge_setting (NMIP4Config *self,
 
 	g_return_if_fail (NM_IS_SETTING_IP4_CONFIG (setting));
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
-
 	g_object_freeze_notify (G_OBJECT (self));
 
 	naddresses = nm_setting_ip_config_get_num_addresses (setting);
@@ -946,7 +933,7 @@ nm_ip4_config_merge_setting (NMIP4Config *self,
 		address.preferred = NM_PLATFORM_LIFETIME_PERMANENT;
 		address.addr_source = NM_IP_CONFIG_SOURCE_USER;
 
-		label = nm_ip_address_get_attribute (s_addr, "label");
+		label = nm_ip_address_get_attribute (s_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL);
 		if (label)
 			g_strlcpy (address.label, g_variant_get_string (label, NULL), sizeof (address.label));
 
@@ -1012,6 +999,8 @@ nm_ip4_config_merge_setting (NMIP4Config *self,
 	if (priority)
 		nm_ip4_config_set_dns_priority (self, priority);
 
+	nm_ip4_config_mdns_set (self, mdns);
+
 	g_object_thaw_notify (G_OBJECT (self));
 }
 
@@ -1058,7 +1047,7 @@ nm_ip4_config_create_setting (const NMIP4Config *self)
 
 		s_addr = nm_ip_address_new_binary (AF_INET, &address->address, address->plen, NULL);
 		if (*address->label)
-			nm_ip_address_set_attribute (s_addr, "label", g_variant_new_string (address->label));
+			nm_ip_address_set_attribute (s_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL, g_variant_new_string (address->label));
 
 		nm_setting_ip_config_add_address (s_ip4, s_addr);
 		nm_ip_address_unref (s_addr);
@@ -1227,6 +1216,11 @@ nm_ip4_config_merge (NMIP4Config *dst,
 	if (nm_ip4_config_get_dns_priority (src))
 		nm_ip4_config_set_dns_priority (dst, nm_ip4_config_get_dns_priority (src));
 
+	/* mdns */
+	nm_ip4_config_mdns_set (dst,
+	                        NM_MAX (nm_ip4_config_mdns_get (src),
+	                                nm_ip4_config_mdns_get (dst)));
+
 	g_object_thaw_notify (G_OBJECT (dst));
 }
 
@@ -1463,13 +1457,18 @@ nm_ip4_config_subtract (NMIP4Config *dst,
 	if (nm_ip4_config_get_dns_priority (src) == nm_ip4_config_get_dns_priority (dst))
 		nm_ip4_config_set_dns_priority (dst, 0);
 
+	/* mdns */
+	if (nm_ip4_config_mdns_get (src) == nm_ip4_config_mdns_get (dst))
+		nm_ip4_config_mdns_set (dst, NM_SETTING_CONNECTION_MDNS_DEFAULT);
+
 	g_object_thaw_notify (G_OBJECT (dst));
 }
 
-void
-nm_ip4_config_intersect (NMIP4Config *dst,
-                         const NMIP4Config *src,
-                         guint32 default_route_metric_penalty)
+static gboolean
+_nm_ip4_config_intersect_helper (NMIP4Config *dst,
+                                 const NMIP4Config *src,
+                                 guint32 default_route_metric_penalty,
+                                 gboolean update_dst)
 {
 	NMIP4ConfigPrivate *dst_priv;
 	const NMIP4ConfigPrivate *src_priv;
@@ -1477,15 +1476,16 @@ nm_ip4_config_intersect (NMIP4Config *dst,
 	const NMPlatformIP4Address *a;
 	const NMPlatformIP4Route *r;
 	const NMPObject *new_best_default_route;
-	gboolean changed;
+	gboolean changed, result = FALSE;
 
-	g_return_if_fail (src);
-	g_return_if_fail (dst);
+	g_return_val_if_fail (src, FALSE);
+	g_return_val_if_fail (dst, FALSE);
 
 	dst_priv = NM_IP4_CONFIG_GET_PRIVATE (dst);
 	src_priv = NM_IP4_CONFIG_GET_PRIVATE (src);
 
-	g_object_freeze_notify (G_OBJECT (dst));
+	if (update_dst)
+		g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
 	changed = FALSE;
@@ -1495,13 +1495,18 @@ nm_ip4_config_intersect (NMIP4Config *dst,
 		                                     NMP_OBJECT_UP_CAST (a)))
 			continue;
 
+		if (!update_dst)
+			return TRUE;
+
 		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
 		                                       ipconf_iter.current) != 1)
 			nm_assert_not_reached ();
 		changed = TRUE;
 	}
-	if (changed)
+	if (changed) {
 		_notify_addresses (dst);
+		result = TRUE;
+	}
 
 	/* ignore nameservers */
 
@@ -1533,6 +1538,9 @@ nm_ip4_config_intersect (NMIP4Config *dst,
 			continue;
 		}
 
+		if (!update_dst)
+			return TRUE;
+
 		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
 		                                       ipconf_iter.current) != 1)
 			nm_assert_not_reached ();
@@ -1542,18 +1550,71 @@ nm_ip4_config_intersect (NMIP4Config *dst,
 		nm_assert (changed);
 		_notify (dst, PROP_GATEWAY);
 	}
-	if (changed)
+
+	if (changed) {
 		_notify_routes (dst);
+		result = TRUE;
+	}
 
 	/* ignore domains */
 	/* ignore dns searches */
 	/* ignore dns options */
 	/* ignore NIS */
 	/* ignore WINS */
+	/* ignore mdns */
 
-	g_object_thaw_notify (G_OBJECT (dst));
+	if (update_dst)
+		g_object_thaw_notify (G_OBJECT (dst));
+	return result;
 }
 
+/**
+ * nm_ip4_config_intersect:
+ * @dst: a configuration to be updated
+ * @src: another configuration
+ * @default_route_metric_penalty: the default route metric penalty
+ *
+ * Computes the intersection between @src and @dst and updates @dst in place
+ * with the result.
+ */
+void
+nm_ip4_config_intersect (NMIP4Config *dst,
+                         const NMIP4Config *src,
+                         guint32 default_route_metric_penalty)
+{
+	_nm_ip4_config_intersect_helper (dst, src, default_route_metric_penalty, TRUE);
+}
+
+/**
+ * nm_ip4_config_intersect_alloc:
+ * @a: a configuration
+ * @b: another configuration
+ * @default_route_metric_penalty: the default route metric penalty
+ *
+ * Computes the intersection between @a and @b and returns the result in a newly
+ * allocated configuration.  As a special case, if @a and @b are identical (with
+ * respect to the only properties considered - addresses and routes) the
+ * functions returns NULL so that one of existing configuration can be reused
+ * without allocation.
+ *
+ * Returns: the intersection between @a and @b, or %NULL if the result is equal
+ * to @a and @b.
+ */
+NMIP4Config *
+nm_ip4_config_intersect_alloc (const NMIP4Config *a,
+                               const NMIP4Config *b,
+                               guint32 default_route_metric_penalty)
+{
+	NMIP4Config *a_copy;
+
+	if (_nm_ip4_config_intersect_helper ((NMIP4Config *) a, b,
+	                                     default_route_metric_penalty, FALSE)) {
+		a_copy = nm_ip4_config_clone (a);
+		_nm_ip4_config_intersect_helper (a_copy, b, default_route_metric_penalty, TRUE);
+		return a_copy;
+	} else
+		return NULL;
+}
 
 /**
  * nm_ip4_config_replace:
@@ -1779,6 +1840,8 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 		has_relevant_changes = TRUE;
 	}
 
+	dst_priv->mdns = src_priv->mdns;
+
 	/* DNS priority */
 	if (src_priv->dns_priority != dst_priv->dns_priority) {
 		nm_ip4_config_set_dns_priority (dst, src_priv->dns_priority);
@@ -1871,7 +1934,7 @@ nm_ip4_config_dump (const NMIP4Config *self, const char *detail)
 		return;
 	}
 
-	str = nm_exported_object_get_path (NM_EXPORTED_OBJECT (self));
+	str = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
 	if (str)
 		g_message ("   path: %s", str);
 
@@ -2456,6 +2519,21 @@ nm_ip4_config_get_dns_option (const NMIP4Config *self, guint i)
 
 /*****************************************************************************/
 
+NMSettingConnectionMdns
+nm_ip4_config_mdns_get (const NMIP4Config *self)
+{
+	return NM_IP4_CONFIG_GET_PRIVATE (self)->mdns;
+}
+
+void
+nm_ip4_config_mdns_set (NMIP4Config *self,
+                        NMSettingConnectionMdns mdns)
+{
+	NM_IP4_CONFIG_GET_PRIVATE (self)->mdns = mdns;
+}
+
+/*****************************************************************************/
+
 void
 nm_ip4_config_set_dns_priority (NMIP4Config *self, gint priority)
 {
@@ -2806,9 +2884,9 @@ nm_ip4_config_equal (const NMIP4Config *a, const NMIP4Config *b)
 {
 	GChecksum *a_checksum = g_checksum_new (G_CHECKSUM_SHA1);
 	GChecksum *b_checksum = g_checksum_new (G_CHECKSUM_SHA1);
-	gsize a_len = g_checksum_type_get_length (G_CHECKSUM_SHA1);
-	gsize b_len = g_checksum_type_get_length (G_CHECKSUM_SHA1);
-	guchar a_data[a_len], b_data[b_len];
+	guchar a_data[20], b_data[20];
+	gsize a_len = sizeof (a_data);
+	gsize b_len = sizeof (b_data);
 	gboolean equal;
 
 	if (a)
@@ -2819,7 +2897,8 @@ nm_ip4_config_equal (const NMIP4Config *a, const NMIP4Config *b)
 	g_checksum_get_digest (a_checksum, a_data, &a_len);
 	g_checksum_get_digest (b_checksum, b_data, &b_len);
 
-	g_assert (a_len == b_len);
+	nm_assert (a_len == sizeof (a_data));
+	nm_assert (b_len == sizeof (b_data));
 	equal = !memcmp (a_data, b_data, a_len);
 
 	g_checksum_free (a_checksum);
@@ -2889,7 +2968,7 @@ get_property (GObject *object, guint prop_id,
 
 				if (*address->label) {
 					g_variant_builder_add (&addr_builder, "{sv}",
-					                       "label",
+					                       NM_IP_ADDRESS_ATTRIBUTE_LABEL,
 					                       g_variant_new_string (address->label));
 				}
 
@@ -3065,6 +3144,7 @@ nm_ip4_config_init (NMIP4Config *self)
 	nm_ip_config_dedup_multi_idx_type_init ((NMIPConfigDedupMultiIdxType *) &priv->idx_ip4_routes,
 	                                        NMP_OBJECT_TYPE_IP4_ROUTE);
 
+	priv->mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
 	priv->nameservers = g_array_new (FALSE, FALSE, sizeof (guint32));
 	priv->domains = g_ptr_array_new_with_free_func (g_free);
 	priv->searches = g_ptr_array_new_with_free_func (g_free);
@@ -3112,13 +3192,37 @@ finalize (GObject *object)
 	nm_dedup_multi_index_unref (priv->multi_idx);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_ip4_config = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_IP4_CONFIG,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Addresses",   "aau",    NM_IP4_CONFIG_ADDRESSES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("AddressData", "aa{sv}", NM_IP4_CONFIG_ADDRESS_DATA),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Gateway",     "s",      NM_IP4_CONFIG_GATEWAY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Routes",      "aau",    NM_IP4_CONFIG_ROUTES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("RouteData",   "aa{sv}", NM_IP4_CONFIG_ROUTE_DATA),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Nameservers", "au",     NM_IP4_CONFIG_NAMESERVERS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Domains",     "as",     NM_IP4_CONFIG_DOMAINS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Searches",    "as",     NM_IP4_CONFIG_SEARCHES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("DnsOptions",  "as",     NM_IP4_CONFIG_DNS_OPTIONS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("DnsPriority", "i",      NM_IP4_CONFIG_DNS_PRIORITY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WinsServers", "au",     NM_IP4_CONFIG_WINS_SERVERS),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_ip4_config_class_init (NMIP4ConfigClass *config_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (config_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (config_class);
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/IP4Config");
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/IP4Config");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_ip4_config);
 
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
@@ -3198,8 +3302,4 @@ nm_ip4_config_class_init (NMIP4ConfigClass *config_class)
 	                          G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (config_class),
-	                                        NMDBUS_TYPE_IP4_CONFIG_SKELETON,
-	                                        NULL);
 }
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index 7c345d20..1c5222df 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -21,7 +21,8 @@
 #ifndef __NETWORKMANAGER_IP4_CONFIG_H__
 #define __NETWORKMANAGER_IP4_CONFIG_H__
 
-#include "nm-exported-object.h"
+#include "nm-setting-connection.h"
+
 #include "nm-setting-ip4-config.h"
 
 #include "nm-utils/nm-dedup-multi.h"
@@ -154,11 +155,12 @@ GType nm_ip4_config_get_type (void);
 NMIP4Config * nm_ip4_config_new (NMDedupMultiIndex *multi_idx,
                                  int ifindex);
 
+NMIP4Config *nm_ip4_config_clone (const NMIP4Config *self);
 int nm_ip4_config_get_ifindex (const NMIP4Config *self);
 
 NMDedupMultiIndex *nm_ip4_config_get_multi_idx (const NMIP4Config *self);
 
-NMIP4Config *nm_ip4_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex, gboolean capture_resolv_conf);
+NMIP4Config *nm_ip4_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex);
 
 void nm_ip4_config_add_dependent_routes (NMIP4Config *self,
                                          guint32 route_table,
@@ -171,6 +173,7 @@ gboolean nm_ip4_config_commit (const NMIP4Config *self,
 
 void nm_ip4_config_merge_setting (NMIP4Config *self,
                                   NMSettingIPConfig *setting,
+                                  NMSettingConnectionMdns mdns,
                                   guint32 route_table,
                                   guint32 route_metric);
 NMSetting *nm_ip4_config_create_setting (const NMIP4Config *self);
@@ -186,6 +189,9 @@ void nm_ip4_config_subtract (NMIP4Config *dst,
 void nm_ip4_config_intersect (NMIP4Config *dst,
                               const NMIP4Config *src,
                               guint32 default_route_metric_penalty);
+NMIP4Config *nm_ip4_config_intersect_alloc (const NMIP4Config *a,
+                                            const NMIP4Config *b,
+                                            guint32 default_route_metric_penalty);
 gboolean nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relevant_changes);
 void nm_ip4_config_dump (const NMIP4Config *self, const char *detail);
 
@@ -194,6 +200,10 @@ const NMPObject *_nm_ip4_config_best_default_route_find (const NMIP4Config *self
 
 in_addr_t nmtst_ip4_config_get_gateway (NMIP4Config *config);
 
+NMSettingConnectionMdns nm_ip4_config_mdns_get (const NMIP4Config *self);
+void                    nm_ip4_config_mdns_set (NMIP4Config *self,
+                                                NMSettingConnectionMdns mdns);
+
 const NMDedupMultiHeadEntry *nm_ip4_config_lookup_addresses (const NMIP4Config *self);
 void nm_ip4_config_reset_addresses (NMIP4Config *self);
 void nm_ip4_config_add_address (NMIP4Config *self, const NMPlatformIP4Address *address);
@@ -218,6 +228,13 @@ const NMPlatformIP4Route *nm_ip4_config_get_direct_route_for_host (const NMIP4Co
 
 void nm_ip4_config_reset_nameservers (NMIP4Config *self);
 void nm_ip4_config_add_nameserver (NMIP4Config *self, guint32 nameserver);
+
+static inline void
+_nm_ip4_config_add_nameserver (NMIP4Config *self, const guint32 *nameserver)
+{
+	nm_ip4_config_add_nameserver (self, *nameserver);
+}
+
 void nm_ip4_config_del_nameserver (NMIP4Config *self, guint i);
 guint nm_ip4_config_get_num_nameservers (const NMIP4Config *self);
 guint32 nm_ip4_config_get_nameserver (const NMIP4Config *self, guint i);
@@ -338,7 +355,7 @@ nm_ip_config_get_addr_family (const NMIPConfig *config)
 	g_return_val_if_reached (AF_UNSPEC);
 }
 
-#define _NM_IP_CONFIG_DISPATCH(config, v4_func, v6_func, dflt, ...) \
+#define _NM_IP_CONFIG_DISPATCH(config, v4_func, v6_func, ...) \
 	G_STMT_START { \
 		gconstpointer _config = (config); \
 		\
@@ -350,58 +367,217 @@ nm_ip_config_get_addr_family (const NMIPConfig *config)
 		} \
 	} G_STMT_END
 
+#define _NM_IP_CONFIG_DISPATCH_VOID(config, v4_func, v6_func, ...) \
+	G_STMT_START { \
+		gconstpointer _config = (config); \
+		\
+		if (NM_IS_IP4_CONFIG (_config)) { \
+			v4_func ((NMIP4Config *) _config, ##__VA_ARGS__); \
+		} else { \
+			nm_assert (NM_IS_IP6_CONFIG (_config)); \
+			v6_func ((NMIP6Config *) _config, ##__VA_ARGS__); \
+		} \
+	} G_STMT_END
+
+static inline int
+nm_ip_config_get_ifindex (const NMIPConfig *self)
+{
+	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_ifindex, nm_ip6_config_get_ifindex);
+}
+
+static inline void
+nm_ip_config_hash (const NMIPConfig *self, GChecksum *sum, gboolean dns_only)
+{
+	_NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_hash, nm_ip6_config_hash, sum, dns_only);
+}
+
+static inline void
+nm_ip_config_add_address (NMIPConfig *self, const NMPlatformIPAddress *address)
+{
+	_NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_add_address, nm_ip6_config_add_address, (gconstpointer) address);
+}
+
+static inline void
+nm_ip_config_reset_addresses (NMIPConfig *self)
+{
+	_NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_reset_addresses, nm_ip6_config_reset_addresses);
+}
+
+static inline void
+nm_ip_config_add_route (NMIPConfig *self,
+                        const NMPlatformIPRoute *new,
+                        const NMPObject **out_obj_new)
+{
+	_NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_add_route, nm_ip6_config_add_route, (gpointer) new, out_obj_new);
+}
+
+static inline void
+nm_ip_config_reset_routes (NMIPConfig *self)
+{
+	_NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_reset_routes, nm_ip6_config_reset_routes);
+}
+
 static inline int
 nm_ip_config_get_dns_priority (const NMIPConfig *self)
 {
-	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_dns_priority, nm_ip6_config_get_dns_priority, 0);
+	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_dns_priority, nm_ip6_config_get_dns_priority);
+}
+
+static inline void
+nm_ip_config_set_dns_priority (NMIPConfig *self, gint priority)
+{
+	_NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_set_dns_priority, nm_ip6_config_set_dns_priority, priority);
+}
+
+static inline void
+nm_ip_config_add_nameserver (NMIPConfig *self, const NMIPAddr *ns)
+{
+	_NM_IP_CONFIG_DISPATCH_VOID (self, _nm_ip4_config_add_nameserver, nm_ip6_config_add_nameserver, (gconstpointer) ns);
+}
+
+static inline void
+nm_ip_config_reset_nameservers (const NMIPConfig *self)
+{
+	_NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_reset_nameservers, nm_ip6_config_reset_nameservers);
 }
 
 static inline guint
 nm_ip_config_get_num_nameservers (const NMIPConfig *self)
 {
-	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_num_nameservers, nm_ip6_config_get_num_nameservers, 0);
+	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_num_nameservers, nm_ip6_config_get_num_nameservers);
 }
 
 static inline gconstpointer
 nm_ip_config_get_nameserver (const NMIPConfig *self, guint i)
 {
-	_NM_IP_CONFIG_DISPATCH (self, _nm_ip4_config_get_nameserver, nm_ip6_config_get_nameserver, 0, i);
+	_NM_IP_CONFIG_DISPATCH (self, _nm_ip4_config_get_nameserver, nm_ip6_config_get_nameserver, i);
 }
 
 static inline guint
 nm_ip_config_get_num_domains (const NMIPConfig *self)
 {
-	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_num_domains, nm_ip6_config_get_num_domains, 0);
+	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_num_domains, nm_ip6_config_get_num_domains);
 }
 
 static inline const char *
 nm_ip_config_get_domain (const NMIPConfig *self, guint i)
 {
-	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_domain, nm_ip6_config_get_domain, NULL, i);
+	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_domain, nm_ip6_config_get_domain, i);
+}
+
+static inline void
+nm_ip_config_reset_searches (const NMIPConfig *self)
+{
+	_NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_reset_searches, nm_ip6_config_reset_searches);
+}
+
+static inline void
+nm_ip_config_add_search (const NMIPConfig *self, const char *new)
+{
+	_NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_add_search, nm_ip6_config_add_search, new);
 }
 
 static inline guint
 nm_ip_config_get_num_searches (const NMIPConfig *self)
 {
-	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_num_searches, nm_ip6_config_get_num_searches, 0);
+	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_num_searches, nm_ip6_config_get_num_searches);
 }
 
 static inline const char *
 nm_ip_config_get_search (const NMIPConfig *self, guint i)
 {
-	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_search, nm_ip6_config_get_search, NULL, i);
+	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_search, nm_ip6_config_get_search, i);
 }
 
 static inline guint
 nm_ip_config_get_num_dns_options (const NMIPConfig *self)
 {
-	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_num_dns_options, nm_ip6_config_get_num_dns_options, 0);
+	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_num_dns_options, nm_ip6_config_get_num_dns_options);
 }
 
 static inline const char *
 nm_ip_config_get_dns_option (const NMIPConfig *self, guint i)
 {
-	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_dns_option, nm_ip6_config_get_dns_option, NULL, i);
+	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_dns_option, nm_ip6_config_get_dns_option, i);
+}
+
+#define _NM_IP_CONFIG_DISPATCH_SET_OP(_return, dst, src, v4_func, v6_func, ...) \
+	G_STMT_START { \
+		gpointer _dst = (dst); \
+		gconstpointer _src = (src); \
+		\
+		if (NM_IS_IP4_CONFIG (_dst)) { \
+			nm_assert (NM_IS_IP4_CONFIG (_src)); \
+			_return v4_func ((NMIP4Config *) _dst, (const NMIP4Config *) _src, ##__VA_ARGS__); \
+		} else { \
+			nm_assert (NM_IS_IP6_CONFIG (_src)); \
+			_return v6_func ((NMIP6Config *) _dst, (const NMIP6Config *) _src, ##__VA_ARGS__); \
+		} \
+	} G_STMT_END
+
+static inline void
+nm_ip_config_intersect (NMIPConfig *dst,
+                        const NMIPConfig *src,
+                        guint32 default_route_metric_penalty)
+{
+	_NM_IP_CONFIG_DISPATCH_SET_OP (, dst, src,
+	                               nm_ip4_config_intersect,
+	                               nm_ip6_config_intersect,
+	                               default_route_metric_penalty);
+}
+
+static inline void
+nm_ip_config_subtract (NMIPConfig *dst,
+                       const NMIPConfig *src,
+                       guint32 default_route_metric_penalty)
+{
+	_NM_IP_CONFIG_DISPATCH_SET_OP (, dst, src,
+	                               nm_ip4_config_subtract,
+	                               nm_ip6_config_subtract,
+	                               default_route_metric_penalty);
+}
+
+static inline void
+nm_ip_config_merge (NMIPConfig *dst,
+                    const NMIPConfig *src,
+                    NMIPConfigMergeFlags merge_flags,
+                    guint32 default_route_metric_penalty)
+{
+	_NM_IP_CONFIG_DISPATCH_SET_OP (, dst, src,
+	                               nm_ip4_config_merge,
+	                               nm_ip6_config_merge,
+	                               merge_flags,
+	                               default_route_metric_penalty);
+}
+
+static inline gboolean
+nm_ip_config_replace (NMIPConfig *dst,
+                      const NMIPConfig *src,
+                      gboolean *relevant_changes)
+{
+	_NM_IP_CONFIG_DISPATCH_SET_OP (return, dst, src,
+	                               nm_ip4_config_replace,
+	                               nm_ip6_config_replace,
+	                               relevant_changes);
+}
+
+static inline NMIPConfig *
+nm_ip_config_intersect_alloc (const NMIPConfig *a,
+                              const NMIPConfig *b,
+                              guint32 default_route_metric_penalty)
+{
+	if (NM_IS_IP4_CONFIG (a)) {
+		nm_assert (NM_IS_IP4_CONFIG (b));
+		return (NMIPConfig *) nm_ip4_config_intersect_alloc ((const NMIP4Config *) a,
+		                                                     (const NMIP4Config *) b,
+		                                                     default_route_metric_penalty);
+	} else {
+		nm_assert (NM_IS_IP6_CONFIG (a));
+		nm_assert (NM_IS_IP6_CONFIG (b));
+		return (NMIPConfig *) nm_ip6_config_intersect_alloc ((const NMIP6Config *) a,
+		                                                     (const NMIP6Config *) b,
+		                                                     default_route_metric_penalty);
+	}
 }
 
 #endif /* __NETWORKMANAGER_IP4_CONFIG_H__ */
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index ea3a2029..c76da995 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -38,8 +38,7 @@
 #include "NetworkManagerUtils.h"
 #include "nm-ip4-config.h"
 #include "ndisc/nm-ndisc.h"
-
-#include "introspection/org.freedesktop.NetworkManager.IP6Config.h"
+#include "nm-dbus-object.h"
 
 /*****************************************************************************/
 
@@ -82,15 +81,15 @@ typedef struct {
 } NMIP6ConfigPrivate;
 
 struct _NMIP6Config {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	NMIP6ConfigPrivate _priv;
 };
 
 struct _NMIP6ConfigClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 };
 
-G_DEFINE_TYPE (NMIP6Config, nm_ip6_config, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMIP6Config, nm_ip6_config, NM_TYPE_DBUS_OBJECT)
 
 #define NM_IP6_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMIP6Config, NM_IS_IP6_CONFIG)
 
@@ -366,14 +365,24 @@ _nmtst_ip6_config_addresses_sort (NMIP6Config *self)
 }
 
 NMIP6Config *
-nm_ip6_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex, gboolean capture_resolv_conf, NMSettingIP6ConfigPrivacy use_temporary)
+nm_ip6_config_clone (const NMIP6Config *self)
+{
+	NMIP6Config *copy;
+
+	copy = nm_ip6_config_new (nm_ip6_config_get_multi_idx (self), -1);
+	nm_ip6_config_replace (copy, self, NULL);
+
+	return copy;
+}
+
+NMIP6Config *
+nm_ip6_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex, NMSettingIP6ConfigPrivacy use_temporary)
 {
 	NMIP6Config *self;
 	NMIP6ConfigPrivate *priv;
 	const NMDedupMultiHeadEntry *head_entry;
 	NMDedupMultiIter iter;
 	const NMPObject *plobj = NULL;
-	gboolean has_addresses = FALSE;
 
 	nm_assert (ifindex > 0);
 
@@ -399,7 +408,6 @@ nm_ip6_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int i
 			                            NULL,
 			                            NULL))
 				nm_assert_not_reached ();
-			has_addresses = TRUE;
 		}
 		head_entry = nm_ip6_config_lookup_addresses (self);
 		nm_assert (head_entry);
@@ -416,23 +424,6 @@ 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 the interface has the default route, and has IPv6 addresses, capture
-	 * nameservers from /etc/resolv.conf.
-	 */
-	if (   has_addresses
-	    && priv->best_default_route
-	    && capture_resolv_conf) {
-		gs_free char *rc_contents = NULL;
-
-		if (g_file_get_contents (_PATH_RESCONF, &rc_contents, NULL, NULL)) {
-			if (nm_utils_resolve_conf_parse (AF_INET6,
-			                                 rc_contents,
-			                                 priv->nameservers,
-			                                 priv->dns_options))
-				_notify (self, PROP_NAMESERVERS);
-		}
-	}
-
 	return self;
 }
 
@@ -441,7 +432,6 @@ nm_ip6_config_add_dependent_routes (NMIP6Config *self,
                                     guint32 route_table,
                                     guint32 route_metric)
 {
-	const NMIP6ConfigPrivate *priv;
 	const NMPlatformIP6Address *my_addr;
 	const NMPlatformIP6Route *my_route;
 	int ifindex;
@@ -449,8 +439,6 @@ nm_ip6_config_add_dependent_routes (NMIP6Config *self,
 
 	g_return_if_fail (NM_IS_IP6_CONFIG (self));
 
-	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
-
 	ifindex = nm_ip6_config_get_ifindex (self);
 	g_return_if_fail (ifindex > 0);
 
@@ -558,7 +546,7 @@ nm_ip6_config_commit (const NMIP6Config *self,
 	                                                    ifindex,
 	                                                    route_table_sync);
 
-	nm_platform_ip6_address_sync (platform, ifindex, addresses, TRUE);
+	nm_platform_ip6_address_sync (platform, ifindex, addresses, FALSE);
 
 	if (!nm_platform_ip_route_sync (platform,
 	                                AF_INET6,
@@ -577,7 +565,6 @@ nm_ip6_config_merge_setting (NMIP6Config *self,
                              guint32 route_table,
                              guint32 route_metric)
 {
-	NMIP6ConfigPrivate *priv;
 	guint naddresses, nroutes, nnameservers, nsearches;
 	const char *gateway_str;
 	struct in6_addr gateway_bin;
@@ -588,8 +575,6 @@ nm_ip6_config_merge_setting (NMIP6Config *self,
 
 	g_return_if_fail (NM_IS_SETTING_IP6_CONFIG (setting));
 
-	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
-
 	naddresses = nm_setting_ip_config_get_num_addresses (setting);
 	nroutes = nm_setting_ip_config_get_num_routes (setting);
 	nnameservers = nm_setting_ip_config_get_num_dns (setting);
@@ -817,8 +802,6 @@ nm_ip6_config_merge (NMIP6Config *dst,
                      NMIPConfigMergeFlags merge_flags,
                      guint32 default_route_metric_penalty)
 {
-	NMIP6ConfigPrivate *dst_priv;
-	const NMIP6ConfigPrivate *src_priv;
 	guint32 i;
 	NMDedupMultiIter ipconf_iter;
 	const NMPlatformIP6Address *address = NULL;
@@ -826,9 +809,6 @@ nm_ip6_config_merge (NMIP6Config *dst,
 	g_return_if_fail (src != NULL);
 	g_return_if_fail (dst != NULL);
 
-	dst_priv = NM_IP6_CONFIG_GET_PRIVATE (dst);
-	src_priv = NM_IP6_CONFIG_GET_PRIVATE (src);
-
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
@@ -1070,26 +1050,28 @@ nm_ip6_config_subtract (NMIP6Config *dst,
 	g_object_thaw_notify (G_OBJECT (dst));
 }
 
-void
-nm_ip6_config_intersect (NMIP6Config *dst,
-                         const NMIP6Config *src,
-                         guint32 default_route_metric_penalty)
+static gboolean
+_nm_ip6_config_intersect_helper (NMIP6Config *dst,
+                                 const NMIP6Config *src,
+                                 guint32 default_route_metric_penalty,
+                                 gboolean update_dst)
 {
 	NMIP6ConfigPrivate *dst_priv;
 	const NMIP6ConfigPrivate *src_priv;
 	NMDedupMultiIter ipconf_iter;
 	const NMPlatformIP6Address *a;
 	const NMPlatformIP6Route *r;
-	gboolean changed;
+	gboolean changed, result = FALSE;
 	const NMPObject *new_best_default_route;
 
-	g_return_if_fail (src);
-	g_return_if_fail (dst);
+	g_return_val_if_fail (src, FALSE);
+	g_return_val_if_fail (dst, FALSE);
 
 	dst_priv = NM_IP6_CONFIG_GET_PRIVATE (dst);
 	src_priv = NM_IP6_CONFIG_GET_PRIVATE (src);
 
-	g_object_freeze_notify (G_OBJECT (dst));
+	if (update_dst)
+		g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
 	changed = FALSE;
@@ -1099,13 +1081,18 @@ nm_ip6_config_intersect (NMIP6Config *dst,
 		                                     NMP_OBJECT_UP_CAST (a)))
 			continue;
 
+		if (!update_dst)
+			return TRUE;
+
 		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
 		                                       ipconf_iter.current) != 1)
 			nm_assert_not_reached ();
 		changed = TRUE;
 	}
-	if (changed)
+	if (changed) {
 		_notify_addresses (dst);
+		result = TRUE;
+	}
 
 	/* ignore nameservers */
 
@@ -1137,6 +1124,9 @@ nm_ip6_config_intersect (NMIP6Config *dst,
 			continue;
 		}
 
+		if (!update_dst)
+			return TRUE;
+
 		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
 		                                       ipconf_iter.current) != 1)
 			nm_assert_not_reached ();
@@ -1146,14 +1136,67 @@ nm_ip6_config_intersect (NMIP6Config *dst,
 		nm_assert (changed);
 		_notify (dst, PROP_GATEWAY);
 	}
-	if (changed)
+	if (changed) {
 		_notify_routes (dst);
+		result = TRUE;
+	}
 
 	/* ignore domains */
 	/* ignore dns searches */
 	/* ignore dns options */
 
-	g_object_thaw_notify (G_OBJECT (dst));
+	if (update_dst)
+		g_object_thaw_notify (G_OBJECT (dst));
+
+	return result;
+}
+
+/**
+ * nm_ip6_config_intersect:
+ * @dst: a configuration to be updated
+ * @src: another configuration
+ * @default_route_metric_penalty: the default route metric penalty
+ *
+ * Computes the intersection between @src and @dst and updates @dst in place
+ * with the result.
+ */
+void
+nm_ip6_config_intersect (NMIP6Config *dst,
+                         const NMIP6Config *src,
+                         guint32 default_route_metric_penalty)
+{
+	_nm_ip6_config_intersect_helper (dst, src, default_route_metric_penalty, TRUE);
+}
+
+/**
+ * nm_ip6_config_intersect_alloc:
+ * @a: a configuration
+ * @b: another configuration
+ * @default_route_metric_penalty: the default route metric penalty
+ *
+ * Computes the intersection between @a and @b and returns the result in a newly
+ * allocated configuration.  As a special case, if @a and @b are identical (with
+ * respect to the only properties considered - addresses and routes) the
+ * functions returns NULL so that one of existing configuration can be reused
+ * without allocation.
+ *
+ * Returns: the intersection between @a and @b, or %NULL if the result is equal
+ * to @a and @b.
+ */
+NMIP6Config *
+nm_ip6_config_intersect_alloc (const NMIP6Config *a,
+                               const NMIP6Config *b,
+                               guint32 default_route_metric_penalty)
+{
+	NMIP6Config *a_copy;
+
+	if (_nm_ip6_config_intersect_helper ((NMIP6Config *) a, b,
+	                                     default_route_metric_penalty, FALSE)) {
+		a_copy = nm_ip6_config_clone (a);
+		_nm_ip6_config_intersect_helper (a_copy, b, default_route_metric_penalty, TRUE);
+		return a_copy;
+	} else
+		return NULL;
 }
 
 /**
@@ -1423,7 +1466,7 @@ nm_ip6_config_dump (const NMIP6Config *self, const char *detail)
 
 	g_message ("--------- NMIP6Config %p (%s)", self, detail);
 
-	str = nm_exported_object_get_path (NM_EXPORTED_OBJECT (self));
+	str = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
 	if (str)
 		g_message ("   path: %s", str);
 
@@ -1629,22 +1672,43 @@ nm_ip6_config_lookup_address (const NMIP6Config *self,
 }
 
 const NMPlatformIP6Address *
-nm_ip6_config_get_address_first_nontentative (const NMIP6Config *self, gboolean linklocal)
+nm_ip6_config_find_first_address (const NMIP6Config *self,
+                                  NMPlatformMatchFlags match_flag)
 {
-	const NMIP6ConfigPrivate *priv;
 	const NMPlatformIP6Address *addr;
 	NMDedupMultiIter iter;
 
 	g_return_val_if_fail (NM_IS_IP6_CONFIG (self), NULL);
 
-	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+	nm_assert (!NM_FLAGS_ANY (match_flag, ~(  NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY
+	                                        | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY)));
 
-	linklocal = !!linklocal;
+	nm_assert (NM_FLAGS_ANY (match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY));
+	nm_assert (NM_FLAGS_ANY (match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY));
 
 	nm_ip_config_iter_ip6_address_for_each (&iter, self, &addr) {
-		if (   ((!!IN6_IS_ADDR_LINKLOCAL (&addr->address)) == linklocal)
-		    && !(addr->n_ifa_flags & IFA_F_TENTATIVE))
-			return addr;
+
+		if (IN6_IS_ADDR_LINKLOCAL (&addr->address)) {
+			if (!NM_FLAGS_HAS (match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL))
+				continue;
+		} else {
+			if (!NM_FLAGS_HAS (match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL))
+				continue;
+		}
+
+		if (NM_FLAGS_HAS (addr->n_ifa_flags, IFA_F_DADFAILED)) {
+			if (!NM_FLAGS_HAS (match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED))
+				continue;
+		} else if (   NM_FLAGS_HAS (addr->n_ifa_flags, IFA_F_TENTATIVE)
+		           && !NM_FLAGS_HAS (addr->n_ifa_flags, IFA_F_OPTIMISTIC)) {
+			if (!NM_FLAGS_HAS (match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE))
+				continue;
+		} else {
+			if (!NM_FLAGS_HAS (match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL))
+				continue;
+		}
+
+		return addr;
 	}
 
 	return NULL;
@@ -2403,9 +2467,9 @@ nm_ip6_config_equal (const NMIP6Config *a, const NMIP6Config *b)
 {
 	GChecksum *a_checksum = g_checksum_new (G_CHECKSUM_SHA1);
 	GChecksum *b_checksum = g_checksum_new (G_CHECKSUM_SHA1);
-	gsize a_len = g_checksum_type_get_length (G_CHECKSUM_SHA1);
-	gsize b_len = g_checksum_type_get_length (G_CHECKSUM_SHA1);
-	guchar a_data[a_len], b_data[b_len];
+	guchar a_data[20], b_data[20];
+	gsize a_len = sizeof (a_data);
+	gsize b_len = sizeof (b_data);
 	gboolean equal;
 
 	if (a)
@@ -2416,7 +2480,8 @@ nm_ip6_config_equal (const NMIP6Config *a, const NMIP6Config *b)
 	g_checksum_get_digest (a_checksum, a_data, &a_len);
 	g_checksum_get_digest (b_checksum, b_data, &b_len);
 
-	g_assert (a_len == b_len);
+	nm_assert (a_len == sizeof (a_data));
+	nm_assert (b_len == sizeof (b_data));
 	equal = !memcmp (a_data, b_data, a_len);
 
 	g_checksum_free (a_checksum);
@@ -2712,13 +2777,36 @@ finalize (GObject *object)
 	nm_dedup_multi_index_unref (priv->multi_idx);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_ip6_config = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_IP6_CONFIG,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Addresses",   "a(ayuay)",  NM_IP6_CONFIG_ADDRESSES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("AddressData", "aa{sv}",    NM_IP6_CONFIG_ADDRESS_DATA),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Gateway",     "s",         NM_IP6_CONFIG_GATEWAY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Routes",      "a(ayuayu)", NM_IP6_CONFIG_ROUTES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("RouteData",   "aa{sv}",    NM_IP6_CONFIG_ROUTE_DATA),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Nameservers", "aay",       NM_IP6_CONFIG_NAMESERVERS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Domains",     "as",        NM_IP6_CONFIG_DOMAINS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Searches",    "as",        NM_IP6_CONFIG_SEARCHES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("DnsOptions",  "as",        NM_IP6_CONFIG_DNS_OPTIONS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("DnsPriority", "i",         NM_IP6_CONFIG_DNS_PRIORITY),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_ip6_config_class_init (NMIP6ConfigClass *config_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (config_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (config_class);
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/IP6Config");
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/IP6Config");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_ip6_config);
 
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
@@ -2792,8 +2880,4 @@ nm_ip6_config_class_init (NMIP6ConfigClass *config_class)
 	                      G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (config_class),
-	                                        NMDBUS_TYPE_IP6_CONFIG_SKELETON,
-	                                        NULL);
 }
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index 2fb8b8a4..cd01ed02 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -23,7 +23,6 @@
 
 #include <netinet/in.h>
 
-#include "nm-exported-object.h"
 #include "nm-setting-ip6-config.h"
 
 #include "nm-utils/nm-dedup-multi.h"
@@ -101,12 +100,13 @@ GType nm_ip6_config_get_type (void);
 NMIP6Config * nm_ip6_config_new (struct _NMDedupMultiIndex *multi_idx, int ifindex);
 NMIP6Config * nm_ip6_config_new_cloned (const NMIP6Config *src);
 
+NMIP6Config *nm_ip6_config_clone (const NMIP6Config *self);
 int nm_ip6_config_get_ifindex (const NMIP6Config *self);
 
 struct _NMDedupMultiIndex *nm_ip6_config_get_multi_idx (const NMIP6Config *self);
 
 NMIP6Config *nm_ip6_config_capture (struct _NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex,
-                                    gboolean capture_resolv_conf, NMSettingIP6ConfigPrivacy use_temporary);
+                                    NMSettingIP6ConfigPrivacy use_temporary);
 
 void nm_ip6_config_add_dependent_routes (NMIP6Config *self,
                                          guint32 route_table,
@@ -133,6 +133,9 @@ void nm_ip6_config_subtract (NMIP6Config *dst,
 void nm_ip6_config_intersect (NMIP6Config *dst,
                               const NMIP6Config *src,
                               guint32 default_route_metric_penalty);
+NMIP6Config *nm_ip6_config_intersect_alloc (const NMIP6Config *a,
+                                            const NMIP6Config *b,
+                                            guint32 default_route_metric_penalty);
 gboolean nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboolean *relevant_changes);
 void nm_ip6_config_dump (const NMIP6Config *self, const char *detail);
 
@@ -146,7 +149,8 @@ void _nmtst_ip6_config_del_address (NMIP6Config *self, guint i);
 guint nm_ip6_config_get_num_addresses (const NMIP6Config *self);
 const NMPlatformIP6Address *nm_ip6_config_get_first_address (const NMIP6Config *self);
 const NMPlatformIP6Address *_nmtst_ip6_config_get_address (const NMIP6Config *self, guint i);
-const NMPlatformIP6Address *nm_ip6_config_get_address_first_nontentative (const NMIP6Config *self, gboolean linklocal);
+const NMPlatformIP6Address *nm_ip6_config_find_first_address (const NMIP6Config *self,
+                                                              NMPlatformMatchFlags match_flag);
 gboolean nm_ip6_config_address_exists (const NMIP6Config *self, const NMPlatformIP6Address *address);
 const NMPlatformIP6Address *nm_ip6_config_lookup_address (const NMIP6Config *self,
                                                           const struct in6_addr *addr);
diff --git a/src/nm-logging.c b/src/nm-logging.c
index f19f0de1..11e05c31 100644
--- a/src/nm-logging.c
+++ b/src/nm-logging.c
@@ -663,7 +663,7 @@ _nm_log_impl (const char *file,
 				NMLogDomain dom = dom_all & _nm_logging_enabled_state[level];
 
 				for (diter = &global.domain_desc[0]; diter->name; diter++) {
-					if (!NM_FLAGS_HAS (dom_all, diter->num))
+					if (!NM_FLAGS_ANY (dom_all, diter->num))
 						continue;
 
 					/* construct a list of all domains (not only the enabled ones).
@@ -681,7 +681,7 @@ _nm_log_impl (const char *file,
 						g_string_append (s_domain_all, diter->name);
 					}
 
-					if (NM_FLAGS_HAS (dom, diter->num)) {
+					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_a (iov++, _MAX_LEN (30, diter->name), "SYSLOG_FACILITY=%s", diter->name);
@@ -768,6 +768,9 @@ nm_log_handler (const gchar *log_domain,
 		break;
 	}
 
+	if (global.debug_stderr)
+		g_printerr ("%s%s\n", global.prefix, message ?: "");
+
 	switch (global.log_backend) {
 #if SYSTEMD_JOURNAL
 	case LOG_BACKEND_JOURNAL:
diff --git a/src/nm-logging.h b/src/nm-logging.h
index 8fcbc8cc..36ba6a5a 100644
--- a/src/nm-logging.h
+++ b/src/nm-logging.h
@@ -83,6 +83,16 @@ typedef enum  { /*< skip >*/
 	LOGD_IP         = LOGD_IP4 | LOGD_IP6,
 } NMLogDomain;
 
+static inline NMLogDomain
+LOGD_IP_from_af (int addr_family)
+{
+	switch (addr_family) {
+	case AF_INET:  return LOGD_IP4;
+	case AF_INET6: return LOGD_IP6;
+	}
+	g_return_val_if_reached (LOGD_NONE);
+}
+
 /* Log levels */
 typedef enum  { /*< skip >*/
 	LOGL_TRACE,
@@ -123,7 +133,7 @@ typedef enum  { /*< skip >*/
                       ""__VA_ARGS__); \
     } G_STMT_END
 
-/* nm_log() only evaluates it's argument list after checking
+/* nm_log() only evaluates its argument list after checking
  * whether logging for the given level/domain is enabled.  */
 #define nm_log(level, domain, ifname, con_uuid, ...) \
     G_STMT_START { \
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 8391dbf2..1ccfad8e 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -30,7 +30,7 @@
 #include <unistd.h>
 
 #include "nm-common-macros.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
 #include "vpn/nm-vpn-manager.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-generic.h"
@@ -56,12 +56,10 @@
 #include "nm-dbus-compat.h"
 #include "nm-checkpoint.h"
 #include "nm-checkpoint-manager.h"
+#include "nm-dbus-object.h"
 #include "nm-dispatcher.h"
 #include "NetworkManagerUtils.h"
 
-#include "introspection/org.freedesktop.NetworkManager.h"
-#include "introspection/org.freedesktop.NetworkManager.Device.h"
-
 /*****************************************************************************/
 
 typedef struct {
@@ -80,8 +78,6 @@ enum {
 	INTERNAL_DEVICE_ADDED,
 	DEVICE_REMOVED,
 	INTERNAL_DEVICE_REMOVED,
-	STATE_CHANGED,
-	CHECK_PERMISSIONS,
 	ACTIVE_CONNECTION_ADDED,
 	ACTIVE_CONNECTION_REMOVED,
 	CONFIGURE_QUIT,
@@ -114,6 +110,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMManager,
 	PROP_METERED,
 	PROP_GLOBAL_DNS_CONFIGURATION,
 	PROP_ALL_DEVICES,
+	PROP_CHECKPOINTS,
 
 	/* Not exported */
 	PROP_SLEEPING,
@@ -131,16 +128,14 @@ typedef struct {
 	NMActiveConnection *activating_connection;
 	NMMetered metered;
 
-	GSList *devices;
+	CList devices_lst_head;
+
 	NMState state;
 	NMConfig *config;
-	NMConnectivityState connectivity_state;
-
+	NMConnectivity *concheck_mgr;
 	NMPolicy *policy;
-
 	NMHostnameManager *hostname_manager;
 
-	NMBusManager  *dbus_mgr;
 	struct {
 		GDBusConnection *connection;
 		guint            id;
@@ -173,26 +168,30 @@ typedef struct {
 
 	guint devices_inited_id;
 
+	NMConnectivityState connectivity_state;
+
 	bool startup:1;
 	bool devices_inited:1;
 
 	bool sleeping:1;
 	bool net_enabled:1;
 
+	unsigned connectivity_check_enabled_last:2;
+
 	guint delete_volatile_connection_idle_id;
 	CList delete_volatile_connection_lst_head;
 } NMManagerPrivate;
 
 struct _NMManager {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	NMManagerPrivate _priv;
 };
 
 typedef struct {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 } NMManagerClass;
 
-G_DEFINE_TYPE (NMManager, nm_manager, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMManager, nm_manager, NM_TYPE_DBUS_OBJECT)
 
 #define NM_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMManager, NM_IS_MANAGER)
 
@@ -269,15 +268,27 @@ NM_DEFINE_SINGLETON_INSTANCE (NMManager);
 
 /*****************************************************************************/
 
+static const NMDBusInterfaceInfoExtended interface_info_manager;
+static const GDBusSignalInfo signal_info_check_permissions;
+static const GDBusSignalInfo signal_info_state_changed;
+static const GDBusSignalInfo signal_info_device_added;
+static const GDBusSignalInfo signal_info_device_removed;
+
 static gboolean add_device (NMManager *self, NMDevice *device, GError **error);
 
+static void _emit_device_added_removed (NMManager *self,
+                                        NMDevice *device,
+                                        gboolean is_added);
+
 static NMActiveConnection *_new_active_connection (NMManager *self,
+                                                   gboolean is_vpn,
                                                    NMConnection *connection,
                                                    NMConnection *applied,
                                                    const char *specific_object,
                                                    NMDevice *device,
                                                    NMAuthSubject *subject,
                                                    NMActivationType activation_type,
+                                                   NMActivationReason activation_reason,
                                                    GError **error);
 
 static void policy_activating_device_changed (GObject *object, GParamSpec *pspec, gpointer user_data);
@@ -318,6 +329,8 @@ static NMActiveConnection *active_connection_find_first (NMManager *self,
                                                          const char *uuid,
                                                          NMActiveConnectionState max_state);
 
+static NMConnectivity *concheck_get_mgr (NMManager *self);
+
 /*****************************************************************************/
 
 static NM_CACHED_QUARK_FCN ("active-connection-add-and-activate", active_connection_add_and_activate_quark)
@@ -326,6 +339,73 @@ static NM_CACHED_QUARK_FCN ("autoconnect-root", autoconnect_root_quark)
 
 /*****************************************************************************/
 
+static gboolean
+_connection_is_vpn (NMConnection *connection)
+{
+	const char *type;
+
+	type = nm_connection_get_connection_type (connection);
+	if (type)
+		return nm_streq (type, NM_SETTING_VPN_SETTING_NAME);
+
+	/* we have an incomplete (invalid) connection at hand. That can only
+	 * happen during AddAndActivate. Determine whether it's VPN type based
+	 * on the existance of a [vpn] section. */
+	return !!nm_connection_get_setting_vpn (connection);
+}
+
+/*****************************************************************************/
+
+static gboolean
+concheck_enabled (NMManager *self, gboolean *out_changed)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	guint check_enabled;
+
+	check_enabled = nm_connectivity_check_enabled (concheck_get_mgr (self))
+	                ? 1 : 2;
+	if (priv->connectivity_check_enabled_last == check_enabled)
+		NM_SET_OUT (out_changed, FALSE);
+	else {
+		NM_SET_OUT (out_changed, TRUE);
+		priv->connectivity_check_enabled_last = check_enabled;
+	}
+	return check_enabled == 1;
+}
+
+static void
+concheck_config_changed_cb (NMConnectivity *connectivity,
+                            NMManager *self)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMDevice *device;
+	gboolean changed;
+
+	concheck_enabled (self, &changed);
+	if (changed)
+		_notify (self, PROP_CONNECTIVITY_CHECK_ENABLED);
+
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst)
+		nm_device_check_connectivity_update_interval (device);
+}
+
+static NMConnectivity *
+concheck_get_mgr (NMManager *self)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+
+	if (G_UNLIKELY (!priv->concheck_mgr)) {
+		priv->concheck_mgr = g_object_ref (nm_connectivity_get ());
+		g_signal_connect (priv->concheck_mgr,
+		                  NM_CONNECTIVITY_CONFIG_CHANGED,
+		                  G_CALLBACK (concheck_config_changed_cb),
+		                  self);
+	}
+	return priv->concheck_mgr;
+}
+
+/*****************************************************************************/
+
 typedef struct {
 	int ifindex;
 	guint32 aspired_metric;
@@ -434,10 +514,10 @@ _device_route_metric_get (NMManager *self,
 				 * hence we skip it. */
 				continue;
 			}
-			if (!nm_g_hash_table_add (priv->device_route_metrics,
-			                          _device_route_metric_data_new (device_state->ifindex,
-			                                                         device_state->route_metric_default_aspired,
-			                                                         device_state->route_metric_default_effective)))
+			if (!g_hash_table_add (priv->device_route_metrics,
+			                       _device_route_metric_data_new (device_state->ifindex,
+			                                                      device_state->route_metric_default_aspired,
+			                                                      device_state->route_metric_default_effective)))
 				nm_assert_not_reached ();
 		}
 	}
@@ -530,7 +610,7 @@ again:
 	_LOGT (LOGD_DEVICE, "default-route-metric: ifindex %d reserves metric %u (aspired %u)",
 	       data->ifindex, data->effective_metric, data->aspired_metric);
 
-	if (!nm_g_hash_table_add (priv->device_route_metrics, data))
+	if (!g_hash_table_add (priv->device_route_metrics, data))
 		nm_assert_not_reached ();
 
 out:
@@ -577,7 +657,7 @@ _delete_volatile_connection_do (NMManager *self,
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 
 	if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
-	                   NM_SETTINGS_CONNECTION_FLAGS_VOLATILE))
+	                   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
 		return;
 	if (active_connection_find_first (self,
 	                                  connection,
@@ -603,7 +683,7 @@ active_connection_remove (NMManager *self, NMActiveConnection *active)
 	nm_assert (NM_IS_ACTIVE_CONNECTION (active));
 	nm_assert (c_list_contains (&priv->active_connections_lst_head, &active->active_connections_lst));
 
-	notify = nm_exported_object_is_exported (NM_EXPORTED_OBJECT (active));
+	notify = nm_dbus_object_is_exported (NM_DBUS_OBJECT (active));
 
 	c_list_unlink (&active->active_connections_lst);
 	g_signal_emit (self, signals[ACTIVE_CONNECTION_REMOVED], 0, active);
@@ -613,7 +693,7 @@ active_connection_remove (NMManager *self, NMActiveConnection *active)
 
 	connection = nm_g_object_ref (nm_active_connection_get_settings_connection (active));
 
-	nm_exported_object_clear_and_unexport (&active);
+	nm_dbus_object_clear_and_unexport (&active);
 
 	if (connection)
 		_delete_volatile_connection_do (self, connection);
@@ -709,8 +789,8 @@ active_connection_add (NMManager *self,
 	                  G_CALLBACK (active_connection_default_changed),
 	                  self);
 
-	if (!nm_exported_object_is_exported (NM_EXPORTED_OBJECT (active)))
-		nm_exported_object_export (NM_EXPORTED_OBJECT (active));
+	if (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (active)))
+		nm_dbus_object_export (NM_DBUS_OBJECT (active));
 
 	g_signal_emit (self, signals[ACTIVE_CONNECTION_ADDED], 0, active);
 
@@ -774,7 +854,7 @@ _get_activatable_connections_filter (NMSettings *settings,
                                      gpointer user_data)
 {
 	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
-	                  NM_SETTINGS_CONNECTION_FLAGS_VOLATILE))
+	                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
 		return FALSE;
 	return !active_connection_find_first (user_data, connection, NULL, NM_ACTIVE_CONNECTION_STATE_DEACTIVATING);
 }
@@ -792,18 +872,20 @@ nm_manager_get_activatable_connections (NMManager *manager, guint *out_len, gboo
 }
 
 static NMActiveConnection *
-active_connection_get_by_path (NMManager *manager, const char *path)
+active_connection_get_by_path (NMManager *self, const char *path)
 {
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMActiveConnection *ac;
 
-	nm_assert (path);
+	ac = (NMActiveConnection *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
+	                                                           path);
+	if (   !ac
+	    || !NM_IS_ACTIVE_CONNECTION (ac)
+	    || c_list_is_empty (&ac->active_connections_lst))
+		return NULL;
 
-	c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
-		if (nm_streq0 (path, nm_exported_object_get_path (NM_EXPORTED_OBJECT (ac))))
-			return ac;
-	}
-	return NULL;
+	nm_assert (c_list_contains (&priv->active_connections_lst_head, &ac->active_connections_lst));
+	return ac;
 }
 
 /*****************************************************************************/
@@ -811,8 +893,14 @@ active_connection_get_by_path (NMManager *manager, const char *path)
 static void
 _config_changed_cb (NMConfig *config, NMConfigData *config_data, NMConfigChangeFlags changes, NMConfigData *old_data, NMManager *self)
 {
+	g_object_freeze_notify (G_OBJECT (self));
+
 	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);
+
+	g_object_thaw_notify (G_OBJECT (self));
 }
 
 static void
@@ -883,28 +971,31 @@ _reload_auth_cb (NMAuthChain *chain,
 	g_dbus_method_invocation_return_value (context, NULL);
 
 out:
-	nm_auth_chain_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 
 static void
-impl_manager_reload (NMManager *self,
-                     GDBusMethodInvocation *context,
-                     guint32 flags)
-{
-	NMManagerPrivate *priv;
+impl_manager_reload (NMDBusObject *obj,
+                     const NMDBusInterfaceInfoExtended *interface_info,
+                     const NMDBusMethodInfoExtended *method_info,
+                     GDBusConnection *connection,
+                     const char *sender,
+                     GDBusMethodInvocation *invocation,
+                     GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthChain *chain;
-	GError *error = NULL;
-
-	g_return_if_fail (NM_IS_MANAGER (self));
+	guint32 flags;
 
-	priv = NM_MANAGER_GET_PRIVATE (self);
+	g_variant_get (parameters, "(u)", &flags);
 
-	chain = nm_auth_chain_new_context (context, _reload_auth_cb, self);
+	chain = nm_auth_chain_new_context (invocation, _reload_auth_cb, self);
 	if (!chain) {
-		error = g_error_new_literal (NM_MANAGER_ERROR,
-		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Unable to authenticate request");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_MANAGER_ERROR,
+		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                                               "Unable to authenticate request");
 		return;
 	}
 
@@ -916,27 +1007,31 @@ impl_manager_reload (NMManager *self,
 /*****************************************************************************/
 
 NMDevice *
-nm_manager_get_device_by_path (NMManager *manager, const char *path)
+nm_manager_get_device_by_path (NMManager *self, const char *path)
 {
-	GSList *iter;
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMDevice *device;
 
-	g_return_val_if_fail (path != NULL, NULL);
+	g_return_val_if_fail (path, NULL);
 
-	for (iter = NM_MANAGER_GET_PRIVATE (manager)->devices; iter; iter = iter->next) {
-		if (!strcmp (nm_exported_object_get_path (NM_EXPORTED_OBJECT (iter->data)), path))
-			return NM_DEVICE (iter->data);
-	}
-	return NULL;
+	device = (NMDevice *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
+	                                                     path);
+	if (   !device
+	    || !NM_IS_DEVICE (device)
+	    || c_list_is_empty (&device->devices_lst))
+		return NULL;
+
+	nm_assert (c_list_contains (&priv->devices_lst_head, &device->devices_lst));
+	return device;
 }
 
 NMDevice *
-nm_manager_get_device_by_ifindex (NMManager *manager, int ifindex)
+nm_manager_get_device_by_ifindex (NMManager *self, int ifindex)
 {
-	GSList *iter;
-
-	for (iter = NM_MANAGER_GET_PRIVATE (manager)->devices; iter; iter = iter->next) {
-		NMDevice *device = NM_DEVICE (iter->data);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMDevice *device;
 
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
 		if (nm_device_get_ifindex (device) == ifindex)
 			return device;
 	}
@@ -945,19 +1040,24 @@ nm_manager_get_device_by_ifindex (NMManager *manager, int ifindex)
 }
 
 static NMDevice *
-find_device_by_permanent_hw_addr (NMManager *manager, const char *hwaddr)
+find_device_by_permanent_hw_addr (NMManager *self, const char *hwaddr)
 {
-	GSList *iter;
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMDevice *device;
 	const char *device_addr;
+	guint8 hwaddr_bin[NM_UTILS_HWADDR_LEN_MAX];
+	gsize hwaddr_len;
 
 	g_return_val_if_fail (hwaddr != NULL, NULL);
 
-	if (nm_utils_hwaddr_valid (hwaddr, -1)) {
-		for (iter = NM_MANAGER_GET_PRIVATE (manager)->devices; iter; iter = iter->next) {
-			device_addr = nm_device_get_permanent_hw_address (NM_DEVICE (iter->data));
-			if (device_addr && nm_utils_hwaddr_matches (hwaddr, -1, device_addr, -1))
-				return NM_DEVICE (iter->data);
-		}
+	if (!_nm_utils_hwaddr_aton (hwaddr, hwaddr_bin, sizeof (hwaddr_bin), &hwaddr_len))
+		return NULL;
+
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
+		device_addr = nm_device_get_permanent_hw_address (device);
+		if (   device_addr
+		    && nm_utils_hwaddr_matches (hwaddr_bin, hwaddr_len, device_addr, -1))
+			return device;
 	}
 	return NULL;
 }
@@ -965,16 +1065,15 @@ find_device_by_permanent_hw_addr (NMManager *manager, const char *hwaddr)
 static NMDevice *
 find_device_by_ip_iface (NMManager *self, const gchar *iface)
 {
-	GSList *iter;
-
-	g_return_val_if_fail (iface != NULL, NULL);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMDevice *device;
 
-	for (iter = NM_MANAGER_GET_PRIVATE (self)->devices; iter; iter = g_slist_next (iter)) {
-		NMDevice *candidate = iter->data;
+	g_return_val_if_fail (iface, NULL);
 
-		if (   nm_device_is_real (candidate)
-		    && g_strcmp0 (nm_device_get_ip_iface (candidate), iface) == 0)
-			return candidate;
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
+		if (   nm_device_is_real (device)
+		    && nm_streq0 (nm_device_get_ip_iface (device), iface))
+			return device;
 	}
 	return NULL;
 }
@@ -1002,12 +1101,11 @@ find_device_by_iface (NMManager *self,
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMDevice *fallback = NULL;
-	GSList *iter;
+	NMDevice *candidate;
 
 	g_return_val_if_fail (iface != NULL, NULL);
 
-	for (iter = priv->devices; iter; iter = iter->next) {
-		NMDevice *candidate = iter->data;
+	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
 
 		if (strcmp (nm_device_get_iface (candidate), iface))
 			continue;
@@ -1062,22 +1160,6 @@ _nm_state_to_string (NMState state)
 	}
 }
 
-static void
-set_state (NMManager *self, NMState state)
-{
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-
-	if (priv->state == state)
-		return;
-
-	priv->state = state;
-
-	_LOGI (LOGD_CORE, "NetworkManager state is now %s", _nm_state_to_string (state));
-
-	_notify (self, PROP_STATE);
-	g_signal_emit (self, signals[STATE_CHANGED], 0, priv->state);
-}
-
 static NMState
 find_best_device_state (NMManager *manager)
 {
@@ -1148,26 +1230,38 @@ nm_manager_update_metered (NMManager *self)
 }
 
 static void
-nm_manager_update_state (NMManager *manager)
+nm_manager_update_state (NMManager *self)
 {
 	NMManagerPrivate *priv;
 	NMState new_state = NM_STATE_DISCONNECTED;
 
-	g_return_if_fail (NM_IS_MANAGER (manager));
+	g_return_if_fail (NM_IS_MANAGER (self));
 
-	priv = NM_MANAGER_GET_PRIVATE (manager);
+	priv = NM_MANAGER_GET_PRIVATE (self);
 
-	if (manager_sleeping (manager))
+	if (manager_sleeping (self))
 		new_state = NM_STATE_ASLEEP;
 	else
-		new_state = find_best_device_state (manager);
+		new_state = find_best_device_state (self);
 
 	if (   new_state >= NM_STATE_CONNECTED_LOCAL
 	    && priv->connectivity_state == NM_CONNECTIVITY_FULL) {
 		new_state = NM_STATE_CONNECTED_GLOBAL;
 	}
 
-	set_state (manager, new_state);
+	if (priv->state == new_state)
+		return;
+
+	priv->state = new_state;
+
+	_LOGI (LOGD_CORE, "NetworkManager state is now %s", _nm_state_to_string (new_state));
+
+	_notify (self, PROP_STATE);
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
+	                            &interface_info_manager,
+	                            &signal_info_state_changed,
+	                            "(u)",
+	                            (guint32) priv->state);
 }
 
 static void
@@ -1180,6 +1274,10 @@ manager_device_state_changed (NMDevice *device,
 	NMManager *self = NM_MANAGER (user_data);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 
+	if (   old_state == NM_DEVICE_STATE_UNMANAGED
+	    && new_state > NM_DEVICE_STATE_UNMANAGED)
+		retry_connections_for_parent_device (self, device);
+
 	switch (new_state) {
 	case NM_DEVICE_STATE_UNMANAGED:
 	case NM_DEVICE_STATE_UNAVAILABLE:
@@ -1205,7 +1303,7 @@ static void
 check_if_startup_complete (NMManager *self)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	GSList *iter;
+	NMDevice *device;
 
 	if (!priv->startup)
 		return;
@@ -1218,12 +1316,10 @@ check_if_startup_complete (NMManager *self)
 		return;
 	}
 
-	for (iter = priv->devices; iter; iter = iter->next) {
-		NMDevice *dev = iter->data;
-
-		if (nm_device_has_pending_action (dev)) {
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
+		if (nm_device_has_pending_action (device)) {
 			_LOGD (LOGD_CORE, "check_if_startup_complete returns FALSE because of %s",
-			       nm_device_get_iface (dev));
+			       nm_device_get_iface (device));
 			return;
 		}
 	}
@@ -1235,8 +1331,8 @@ check_if_startup_complete (NMManager *self)
 	/* we no longer care about these signals. Startup-complete only
 	 * happens once. */
 	g_signal_handlers_disconnect_by_func (priv->settings, G_CALLBACK (settings_startup_complete_changed), self);
-	for (iter = priv->devices; iter; iter = iter->next) {
-		g_signal_handlers_disconnect_by_func (iter->data,
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
+		g_signal_handlers_disconnect_by_func (device,
 		                                      G_CALLBACK (device_has_pending_action_changed),
 		                                      self);
 	}
@@ -1268,18 +1364,18 @@ _parent_notify_changed (NMManager *self,
                         NMDevice *device,
                         gboolean device_removed)
 {
-	GSList *iter;
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMDevice *candidate;
 
 	nm_assert (NM_IS_DEVICE (device));
-	nm_assert (NM_IS_MANAGER (self));
 
-	for (iter = NM_MANAGER_GET_PRIVATE (self)->devices; iter; ) {
-		if (nm_device_parent_notify_changed (iter->data, device, device_removed)) {
+again:
+	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
+		if (nm_device_parent_notify_changed (candidate, device, device_removed)) {
 			/* in the unlikely event that this changes anything, we start iterating
 			 * again, to be sure that the device list is up-to-date. */
-			iter = NM_MANAGER_GET_PRIVATE (self)->devices;
-		} else
-			iter = iter->next;
+			goto again;
+		}
 	}
 }
 
@@ -1319,7 +1415,8 @@ remove_device (NMManager *self,
 	g_signal_handlers_disconnect_matched (device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
 
 	nm_settings_device_removed (priv->settings, device, quitting);
-	priv->devices = g_slist_remove (priv->devices, device);
+
+	c_list_unlink (&device->devices_lst);
 
 	_parent_notify_changed (self, device, TRUE);
 
@@ -1337,8 +1434,7 @@ remove_device (NMManager *self,
 		 * Control that by passing @unconfigure_ip_config.  */
 		nm_device_removed (device, unconfigure_ip_config);
 
-		g_signal_emit (self, signals[DEVICE_REMOVED], 0, device);
-		_notify (self, PROP_DEVICES);
+		_emit_device_added_removed (self, device, FALSE);
 	} else {
 		/* unrealize() does not release a slave device from master and
 		 * clear IP configurations, do it here */
@@ -1348,7 +1444,7 @@ remove_device (NMManager *self,
 	g_signal_emit (self, signals[INTERNAL_DEVICE_REMOVED], 0, device);
 	_notify (self, PROP_ALL_DEVICES);
 
-	nm_exported_object_clear_and_unexport (&device);
+	nm_dbus_object_clear_and_unexport (&device);
 
 	check_if_startup_complete (self);
 }
@@ -1377,7 +1473,7 @@ find_parent_device_for_connection (NMManager *self, NMConnection *connection, NM
 	const char *parent_name = NULL;
 	NMSettingsConnection *parent_connection;
 	NMDevice *parent, *first_compatible = NULL;
-	GSList *iter;
+	NMDevice *candidate;
 
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
 
@@ -1410,9 +1506,7 @@ find_parent_device_for_connection (NMManager *self, NMConnection *connection, NM
 	/* Check if the parent connection is currently activated or is comaptible
 	 * with some known device.
 	 */
-	for (iter = priv->devices; iter; iter = iter->next) {
-		NMDevice *candidate = iter->data;
-
+	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
 		/* Unmanaged devices are not compatible with any connection */
 		if (!nm_device_get_managed (candidate, FALSE))
 			continue;
@@ -1528,18 +1622,15 @@ NMDevice *
 nm_manager_get_device (NMManager *self, const char *ifname, NMDeviceType device_type)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	GSList *iter;
-	NMDevice *d;
+	NMDevice *device;
 
 	g_return_val_if_fail (ifname, NULL);
 	g_return_val_if_fail (device_type != NM_DEVICE_TYPE_UNKNOWN, NULL);
 
-	for (iter = priv->devices; iter; iter = iter->next) {
-		d = iter->data;
-
-		if (   nm_device_get_device_type (d) == device_type
-		    && nm_streq0 (nm_device_get_iface (d), ifname))
-			return d;
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
+		if (   nm_device_get_device_type (device) == device_type
+		    && nm_streq0 (nm_device_get_iface (device), ifname))
+			return device;
 	}
 
 	return NULL;
@@ -1575,9 +1666,9 @@ system_create_virtual_device (NMManager *self, NMConnection *connection)
 	NMDeviceFactory *factory;
 	gs_free NMSettingsConnection **connections = NULL;
 	guint i;
-	GSList *iter;
 	gs_free char *iface = NULL;
 	NMDevice *device = NULL, *parent = NULL;
+	NMDevice *dev_candidate;
 	GError *error = NULL;
 	NMLogLevel log_level;
 
@@ -1593,17 +1684,15 @@ system_create_virtual_device (NMManager *self, NMConnection *connection)
 	}
 
 	/* See if there's a device that is already compatible with this connection */
-	for (iter = priv->devices; iter; iter = g_slist_next (iter)) {
-		NMDevice *candidate = iter->data;
-
-		if (nm_device_check_connection_compatible (candidate, connection)) {
-			if (nm_device_is_real (candidate)) {
+	c_list_for_each_entry (dev_candidate, &priv->devices_lst_head, devices_lst) {
+		if (nm_device_check_connection_compatible (dev_candidate, connection)) {
+			if (nm_device_is_real (dev_candidate)) {
 				_LOG3D (LOGD_DEVICE, connection, "already created virtual interface name %s",
 				       iface);
 				return NULL;
 			}
 
-			device = candidate;
+			device = dev_candidate;
 			break;
 		}
 	}
@@ -1799,7 +1888,7 @@ connection_flags_changed (NMSettings *settings,
 	DeleteVolatileConnectionData *data;
 
 	if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
-	                   NM_SETTINGS_CONNECTION_FLAGS_VOLATILE))
+	                   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
 		return;
 
 	if (active_connection_find_first (self, connection, NULL, NM_ACTIVE_CONNECTION_STATE_DEACTIVATED)) {
@@ -1824,10 +1913,10 @@ system_unmanaged_devices_changed_cb (NMSettings *settings,
 {
 	NMManager *self = NM_MANAGER (user_data);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	const GSList *iter;
+	NMDevice *device;
 
-	for (iter = priv->devices; iter; iter = g_slist_next (iter))
-		nm_device_set_unmanaged_by_user_settings (NM_DEVICE (iter->data));
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst)
+		nm_device_set_unmanaged_by_user_settings (device);
 }
 
 static void
@@ -1873,7 +1962,7 @@ manager_update_radio_enabled (NMManager *self,
                               gboolean enabled)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	GSList *iter;
+	NMDevice *device;
 
 	/* Do nothing for radio types not yet implemented */
 	if (!rstate->prop)
@@ -1886,9 +1975,7 @@ manager_update_radio_enabled (NMManager *self,
 		return;
 
 	/* enable/disable wireless devices as required */
-	for (iter = priv->devices; iter; iter = iter->next) {
-		NMDevice *device = NM_DEVICE (iter->data);
-
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
 		if (nm_device_get_rfkill_type (device) == rstate->rtype) {
 			_LOG2D (LOGD_RFKILL, device, "rfkill: setting radio %s", enabled ? "enabled" : "disabled");
 			nm_device_set_enabled (device, enabled);
@@ -2031,7 +2118,7 @@ device_auth_done_cb (NMAuthChain *chain,
 	          nm_auth_chain_get_data (chain, "user-data"));
 
 	g_clear_error (&error);
-	nm_auth_chain_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 
 static void
@@ -2047,7 +2134,6 @@ device_auth_request_cb (NMDevice *device,
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	GError *error = NULL;
 	NMAuthSubject *subject = NULL;
-	char *error_desc = NULL;
 	NMAuthChain *chain;
 
 	/* Validate the caller */
@@ -2060,15 +2146,13 @@ device_auth_request_cb (NMDevice *device,
 	}
 
 	/* Ensure the subject has permissions for this connection */
-	if (connection && !nm_auth_is_subject_in_acl (connection,
-	                                              subject,
-	                                              &error_desc)) {
-		error = g_error_new_literal (NM_MANAGER_ERROR,
-		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             error_desc);
-		g_free (error_desc);
+	if (   connection
+	    && !nm_auth_is_subject_in_acl_set_error (connection,
+	                                             subject,
+	                                             NM_MANAGER_ERROR,
+	                                             NM_MANAGER_ERROR_PERMISSION_DENIED,
+	                                             &error))
 		goto done;
-	}
 
 	/* Validate the request */
 	chain = nm_auth_chain_new_subject (subject, context, device_auth_done_cb, self);
@@ -2112,13 +2196,14 @@ get_existing_connection (NMManager *self,
 	gs_unref_object NMConnection *connection = NULL;
 	NMSettingsConnection *added = NULL;
 	GError *error = NULL;
+	gs_free_error GError *gen_error = NULL;
 	NMDevice *master = NULL;
 	int ifindex = nm_device_get_ifindex (device);
 	NMSettingsConnection *matched;
 	NMSettingsConnection *connection_checked = NULL;
 	gboolean assume_state_guess_assume = FALSE;
 	const char *assume_state_connection_uuid = NULL;
-	gboolean maybe_later;
+	gboolean maybe_later, only_by_uuid = FALSE;
 
 	if (out_generated)
 		*out_generated = FALSE;
@@ -2151,14 +2236,18 @@ get_existing_connection (NMManager *self,
 	 * update_connection() implemented, otherwise nm_device_generate_connection()
 	 * returns NULL.
 	 */
-	connection = nm_device_generate_connection (device, master, &maybe_later, &error);
+	connection = nm_device_generate_connection (device, master, &maybe_later, &gen_error);
 	if (!connection) {
-		if (!maybe_later)
+		if (maybe_later) {
+			/* The device can generate a connection, but it failed for now.
+			 * Give it a chance to match a connection from the state file. */
+			only_by_uuid = TRUE;
+		} else {
 			nm_device_assume_state_reset (device);
-		_LOG2D (LOGD_DEVICE, device, "assume: cannot generate connection: %s",
-		        error->message);
-		g_error_free (error);
-		return NULL;
+			_LOG2D (LOGD_DEVICE, device, "assume: cannot generate connection: %s",
+			        gen_error->message);
+			return NULL;
+		}
 	}
 
 	nm_device_assume_state_get (device,
@@ -2179,21 +2268,31 @@ get_existing_connection (NMManager *self,
 	    && !active_connection_find_first (self, connection_checked, NULL,
 	                                      NM_ACTIVE_CONNECTION_STATE_DEACTIVATING)
 	    && nm_device_check_connection_compatible (device, NM_CONNECTION (connection_checked))) {
-		NMConnection *const connections[] = {
-			NM_CONNECTION (connection_checked),
-			NULL,
-		};
-
-		matched = NM_SETTINGS_CONNECTION (nm_utils_match_connection (connections,
-		                                                             connection,
-		                                                             TRUE,
-		                                                             nm_device_has_carrier (device),
-		                                                             nm_device_get_route_metric (device, AF_INET),
-		                                                             nm_device_get_route_metric (device, AF_INET6),
-		                                                             NULL, NULL));
+
+		if (connection) {
+			NMConnection *const connections[] = {
+				NM_CONNECTION (connection_checked),
+				NULL,
+			};
+
+			matched = NM_SETTINGS_CONNECTION (nm_utils_match_connection (connections,
+			                                                             connection,
+			                                                             TRUE,
+			                                                             nm_device_has_carrier (device),
+			                                                             nm_device_get_route_metric (device, AF_INET),
+			                                                             nm_device_get_route_metric (device, AF_INET6),
+			                                                             NULL, NULL));
+		} else
+			matched = connection_checked;
 	} else
 		matched = NULL;
 
+	if (!matched && only_by_uuid) {
+		_LOG2D (LOGD_DEVICE, device, "assume: cannot generate connection: %s",
+		        gen_error->message);
+		return NULL;
+	}
+
 	if (!matched && assume_state_guess_assume) {
 		gs_free NMSettingsConnection **connections = NULL;
 		guint len, i, j;
@@ -2250,8 +2349,8 @@ get_existing_connection (NMManager *self,
 	}
 
 	nm_settings_connection_set_flags (NM_SETTINGS_CONNECTION (added),
-	                                  NM_SETTINGS_CONNECTION_FLAGS_NM_GENERATED |
-	                                  NM_SETTINGS_CONNECTION_FLAGS_VOLATILE,
+	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED |
+	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
 	                                  TRUE);
 	NM_SET_OUT (out_generated, TRUE);
 	return added;
@@ -2315,14 +2414,20 @@ recheck_assume_connection (NMManager *self,
 		GError *error = NULL;
 
 		subject = nm_auth_subject_new_internal ();
-		active = _new_active_connection (self, NM_CONNECTION (connection), NULL, NULL,
-		                                 device, subject,
+		active = _new_active_connection (self,
+		                                 FALSE,
+		                                 NM_CONNECTION (connection),
+		                                 NULL,
+		                                 NULL,
+		                                 device,
+		                                 subject,
 		                                 generated ? NM_ACTIVATION_TYPE_EXTERNAL : NM_ACTIVATION_TYPE_ASSUME,
+		                                 NM_ACTIVATION_REASON_AUTOCONNECT,
 		                                 &error);
 
 		if (!active) {
 			_LOGW (LOGD_DEVICE, "assume: assumed connection %s failed to activate: %s",
-			       nm_connection_get_path (NM_CONNECTION (connection)),
+			       nm_dbus_object_get_path (NM_DBUS_OBJECT (connection)),
 			       error->message);
 			g_error_free (error);
 
@@ -2373,18 +2478,17 @@ device_ip_iface_changed (NMDevice *device,
                          GParamSpec *pspec,
                          NMManager *self)
 {
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	const char *ip_iface = nm_device_get_ip_iface (device);
 	NMDeviceType device_type = nm_device_get_device_type (device);
-	GSList *iter;
+	NMDevice *candidate;
 
 	/* Remove NMDevice objects that are actually child devices of others,
 	 * when the other device finally knows its IP interface name.  For example,
 	 * remove the PPP interface that's a child of a WWAN device, since it's
 	 * not really a standalone NMDevice.
 	 */
-	for (iter = NM_MANAGER_GET_PRIVATE (self)->devices; iter; iter = iter->next) {
-		NMDevice *candidate = NM_DEVICE (iter->data);
-
+	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
 		if (   candidate != device
 		    && g_strcmp0 (nm_device_get_iface (candidate), ip_iface) == 0
 		    && nm_device_get_device_type (candidate) == device_type
@@ -2406,35 +2510,56 @@ device_iface_changed (NMDevice *device,
 	retry_connections_for_parent_device (self, device);
 }
 
+static void
+_emit_device_added_removed (NMManager *self,
+                            NMDevice *device,
+                            gboolean is_added)
+{
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
+	                            &interface_info_manager,
+	                            is_added
+	                              ? &signal_info_device_added
+	                              : &signal_info_device_removed,
+	                            "(o)",
+	                            nm_dbus_object_get_path (NM_DBUS_OBJECT (device)));
+	g_signal_emit (self,
+	               signals[is_added ? DEVICE_ADDED : DEVICE_REMOVED],
+	               0,
+	               device);
+	_notify (self, PROP_DEVICES);
+}
 
 static void
 device_realized (NMDevice *device,
                  GParamSpec *pspec,
                  NMManager *self)
 {
-	gboolean real = nm_device_is_real (device);
-
-	/* Emit D-Bus signals */
-	g_signal_emit (self, signals[real ? DEVICE_ADDED : DEVICE_REMOVED], 0, device);
-	_notify (self, PROP_DEVICES);
+	_emit_device_added_removed (self, device, nm_device_is_real (device));
 }
 
-#if WITH_CONCHECK
 static void
 device_connectivity_changed (NMDevice *device,
-                             GParamSpec *pspec,
                              NMManager *self)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMConnectivityState best_state = NM_CONNECTIVITY_UNKNOWN;
 	NMConnectivityState state;
-	const GSList *devices;
+	NMDevice *dev;
 
-	for (devices = priv->devices; devices; devices = devices->next) {
-		state = nm_device_get_connectivity_state (NM_DEVICE (devices->data));
-		if (state > best_state)
+	best_state = nm_device_get_connectivity_state (device);
+	if (best_state < NM_CONNECTIVITY_FULL) {
+		c_list_for_each_entry (dev, &priv->devices_lst_head, devices_lst) {
+			state = nm_device_get_connectivity_state (dev);
+			if (state <= best_state)
+				continue;
 			best_state = state;
+			if (best_state >= NM_CONNECTIVITY_FULL) {
+				/* it doesn't get better than this. */
+				break;
+			}
+		}
 	}
+	nm_assert (best_state <= NM_CONNECTIVITY_FULL);
 
 	if (best_state != priv->connectivity_state) {
 		priv->connectivity_state = best_state;
@@ -2447,7 +2572,6 @@ device_connectivity_changed (NMDevice *device,
 		nm_dispatcher_call_connectivity (priv->connectivity_state, NULL, NULL, NULL);
 	}
 }
-#endif
 
 static void
 _device_realize_finish (NMManager *self,
@@ -2493,6 +2617,7 @@ add_device (NMManager *self, NMDevice *device, GError **error)
 	GSList *iter, *remove = NULL;
 	int ifindex;
 	const char *dbus_path;
+	NMDevice *candidate;
 
 	/* No duplicates */
 	ifindex = nm_device_get_ifindex (device);
@@ -2509,18 +2634,20 @@ add_device (NMManager *self, NMDevice *device, GError **error)
 	 * FIXME: use parent/child device relationships instead of removing
 	 * the child NMDevice entirely
 	 */
-	for (iter = priv->devices; iter; iter = iter->next) {
-		NMDevice *candidate = iter->data;
-
-		iface = nm_device_get_ip_iface (candidate);
-		if (nm_device_is_real (candidate) && nm_device_owns_iface (device, iface))
+	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
+		if (   nm_device_is_real (candidate)
+		    && (iface = nm_device_get_ip_iface (candidate))
+		    && nm_device_owns_iface (device, iface))
 			remove = g_slist_prepend (remove, candidate);
 	}
 	for (iter = remove; iter; iter = iter->next)
 		remove_device (self, NM_DEVICE (iter->data), FALSE, FALSE);
 	g_slist_free (remove);
 
-	priv->devices = g_slist_append (priv->devices, g_object_ref (device));
+	g_object_ref (device);
+
+	nm_assert (c_list_is_empty (&device->devices_lst));
+	c_list_link_tail (&priv->devices_lst_head, &device->devices_lst);
 
 	g_signal_connect (device, NM_DEVICE_STATE_CHANGED,
 	                  G_CALLBACK (manager_device_state_changed),
@@ -2554,11 +2681,9 @@ add_device (NMManager *self, NMDevice *device, GError **error)
 	                  G_CALLBACK (device_realized),
 	                  self);
 
-#if WITH_CONCHECK
-	g_signal_connect (device, "notify::" NM_DEVICE_CONNECTIVITY,
+	g_signal_connect (device, NM_DEVICE_CONNECTIVITY_CHANGED,
 	                  G_CALLBACK (device_connectivity_changed),
 	                  self);
-#endif
 
 	if (priv->startup) {
 		g_signal_connect (device, "notify::" NM_DEVICE_HAS_PENDING_ACTION,
@@ -2587,7 +2712,7 @@ add_device (NMManager *self, NMDevice *device, GError **error)
 	                               NM_UNMANAGED_SLEEPING,
 	                               manager_sleeping (self));
 
-	dbus_path = nm_exported_object_export (NM_EXPORTED_OBJECT (device));
+	dbus_path = nm_dbus_object_export (NM_DBUS_OBJECT (device));
 	_LOG2I (LOGD_DEVICE, device, "new %s device (%s)", type_desc, dbus_path);
 
 	nm_settings_device_added (priv->settings, device);
@@ -2634,12 +2759,11 @@ factory_component_added_cb (NMDeviceFactory *factory,
                             gpointer user_data)
 {
 	NMManager *self = user_data;
-	GSList *iter;
-
-	g_return_val_if_fail (self, FALSE);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMDevice *device;
 
-	for (iter = NM_MANAGER_GET_PRIVATE (self)->devices; iter; iter = iter->next) {
-		if (nm_device_notify_component_added ((NMDevice *) iter->data, component))
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
+		if (nm_device_notify_component_added (device, component))
 			return TRUE;
 	}
 	return FALSE;
@@ -2669,9 +2793,10 @@ platform_link_added (NMManager *self,
                      gboolean guess_assume,
                      const NMConfigDeviceStateData *dev_state)
 {
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMDeviceFactory *factory;
 	NMDevice *device = NULL;
-	GSList *iter;
+	NMDevice *candidate;
 
 	g_return_if_fail (ifindex > 0);
 
@@ -2679,8 +2804,7 @@ platform_link_added (NMManager *self,
 		return;
 
 	/* Let unrealized devices try to realize themselves with the link */
-	for (iter = NM_MANAGER_GET_PRIVATE (self)->devices; iter; iter = iter->next) {
-		NMDevice *candidate = iter->data;
+	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
 		gboolean compatible = TRUE;
 		gs_free_error GError *error = NULL;
 
@@ -2907,12 +3031,12 @@ rfkill_manager_rfkill_changed_cb (NMRfkillManager *rfkill_mgr,
 	nm_manager_rfkill_update (NM_MANAGER (user_data), rtype);
 }
 
-const GSList *
+const CList *
 nm_manager_get_devices (NMManager *manager)
 {
 	g_return_val_if_fail (NM_IS_MANAGER (manager), NULL);
 
-	return NM_MANAGER_GET_PRIVATE (manager)->devices;
+	return &NM_MANAGER_GET_PRIVATE (manager)->devices_lst_head;
 }
 
 static NMDevice *
@@ -2921,9 +3045,10 @@ nm_manager_get_best_device_for_connection (NMManager *self,
                                            gboolean for_user_request,
                                            GHashTable *unavailable_devices)
 {
-	const GSList *devices, *iter;
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMActiveConnection *ac;
 	NMDevice *act_device;
+	NMDevice *device;
 	NMDeviceCheckConAvailableFlags flags;
 
 	ac = active_connection_find_first_by_connection (self, connection);
@@ -2936,9 +3061,7 @@ nm_manager_get_best_device_for_connection (NMManager *self,
 	flags = for_user_request ? NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST : NM_DEVICE_CHECK_CON_AVAILABLE_NONE;
 
 	/* Pick the first device that's compatible with the connection. */
-	devices = nm_manager_get_devices (self);
-	for (iter = devices; iter; iter = g_slist_next (iter)) {
-		NMDevice *device = NM_DEVICE (iter->data);
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
 
 		if (unavailable_devices && g_hash_table_contains (unavailable_devices, device))
 			continue;
@@ -2951,67 +3074,100 @@ nm_manager_get_best_device_for_connection (NMManager *self,
 	return NULL;
 }
 
-static void
-_get_devices (NMManager *self,
-              GDBusMethodInvocation *context,
-              gboolean all_devices)
+static const char **
+_get_devices_paths (NMManager *self,
+                    gboolean all_devices)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	gs_free const char **paths = NULL;
+	const char **paths = NULL;
 	guint i;
-	GSList *iter;
+	NMDevice *device;
 
-	paths = g_new (const char *, g_slist_length (priv->devices) + 1);
+	paths = g_new (const char *, c_list_length (&priv->devices_lst_head) + 1);
 
-	for (i = 0, iter = priv->devices; iter; iter = iter->next) {
+	i = 0;
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
 		const char *path;
 
-		path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (iter->data));
-		if (   path
-		    && (all_devices || nm_device_is_real (iter->data)))
-			paths[i++] = path;
+		path = nm_dbus_object_get_path (NM_DBUS_OBJECT (device));
+		if (!path)
+			continue;
+
+		if (   !all_devices
+		    && !nm_device_is_real (device))
+			continue;
+
+		paths[i++] = path;
 	}
 	paths[i++] = NULL;
 
-	g_dbus_method_invocation_return_value (context,
-	                                       g_variant_new ("(^ao)", (char **) paths));
+	return paths;
 }
 
 static void
-impl_manager_get_devices (NMManager *self,
-                          GDBusMethodInvocation *context)
-{
-	_get_devices (self, context, FALSE);
+impl_manager_get_devices (NMDBusObject *obj,
+                          const NMDBusInterfaceInfoExtended *interface_info,
+                          const NMDBusMethodInfoExtended *method_info,
+                          GDBusConnection *connection,
+                          const char *sender,
+                          GDBusMethodInvocation *invocation,
+                          GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
+	gs_free const char **paths = NULL;
+
+	paths = _get_devices_paths (self, FALSE);
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new ("(^ao)", (char **) paths));
 }
 
 static void
-impl_manager_get_all_devices (NMManager *self,
-                              GDBusMethodInvocation *context)
-{
-	_get_devices (self, context, TRUE);
+impl_manager_get_all_devices (NMDBusObject *obj,
+                              const NMDBusInterfaceInfoExtended *interface_info,
+                              const NMDBusMethodInfoExtended *method_info,
+                              GDBusConnection *connection,
+                              const char *sender,
+                              GDBusMethodInvocation *invocation,
+                              GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
+	gs_free const char **paths = NULL;
+
+	paths = _get_devices_paths (self, TRUE);
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new ("(^ao)", (char **) paths));
 }
 
 static void
-impl_manager_get_device_by_ip_iface (NMManager *self,
-                                     GDBusMethodInvocation *context,
-                                     const char *iface)
-{
+impl_manager_get_device_by_ip_iface (NMDBusObject *obj,
+                                     const NMDBusInterfaceInfoExtended *interface_info,
+                                     const NMDBusMethodInfoExtended *method_info,
+                                     GDBusConnection *connection,
+                                     const char *sender,
+                                     GDBusMethodInvocation *invocation,
+                                     GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
 	NMDevice *device;
 	const char *path = NULL;
+	const char *iface;
+
+	g_variant_get (parameters, "(&s)", &iface);
 
 	device = find_device_by_ip_iface (self, iface);
 	if (device)
-		path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (device));
+		path = nm_dbus_object_get_path (NM_DBUS_OBJECT (device));
 
-	if (path == NULL) {
-		g_dbus_method_invocation_return_error (context,
+	if (!path) {
+		g_dbus_method_invocation_return_error (invocation,
 		                                       NM_MANAGER_ERROR,
 		                                       NM_MANAGER_ERROR_UNKNOWN_DEVICE,
 		                                       "No device found for the requested iface.");
-	} else {
-		g_dbus_method_invocation_return_value (context,
-		                                       g_variant_new ("(o)", path));
+		return;
 	}
+
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new ("(o)", path));
 }
 
 static gboolean
@@ -3084,7 +3240,6 @@ find_master (NMManager *self,
 	const char *master;
 	NMDevice *master_device = NULL;
 	NMSettingsConnection *master_connection = NULL;
-	GSList *iter;
 
 	s_con = nm_connection_get_setting_connection (connection);
 	g_assert (s_con);
@@ -3113,10 +3268,10 @@ find_master (NMManager *self,
 		/* Try master as a connection UUID */
 		master_connection = nm_settings_get_connection_by_uuid (priv->settings, master);
 		if (master_connection) {
-			/* Check if the master connection is activated on some device already */
-			for (iter = priv->devices; iter; iter = g_slist_next (iter)) {
-				NMDevice *candidate = NM_DEVICE (iter->data);
+			NMDevice *candidate;
 
+			/* Check if the master connection is activated on some device already */
+			c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
 				if (candidate == device)
 					continue;
 
@@ -3154,6 +3309,7 @@ find_master (NMManager *self,
  * @device: the #NMDevice, if any, which will activate @connection
  * @master_connection: the master connection, or %NULL
  * @master_device: the master device, or %NULL
+ * @activation_reason: the reason for activation
  * @error: the error, if an error occurred
  *
  * Determines whether a given #NMConnection depends on another connection to
@@ -3181,12 +3337,12 @@ ensure_master_active_connection (NMManager *self,
                                  NMDevice *device,
                                  NMSettingsConnection *master_connection,
                                  NMDevice *master_device,
+                                 NMActivationReason activation_reason,
                                  GError **error)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMActiveConnection *master_ac = NULL;
 	NMDeviceState master_state;
-	GSList *iter;
 
 	g_assert (connection);
 	g_assert (master_connection || master_device);
@@ -3244,6 +3400,7 @@ ensure_master_active_connection (NMManager *self,
 					                                            master_device,
 					                                            subject,
 					                                            NM_ACTIVATION_TYPE_MANAGED,
+					                                            activation_reason,
 					                                            error);
 					return master_ac;
 				}
@@ -3262,12 +3419,10 @@ ensure_master_active_connection (NMManager *self,
 		             NM_MANAGER_ERROR_DEPENDENCY_FAILED,
 		             "Device unmanaged or not available for activation");
 	} else if (master_connection) {
-		gboolean found_device = FALSE;
+		NMDevice *candidate;
 
 		/* Find a compatible device and activate it using this connection */
-		for (iter = priv->devices; iter; iter = g_slist_next (iter)) {
-			NMDevice *candidate = NM_DEVICE (iter->data);
-
+		c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
 			if (candidate == device) {
 				/* A device obviously can't be its own master */
 				continue;
@@ -3276,7 +3431,6 @@ ensure_master_active_connection (NMManager *self,
 			if (!nm_device_check_connection_available (candidate, NM_CONNECTION (master_connection), NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, NULL))
 				continue;
 
-			found_device = TRUE;
 			if (!nm_device_is_software (candidate)) {
 				master_state = nm_device_get_state (candidate);
 				if (nm_device_is_real (candidate) && master_state != NM_DEVICE_STATE_DISCONNECTED)
@@ -3290,6 +3444,7 @@ ensure_master_active_connection (NMManager *self,
 			                                            candidate,
 			                                            subject,
 			                                            NM_ACTIVATION_TYPE_MANAGED,
+			                                            activation_reason,
 			                                            error);
 			return master_ac;
 		}
@@ -3341,7 +3496,7 @@ find_slaves (NMManager *manager,
 	s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
 	g_return_val_if_fail (s_con, NULL);
 
-	devices = g_hash_table_new (g_direct_hash, g_direct_equal);
+	devices = g_hash_table_new (nm_direct_hash, NULL);
 
 	/* Search through all connections, not only inactive ones, because
 	 * even if a slave was already active, it might be deactivated during
@@ -3512,6 +3667,7 @@ autoconnect_slaves (NMManager *self,
 			                                slave->device,
 			                                subject,
 			                                NM_ACTIVATION_TYPE_MANAGED,
+			                                NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES,
 			                                &local_err);
 			if (local_err) {
 				_LOGW (LOGD_CORE, "Slave connection activation failed: %s", local_err->message);
@@ -3526,11 +3682,11 @@ _internal_activate_vpn (NMManager *self, NMActiveConnection *active, GError **er
 {
 	nm_assert (NM_IS_VPN_CONNECTION (active));
 
-	nm_exported_object_export (NM_EXPORTED_OBJECT (active));
+	nm_dbus_object_export (NM_DBUS_OBJECT (active));
 	if (!nm_vpn_manager_activate_connection (NM_MANAGER_GET_PRIVATE (self)->vpn_manager,
 	                                         NM_VPN_CONNECTION (active),
 	                                         error)) {
-		nm_exported_object_unexport (NM_EXPORTED_OBJECT (active));
+		nm_dbus_object_unexport (NM_DBUS_OBJECT (active));
 		return FALSE;
 	}
 
@@ -3625,7 +3781,6 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 	NMConnection *existing_connection = NULL;
 	NMActiveConnection *master_ac = NULL;
 	NMAuthSubject *subject;
-	char *error_desc = NULL;
 
 	g_return_val_if_fail (NM_IS_MANAGER (self), FALSE);
 	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (active), FALSE);
@@ -3633,14 +3788,14 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 
 	g_assert (NM_IS_VPN_CONNECTION (active) == FALSE);
 
+	device = nm_active_connection_get_device (active);
+	g_return_val_if_fail (device != NULL, FALSE);
+
 	connection = nm_active_connection_get_settings_connection (active);
-	g_assert (connection);
+	nm_assert (connection);
 
 	applied = nm_active_connection_get_applied_connection (active);
 
-	device = nm_active_connection_get_device (active);
-	g_return_val_if_fail (device != NULL, FALSE);
-
 	/* If the device is active and its connection is not visible to the
 	 * user that's requesting this new activation, fail, since other users
 	 * should not be allowed to implicitly deactivate private connections
@@ -3648,16 +3803,13 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 	 */
 	existing_connection = nm_device_get_applied_connection (device);
 	subject = nm_active_connection_get_subject (active);
-	if (existing_connection &&
-	    !nm_auth_is_subject_in_acl (existing_connection,
-	                                subject,
-	                                &error_desc)) {
-		g_set_error (error,
-		             NM_MANAGER_ERROR,
-		             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		             "Private connection already active on the device: %s",
-		             error_desc);
-		g_free (error_desc);
+	if (   existing_connection
+	    && !nm_auth_is_subject_in_acl_set_error (existing_connection,
+	                                             subject,
+	                                             NM_MANAGER_ERROR,
+	                                             NM_MANAGER_ERROR_PERMISSION_DENIED,
+	                                             error)) {
+		g_prefix_error (error, "Private connection already active on the device: ");
 		return FALSE;
 	}
 
@@ -3669,6 +3821,9 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 		return FALSE;
 	}
 
+	if (nm_active_connection_get_activation_type (active) == NM_ACTIVATION_TYPE_MANAGED)
+		nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
+
 	/* Create any backing resources the device needs */
 	if (!nm_device_is_real (device)) {
 		NMDevice *parent;
@@ -3686,7 +3841,10 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 			}
 
 			parent_ac = nm_manager_activate_connection (self, parent_con, NULL, NULL, parent,
-			                                            subject, NM_ACTIVATION_TYPE_MANAGED, error);
+			                                            subject,
+			                                            NM_ACTIVATION_TYPE_MANAGED,
+			                                            nm_active_connection_get_activation_reason (active),
+			                                            error);
 			if (!parent_ac) {
 				g_prefix_error (error, "%s failed to activate parent: ", nm_device_get_iface (device));
 				return FALSE;
@@ -3747,6 +3905,7 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 			                                             device,
 			                                             master_connection,
 			                                             master_device,
+			                                             nm_active_connection_get_activation_reason (active),
 			                                             error);
 			if (!master_ac) {
 				if (master_device) {
@@ -3773,7 +3932,7 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 		_LOGD (LOGD_CORE, "Activation of '%s' depends on active connection %p %s",
 		       nm_settings_connection_get_id (connection),
 		       master_ac,
-		       nm_exported_object_get_path (NM_EXPORTED_OBJECT  (master_ac)) ?: "");
+		       nm_dbus_object_get_path (NM_DBUS_OBJECT  (master_ac)) ?: "");
 	}
 
 	/* Check slaves for master connection and possibly activate them */
@@ -3845,101 +4004,95 @@ _internal_activate_generic (NMManager *self, NMActiveConnection *active, GError
 }
 
 static NMActiveConnection *
-_new_vpn_active_connection (NMManager *self,
-                            NMSettingsConnection *settings_connection,
-                            const char *specific_object,
-                            NMAuthSubject *subject,
-                            GError **error)
-{
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	NMActiveConnection *parent = NULL;
-	NMDevice *device = NULL;
-
-	g_return_val_if_fail (!settings_connection || NM_IS_SETTINGS_CONNECTION (settings_connection), NULL);
-
-	if (specific_object) {
-		/* Find the specific connection the client requested we use */
-		parent = active_connection_get_by_path (self, specific_object);
-		if (!parent) {
-			g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE,
-			                     "Base connection for VPN connection not active.");
-			return NULL;
-		}
-	} else
-		parent = priv->primary_connection;
-
-	if (!parent) {
-		g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
-		                     "Could not find source connection.");
-		return NULL;
-	}
-
-	device = nm_active_connection_get_device (parent);
-	if (!device) {
-		g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
-		                     "Source connection had no active device.");
-		return NULL;
-	}
-
-	return (NMActiveConnection *) nm_vpn_connection_new (settings_connection,
-	                                                     device,
-	                                                     nm_exported_object_get_path (NM_EXPORTED_OBJECT (parent)),
-	                                                     subject);
-}
-
-static NMActiveConnection *
 _new_active_connection (NMManager *self,
+                        gboolean is_vpn,
                         NMConnection *connection,
                         NMConnection *applied,
                         const char *specific_object,
                         NMDevice *device,
                         NMAuthSubject *subject,
                         NMActivationType activation_type,
+                        NMActivationReason activation_reason,
                         GError **error)
 {
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMSettingsConnection *settings_connection = NULL;
-	NMActiveConnection *existing_ac;
-	gboolean is_vpn;
+	NMDevice *parent_device;
 
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
 	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL);
 
-	/* Can't create new AC for already-active connection */
-	existing_ac = active_connection_find_first_by_connection (self, connection);
-	if (NM_IS_VPN_CONNECTION (existing_ac)) {
-		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE,
-		             "Connection '%s' is already active",
-		             nm_connection_get_id (connection));
-		return NULL;
-	}
-
-	/* Normalize the specific object */
-	if (specific_object && g_strcmp0 (specific_object, "/") == 0)
-		specific_object = NULL;
-
-	is_vpn = nm_connection_is_type (NM_CONNECTION (connection), NM_SETTING_VPN_SETTING_NAME);
+	nm_assert (is_vpn == _connection_is_vpn (connection));
+	nm_assert (is_vpn || NM_IS_DEVICE (device));
+	nm_assert (!nm_streq0 (specific_object, "/"));
 
 	if (NM_IS_SETTINGS_CONNECTION (connection))
 		settings_connection = (NMSettingsConnection *) connection;
 
 	if (is_vpn) {
+		NMActiveConnection *parent;
+
+		/* FIXME: for VPN connections, we don't allow re-activating an
+		 * already active connection. It's a bug, and should be fixed together
+		 * when reworking VPN handling. */
+		if (active_connection_find_first_by_connection (self, connection)) {
+			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE,
+			             "Connection '%s' is already active",
+			             nm_connection_get_id (connection));
+			return NULL;
+		}
+
+		/* FIXME: apparently, activation here only works if @connection is
+		 * a settings-connection. Which is not the case during AddAndActivatate.
+		 * Probably, AddAndActivate is broken for VPN. */
 		if (activation_type != NM_ACTIVATION_TYPE_MANAGED)
 			g_return_val_if_reached (NULL);
-		return _new_vpn_active_connection (self,
-		                                   settings_connection,
-		                                   specific_object,
-		                                   subject,
-		                                   error);
-	}
 
-	if (device && (activation_type == NM_ACTIVATION_TYPE_MANAGED))
-		nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
+		g_return_val_if_fail (!settings_connection || NM_IS_SETTINGS_CONNECTION (settings_connection), NULL);
+
+		if (specific_object) {
+			/* Find the specific connection the client requested we use */
+			parent = active_connection_get_by_path (self, specific_object);
+			if (!parent) {
+				g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE,
+				                     "Base connection for VPN connection not active.");
+				return NULL;
+			}
+		} else
+			parent = priv->primary_connection;
+
+		if (!parent) {
+			g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
+			                     "Could not find source connection.");
+			return NULL;
+		}
+
+		parent_device = nm_active_connection_get_device (parent);
+		if (!parent_device) {
+			g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+			                     "Source connection had no active device");
+			return NULL;
+		}
+
+		if (device && device != parent_device) {
+			g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+			                     "The device doesn't match the active connection.");
+			return NULL;
+		}
+
+		return (NMActiveConnection *) nm_vpn_connection_new (settings_connection,
+		                                                     parent_device,
+		                                                     nm_dbus_object_get_path (NM_DBUS_OBJECT (parent)),
+		                                                     activation_reason,
+		                                                     subject);
+	}
 
 	return (NMActiveConnection *) nm_act_request_new (settings_connection,
 	                                                  applied,
 	                                                  specific_object,
 	                                                  subject,
 	                                                  activation_type,
+	                                                  activation_reason,
 	                                                  device);
 }
 
@@ -3958,13 +4111,17 @@ _internal_activation_auth_done (NMActiveConnection *active,
 
 	priv->authorizing_connections = g_slist_remove (priv->authorizing_connections, active);
 
-	/* Don't continue with the activation if an equivalent active connection
-	 * already exists.  We also check this earlier, but there we may fail to
+	if (!success)
+		goto fail;
+
+	/* Don't continue with an internal activation if an equivalent active
+	 * connection already exists. Note that slave autoconnections always force a
+	 * reconnection.  We also check this earlier, but there we may fail to
 	 * detect a duplicate if the existing active connection is undergoing
 	 * authorization in impl_manager_activate_connection().
 	 */
-	if (   success
-	    && nm_auth_subject_is_internal (nm_active_connection_get_subject (active))) {
+	if (   nm_auth_subject_is_internal (nm_active_connection_get_subject (active))
+	    && nm_active_connection_get_activation_reason (active) != NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES) {
 		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)
@@ -3976,17 +4133,15 @@ _internal_activation_auth_done (NMActiveConnection *active,
 				             NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE,
 				             "Connection '%s' is already active",
 				             nm_active_connection_get_settings_connection_id (active));
-				success = FALSE;
-				break;
+				goto fail;
 			}
 		}
 	}
 
-	if (success) {
-		if (_internal_activate_generic (self, active, &error))
-			return;
-	}
+	if (_internal_activate_generic (self, active, &error))
+		return;
 
+fail:
 	nm_assert (error_desc || error);
 	nm_active_connection_set_state_fail (active,
 	                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
@@ -4003,6 +4158,7 @@ _internal_activation_auth_done (NMActiveConnection *active,
  * @subject: the subject which requested activation
  * @activation_type: whether to assume the connection. That is, take over gracefully,
  *   non-destructible.
+ * @activation_reason: the reason for activation
  * @error: return location for an error
  *
  * Begins a new internally-initiated activation of @connection on @device.
@@ -4023,29 +4179,27 @@ nm_manager_activate_connection (NMManager *self,
                                 NMDevice *device,
                                 NMAuthSubject *subject,
                                 NMActivationType activation_type,
+                                NMActivationReason activation_reason,
                                 GError **error)
 {
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMManagerPrivate *priv;
 	NMActiveConnection *active;
-	char *error_desc = NULL;
 	GSList *iter;
 
-	g_return_val_if_fail (self != NULL, NULL);
-	g_return_val_if_fail (connection != NULL, NULL);
-	g_return_val_if_fail (error != NULL, NULL);
-	g_return_val_if_fail (*error == NULL, NULL);
+	g_return_val_if_fail (NM_IS_MANAGER (self), NULL);
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (connection), NULL);
+	g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+	nm_assert (!nm_streq0 (specific_object, "/"));
 
-	/* Ensure the subject has permissions for this connection */
-	if (!nm_auth_is_subject_in_acl (NM_CONNECTION (connection),
-	                                subject,
-	                                &error_desc)) {
-		g_set_error_literal (error,
-		                     NM_MANAGER_ERROR,
-		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                     error_desc);
-		g_free (error_desc);
+	priv = NM_MANAGER_GET_PRIVATE (self);
+
+	if (!nm_auth_is_subject_in_acl_set_error (NM_CONNECTION (connection),
+	                                          subject,
+	                                          NM_MANAGER_ERROR,
+	                                          NM_MANAGER_ERROR_PERMISSION_DENIED,
+	                                          error))
 		return NULL;
-	}
 
 	/* Look for a active connection that's equivalent and is already pending authorization
 	 * and eventual activation. This is used to de-duplicate concurrent activations which would
@@ -4056,25 +4210,29 @@ nm_manager_activate_connection (NMManager *self,
 		active = iter->data;
 
 		if (   connection == nm_active_connection_get_settings_connection (active)
-		    && g_strcmp0 (nm_active_connection_get_specific_object (active), specific_object) == 0
+		    && nm_streq0 (nm_active_connection_get_specific_object (active), specific_object)
 		    && nm_active_connection_get_device (active) == device
 		    && nm_auth_subject_is_internal (nm_active_connection_get_subject (active))
-		    && nm_auth_subject_is_internal (subject))
+		    && nm_auth_subject_is_internal (subject)
+		    && nm_active_connection_get_activation_reason (active) == activation_reason)
 			return active;
 	}
 
 	active = _new_active_connection (self,
+	                                 _connection_is_vpn (NM_CONNECTION (connection)),
 	                                 NM_CONNECTION (connection),
 	                                 applied,
 	                                 specific_object,
 	                                 device,
 	                                 subject,
 	                                 activation_type,
+	                                 activation_reason,
 	                                 error);
-	if (active) {
-		priv->authorizing_connections = g_slist_prepend (priv->authorizing_connections, active);
-		nm_active_connection_authorize (active, NULL, _internal_activation_auth_done, self, NULL);
-	}
+	if (!active)
+		return NULL;
+
+	priv->authorizing_connections = g_slist_prepend (priv->authorizing_connections, active);
+	nm_active_connection_authorize (active, NULL, _internal_activation_auth_done, self, NULL);
 	return active;
 }
 
@@ -4083,9 +4241,12 @@ nm_manager_activate_connection (NMManager *self,
  * @self: the #NMManager
  * @context: the D-Bus context of the requestor
  * @connection: the partial or complete #NMConnection to be activated
- * @device_path: the object path of the device to be activated, or "/"
+ * @device_path: the object path of the device to be activated, or NULL
  * @out_device: on successful reutrn, the #NMDevice to be activated with @connection
- * @out_vpn: on successful return, %TRUE if @connection is a VPN connection
+ *   The caller may pass in a device which shortcuts the lookup by path.
+ *   In this case, the passed in device must have the matching @device_path
+ *   already.
+ * @out_is_vpn: on successful return, %TRUE if @connection is a VPN connection
  * @error: location to store an error on failure
  *
  * Performs basic validation on an activation request, including ensuring that
@@ -4101,17 +4262,16 @@ validate_activation_request (NMManager *self,
                              NMConnection *connection,
                              const char *device_path,
                              NMDevice **out_device,
-                             gboolean *out_vpn,
+                             gboolean *out_is_vpn,
                              GError **error)
 {
 	NMDevice *device = NULL;
-	gboolean vpn = FALSE;
-	NMAuthSubject *subject = NULL;
-	char *error_desc = NULL;
+	gboolean is_vpn = FALSE;
+	gs_unref_object NMAuthSubject *subject = NULL;
 
-	g_assert (connection);
-	g_assert (out_device);
-	g_assert (out_vpn);
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (out_device);
+	nm_assert (out_is_vpn);
 
 	/* Validate the caller */
 	subject = nm_auth_subject_new_unix_process_from_context (context);
@@ -4123,76 +4283,80 @@ validate_activation_request (NMManager *self,
 		return NULL;
 	}
 
-	/* Ensure the subject has permissions for this connection */
-	if (!nm_auth_is_subject_in_acl (connection,
-	                                subject,
-	                                &error_desc)) {
-		g_set_error_literal (error,
-		                     NM_MANAGER_ERROR,
-		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                     error_desc);
-		g_free (error_desc);
-		goto error;
-	}
-
-	/* Check whether it's a VPN or not */
-	if (   nm_connection_get_setting_vpn (connection)
-	    || nm_connection_is_type (connection, NM_SETTING_VPN_SETTING_NAME))
-		vpn = TRUE;
+	if (!nm_auth_is_subject_in_acl_set_error (connection,
+	                                          subject,
+	                                          NM_MANAGER_ERROR,
+	                                          NM_MANAGER_ERROR_PERMISSION_DENIED,
+	                                          error))
+		return NULL;
 
-	/* Normalize device path */
-	if (device_path && g_strcmp0 (device_path, "/") == 0)
-		device_path = NULL;
+	is_vpn = _connection_is_vpn (connection);
 
-	/* And validate it */
-	if (device_path) {
+	if (*out_device) {
+		device = *out_device;
+		nm_assert (NM_IS_DEVICE (device));
+		nm_assert (device_path);
+		nm_assert (nm_streq0 (device_path, nm_dbus_object_get_path (NM_DBUS_OBJECT (device))));
+		nm_assert (device == nm_manager_get_device_by_path (self, device_path));
+	} else if (device_path) {
 		device = nm_manager_get_device_by_path (self, device_path);
 		if (!device) {
 			g_set_error_literal (error,
 			                     NM_MANAGER_ERROR,
 			                     NM_MANAGER_ERROR_UNKNOWN_DEVICE,
 			                     "Device not found");
-			goto error;
+			return NULL;
 		}
-	} else
+	} else if (!is_vpn) {
 		device = nm_manager_get_best_device_for_connection (self, connection, TRUE, NULL);
+		if (!device) {
+			gs_free char *iface = NULL;
+
+			/* VPN and software-device connections don't need a device yet,
+			 * but non-virtual connections do ... */
+			if (!nm_connection_is_virtual (connection)) {
+				g_set_error_literal (error,
+				                     NM_MANAGER_ERROR,
+				                     NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+				                     "No suitable device found for this connection.");
+				return NULL;
+			}
 
-	if (!device && !vpn) {
-		gs_free char *iface = NULL;
+			/* Look for an existing device with the connection's interface name */
+			iface = nm_manager_get_connection_iface (self, connection, NULL, error);
+			if (!iface)
+				return NULL;
 
-		/* VPN and software-device connections don't need a device yet,
-		 * but non-virtual connections do ... */
-		if (!nm_connection_is_virtual (connection)) {
-			g_set_error_literal (error,
-			                     NM_MANAGER_ERROR,
-			                     NM_MANAGER_ERROR_UNKNOWN_DEVICE,
-			                     "No suitable device found for this connection.");
-			goto error;
+			device = find_device_by_iface (self, iface, connection, NULL);
+			if (!device) {
+				g_set_error_literal (error,
+				                     NM_MANAGER_ERROR,
+				                     NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+				                     "Failed to find a compatible device for this connection");
+				return NULL;
+			}
 		}
-
-		/* Look for an existing device with the connection's interface name */
-		iface = nm_manager_get_connection_iface (self, connection, NULL, error);
-		if (!iface)
-			goto error;
-
-		device = find_device_by_iface (self, iface, connection, NULL);
 	}
 
-	if ((!vpn || device_path) && !device) {
+	if (is_vpn && device) {
+		/* VPN's are treated specially. Maybe the should accept a device as well,
+		 * however, later on during activation, we don't handle the device.
+		 *
+		 * Maybe we should, and maybe it makes sense to specify a device
+		 * when activating a VPN. But for now, just error out.  */
 		g_set_error_literal (error,
 		                     NM_MANAGER_ERROR,
 		                     NM_MANAGER_ERROR_UNKNOWN_DEVICE,
-		                     "Failed to find a compatible device for this connection");
-		goto error;
+		                     "Cannot specify device when activating VPN");
+		return NULL;
 	}
 
-	*out_device = device;
-	*out_vpn = vpn;
-	return subject;
+	nm_assert (   ( is_vpn && !device)
+	           || (!is_vpn && NM_IS_DEVICE (device)));
 
-error:
-	g_object_unref (subject);
-	return NULL;
+	*out_device = device;
+	*out_is_vpn = is_vpn;
+	return g_steal_pointer (&subject);
 }
 
 /*****************************************************************************/
@@ -4214,24 +4378,27 @@ _activation_auth_done (NMActiveConnection *active,
 	subject = nm_active_connection_get_subject (active);
 	connection = nm_active_connection_get_settings_connection (active);
 
-	if (success) {
-		if (_internal_activate_generic (self, active, &error)) {
-			nm_settings_connection_autoconnect_blocked_reason_set (connection,
-			                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
-			                                                       FALSE);
-			g_dbus_method_invocation_return_value (context,
-			                                       g_variant_new ("(o)",
-			                                       nm_exported_object_get_path (NM_EXPORTED_OBJECT (active))));
-			nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, TRUE, NULL,
-			                            subject, NULL);
-			return;
-		}
-	} else {
+	if (!success) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
 		                             error_desc);
+		goto fail;
 	}
 
+	if (!_internal_activate_generic (self, active, &error))
+		goto fail;
+
+	nm_settings_connection_autoconnect_blocked_reason_set (connection,
+	                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
+	                                                       FALSE);
+	g_dbus_method_invocation_return_value (context,
+	                                       g_variant_new ("(o)",
+	                                       nm_dbus_object_get_path (NM_DBUS_OBJECT (active))));
+	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, TRUE, NULL,
+	                            subject, NULL);
+	return;
+
+fail:
 	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, FALSE, NULL,
 	                            subject, error->message);
 	nm_active_connection_set_state_fail (active,
@@ -4242,12 +4409,15 @@ _activation_auth_done (NMActiveConnection *active,
 }
 
 static void
-impl_manager_activate_connection (NMManager *self,
-                                  GDBusMethodInvocation *context,
-                                  const char *connection_path,
-                                  const char *device_path,
-                                  const char *specific_object_path)
-{
+impl_manager_activate_connection (NMDBusObject *obj,
+                                  const NMDBusInterfaceInfoExtended *interface_info,
+                                  const NMDBusMethodInfoExtended *method_info,
+                                  GDBusConnection *dbus_connection,
+                                  const char *sender,
+                                  GDBusMethodInvocation *invocation,
+                                  GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	gs_unref_object NMActiveConnection *active = NULL;
 	gs_unref_object NMAuthSubject *subject = NULL;
@@ -4255,14 +4425,15 @@ impl_manager_activate_connection (NMManager *self,
 	NMDevice *device = NULL;
 	gboolean is_vpn = FALSE;
 	GError *error = NULL;
+	const char *connection_path;
+	const char *device_path;
+	const char *specific_object_path;
+
+	g_variant_get (parameters, "(&o&o&o)", &connection_path, &device_path, &specific_object_path);
 
-	/* Normalize object paths */
-	if (g_strcmp0 (connection_path, "/") == 0)
-		connection_path = NULL;
-	if (g_strcmp0 (specific_object_path, "/") == 0)
-		specific_object_path = NULL;
-	if (g_strcmp0 (device_path, "/") == 0)
-		device_path = NULL;
+	connection_path = nm_utils_dbus_normalize_object_path (connection_path);
+	specific_object_path = nm_utils_dbus_normalize_object_path (specific_object_path);
+	device_path = nm_utils_dbus_normalize_object_path (device_path);
 
 	/* If the connection path is given and valid, that connection is activated.
 	 * Otherwise the "best" connection for the device is chosen and activated,
@@ -4297,7 +4468,7 @@ impl_manager_activate_connection (NMManager *self,
 	}
 
 	subject = validate_activation_request (self,
-	                                       context,
+	                                       invocation,
 	                                       NM_CONNECTION (connection),
 	                                       device_path,
 	                                       &device,
@@ -4307,12 +4478,14 @@ impl_manager_activate_connection (NMManager *self,
 		goto error;
 
 	active = _new_active_connection (self,
+	                                 is_vpn,
 	                                 NM_CONNECTION (connection),
 	                                 NULL,
 	                                 specific_object_path,
 	                                 device,
 	                                 subject,
 	                                 NM_ACTIVATION_TYPE_MANAGED,
+	                                 NM_ACTIVATION_REASON_USER_REQUEST,
 	                                 &error);
 	if (!active)
 		goto error;
@@ -4324,7 +4497,7 @@ impl_manager_activate_connection (NMManager *self,
 	                                NULL,
 	                                _activation_auth_done,
 	                                self,
-	                                context);
+	                                invocation);
 	return;
 
 error:
@@ -4332,7 +4505,7 @@ error:
 		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, FALSE, NULL,
 		                            subject, error->message);
 	}
-	g_dbus_method_invocation_take_error (context, error);
+	g_dbus_method_invocation_take_error (invocation, error);
 }
 
 /*****************************************************************************/
@@ -4353,7 +4526,7 @@ activation_add_done (NMSettings *settings,
 	AddAndActivateInfo *info = user_data;
 	NMManager *self;
 	gs_unref_object NMActiveConnection *active = NULL;
-	GError *local = NULL;
+	gs_free_error GError *local = NULL;
 
 	self = info->manager;
 	active = info->active;
@@ -4372,8 +4545,8 @@ activation_add_done (NMSettings *settings,
 			g_dbus_method_invocation_return_value (
 			    context,
 			    g_variant_new ("(oo)",
-			                   nm_connection_get_path (NM_CONNECTION (new_connection)),
-			                   nm_exported_object_get_path (NM_EXPORTED_OBJECT (active))));
+			                   nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)),
+			                   nm_dbus_object_get_path (NM_DBUS_OBJECT (active))));
 			nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE,
 			                            nm_active_connection_get_settings_connection (active),
 			                            TRUE,
@@ -4386,6 +4559,7 @@ activation_add_done (NMSettings *settings,
 	}
 
 	nm_assert (error);
+
 	nm_active_connection_set_state_fail (active,
 	                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
 	                                     error->message);
@@ -4398,7 +4572,6 @@ activation_add_done (NMSettings *settings,
 	                            NULL,
 	                            nm_active_connection_get_subject (active),
 	                            error->message);
-	g_clear_error (&local);
 }
 
 static void
@@ -4413,27 +4586,12 @@ _add_and_activate_auth_done (NMActiveConnection *active,
 	GDBusMethodInvocation *context = user_data2;
 	AddAndActivateInfo *info;
 	GError *error = NULL;
+	gs_unref_object NMConnection *connection = NULL;
 
-	if (success) {
-		NMConnection *connection;
-
-		connection = g_object_steal_qdata (G_OBJECT (active),
-		                                   active_connection_add_and_activate_quark ());
-
-		info = g_slice_new (AddAndActivateInfo);
-		info->manager = self;
-		info->active = g_object_ref (active);
+	connection = g_object_steal_qdata (G_OBJECT (active),
+	                                   active_connection_add_and_activate_quark ());
 
-		/* Basic sender auth checks performed; try to add the connection */
-		nm_settings_add_connection_dbus (priv->settings,
-		                                 connection,
-		                                 FALSE,
-		                                 context,
-		                                 activation_add_done,
-		                                 info);
-		g_object_unref (connection);
-	} else {
-		g_assert (error_desc);
+	if (!success) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
 		                             error_desc);
@@ -4444,32 +4602,50 @@ _add_and_activate_auth_done (NMActiveConnection *active,
 		                            nm_active_connection_get_subject (active),
 		                            error->message);
 		g_dbus_method_invocation_take_error (context, error);
+		g_object_unref (active);
+		return;
 	}
 
-	g_object_unref (active);
+	info = g_slice_new (AddAndActivateInfo);
+	info->manager = self;
+
+	/* we pass on the reference to @active. */
+	info->active = active;
+
+	/* Basic sender auth checks performed; try to add the connection */
+	nm_settings_add_connection_dbus (priv->settings,
+	                                 connection,
+	                                 FALSE,
+	                                 context,
+	                                 activation_add_done,
+	                                 info);
 }
 
 static void
-impl_manager_add_and_activate_connection (NMManager *self,
-                                          GDBusMethodInvocation *context,
-                                          GVariant *settings,
-                                          const char *device_path,
-                                          const char *specific_object_path)
-{
+impl_manager_add_and_activate_connection (NMDBusObject *obj,
+                                          const NMDBusInterfaceInfoExtended *interface_info,
+                                          const NMDBusMethodInfoExtended *method_info,
+                                          GDBusConnection *dbus_connection,
+                                          const char *sender,
+                                          GDBusMethodInvocation *invocation,
+                                          GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	NMConnection *connection = NULL;
-	GSList *all_connections = NULL;
+	gs_unref_object NMConnection *connection = NULL;
 	NMActiveConnection *active = NULL;
-	NMAuthSubject *subject = NULL;
+	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
 	NMDevice *device = NULL;
-	gboolean vpn = FALSE;
+	gboolean is_vpn = FALSE;
+	gs_unref_variant GVariant *settings = NULL;
+	const char *device_path;
+	const char *specific_object_path;
 
-	/* Normalize object paths */
-	if (g_strcmp0 (specific_object_path, "/") == 0)
-		specific_object_path = NULL;
-	if (g_strcmp0 (device_path, "/") == 0)
-		device_path = NULL;
+	g_variant_get (parameters, "(@a{sa{sv}}&o&o)", &settings, &device_path, &specific_object_path);
+
+	specific_object_path = nm_utils_dbus_normalize_object_path (specific_object_path);
+	device_path = nm_utils_dbus_normalize_object_path (device_path);
 
 	/* Try to create a new connection with the given settings.
 	 * We allow empty settings for AddAndActivateConnection(). In that case,
@@ -4483,29 +4659,16 @@ impl_manager_add_and_activate_connection (NMManager *self,
 		_nm_connection_replace_settings (connection, settings, NM_SETTING_PARSE_FLAGS_STRICT, NULL);
 
 	subject = validate_activation_request (self,
-	                                       context,
+	                                       invocation,
 	                                       connection,
 	                                       device_path,
 	                                       &device,
-	                                       &vpn,
+	                                       &is_vpn,
 	                                       &error);
 	if (!subject)
 		goto error;
 
-	{
-		gs_free NMSettingsConnection **connections = NULL;
-		guint i, len;
-
-		connections = nm_settings_get_connections_clone (priv->settings, &len,
-		                                                 NULL, NULL,
-		                                                 nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
-		all_connections = NULL;
-		for (i = len; i > 0; ) {
-			i--;
-			all_connections = g_slist_prepend (all_connections, connections[i]);
-		}
-	}
-	if (vpn) {
+	if (is_vpn) {
 		/* Try to fill the VPN's connection setting and name at least */
 		if (!nm_connection_get_setting_vpn (connection)) {
 			error = g_error_new_literal (NM_CONNECTION_ERROR,
@@ -4518,7 +4681,7 @@ impl_manager_add_and_activate_connection (NMManager *self,
 		nm_utils_complete_generic (priv->platform,
 		                           connection,
 		                           NM_SETTING_VPN_SETTING_NAME,
-		                           all_connections,
+		                           (NMConnection *const*) nm_settings_get_connections (priv->settings, NULL),
 		                           NULL,
 		                           _("VPN connection"),
 		                           NULL,
@@ -4528,42 +4691,44 @@ impl_manager_add_and_activate_connection (NMManager *self,
 		if (!nm_device_complete_connection (device,
 		                                    connection,
 		                                    specific_object_path,
-		                                    all_connections,
+		                                    (NMConnection *const*) nm_settings_get_connections (priv->settings, NULL),
 		                                    &error))
 			goto error;
 	}
-	g_slist_free (all_connections);
-	all_connections = NULL;
 
 	active = _new_active_connection (self,
+	                                 is_vpn,
 	                                 connection,
 	                                 NULL,
 	                                 specific_object_path,
 	                                 device,
 	                                 subject,
 	                                 NM_ACTIVATION_TYPE_MANAGED,
+	                                 NM_ACTIVATION_REASON_USER_REQUEST,
 	                                 &error);
 	if (!active)
 		goto error;
 
+	/* FIXME: nm_active_connection_authorize() already has two user-data pointers
+	 *        to piggyback additional data. Instead of attaching the third argument to
+	 *        @active's user-data, add a third paramter.
+	 *        Or alternatively, allocate a data structure to pass on additional data.
+	 *        Then we don't need two user-data pointers. */
 	g_object_set_qdata_full (G_OBJECT (active),
 	                         active_connection_add_and_activate_quark (),
 	                         connection,
 	                         g_object_unref);
 
-	nm_active_connection_authorize (active, connection, _add_and_activate_auth_done, self, context);
-	g_object_unref (subject);
+	nm_active_connection_authorize (active, connection, _add_and_activate_auth_done, self, invocation);
+
+	/* we passed the pointers on to the callback of authorize. */
+	g_steal_pointer (&connection);
+	g_steal_pointer (&active);
 	return;
 
 error:
 	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE, NULL, FALSE, NULL, subject, error->message);
-	g_clear_object (&connection);
-	g_slist_free (all_connections);
-	g_clear_object (&subject);
-	g_clear_object (&active);
-
-	g_assert (error);
-	g_dbus_method_invocation_take_error (context, error);
+	g_dbus_method_invocation_take_error (invocation, error);
 }
 
 /*****************************************************************************/
@@ -4659,21 +4824,28 @@ deactivate_net_auth_done_cb (NMAuthChain *chain,
 	else
 		g_dbus_method_invocation_return_value (context, NULL);
 
-	nm_auth_chain_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 
 static void
-impl_manager_deactivate_connection (NMManager *self,
-                                    GDBusMethodInvocation *context,
-                                    const char *active_path)
-{
+impl_manager_deactivate_connection (NMDBusObject *obj,
+                                    const NMDBusInterfaceInfoExtended *interface_info,
+                                    const NMDBusMethodInfoExtended *method_info,
+                                    GDBusConnection *dbus_connection,
+                                    const char *sender,
+                                    GDBusMethodInvocation *invocation,
+                                    GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMActiveConnection *ac;
 	NMSettingsConnection *connection = NULL;
 	GError *error = NULL;
 	NMAuthSubject *subject = NULL;
 	NMAuthChain *chain;
-	char *error_desc = NULL;
+	const char *active_path;
+
+	g_variant_get (parameters, "(&o)", &active_path);
 
 	/* Find the connection by its object path */
 	ac = active_connection_get_by_path (self, active_path);
@@ -4688,7 +4860,7 @@ impl_manager_deactivate_connection (NMManager *self,
 	}
 
 	/* Validate the caller */
-	subject = nm_auth_subject_new_unix_process_from_context (context);
+	subject = nm_auth_subject_new_unix_process_from_context (invocation);
 	if (!subject) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
@@ -4696,19 +4868,15 @@ impl_manager_deactivate_connection (NMManager *self,
 		goto done;
 	}
 
-	/* Ensure the subject has permissions for this connection */
-	if (!nm_auth_is_subject_in_acl (NM_CONNECTION (connection),
-	                                subject,
-	                                &error_desc)) {
-		error = g_error_new_literal (NM_MANAGER_ERROR,
-		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             error_desc);
-		g_free (error_desc);
+	if (!nm_auth_is_subject_in_acl_set_error (NM_CONNECTION (connection),
+	                                          subject,
+	                                          NM_MANAGER_ERROR,
+	                                          NM_MANAGER_ERROR_PERMISSION_DENIED,
+	                                          &error))
 		goto done;
-	}
 
 	/* Validate the user request */
-	chain = nm_auth_chain_new_subject (subject, context, deactivate_net_auth_done_cb, self);
+	chain = nm_auth_chain_new_subject (subject, invocation, deactivate_net_auth_done_cb, self);
 	if (!chain) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
@@ -4726,7 +4894,7 @@ done:
 			nm_audit_log_connection_op (NM_AUDIT_OP_CONN_DEACTIVATE, connection, FALSE, NULL,
 			                            subject, error->message);
 		}
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_take_error (invocation, error);
 	}
 	g_clear_object (&subject);
 }
@@ -4834,7 +5002,7 @@ do_sleep_wake (NMManager *self, gboolean sleeping_changed)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	gboolean suspending, waking_from_suspend;
-	GSList *iter;
+	NMDevice *device;
 
 	suspending = sleeping_changed && priv->sleeping;
 	waking_from_suspend = sleeping_changed && !priv->sleeping;
@@ -4845,9 +5013,7 @@ do_sleep_wake (NMManager *self, gboolean sleeping_changed)
 		/* FIXME: are there still hardware devices that need to be disabled around
 		 * suspend/resume?
 		 */
-		for (iter = priv->devices; iter; iter = iter->next) {
-			NMDevice *device = iter->data;
-
+		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;
@@ -4875,9 +5041,7 @@ do_sleep_wake (NMManager *self, gboolean sleeping_changed)
 
 		if (waking_from_suspend) {
 			sleep_devices_clear (self);
-			for (iter = priv->devices; iter; iter = iter->next) {
-				NMDevice *device = iter->data;
-
+			c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
 				if (nm_device_is_software (device))
 					continue;
 
@@ -4904,8 +5068,7 @@ do_sleep_wake (NMManager *self, gboolean sleeping_changed)
 		nm_manager_rfkill_update (self, RFKILL_TYPE_UNKNOWN);
 
 		/* Re-manage managed devices */
-		for (iter = priv->devices; iter; iter = iter->next) {
-			NMDevice *device = NM_DEVICE (iter->data);
+		c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
 			guint i;
 
 			if (nm_device_is_software (device)) {
@@ -4998,27 +5161,28 @@ sleep_auth_done_cb (NMAuthChain *chain,
 		g_dbus_method_invocation_return_value (context, NULL);
 	}
 
-	nm_auth_chain_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 #endif
 
 static void
-impl_manager_sleep (NMManager *self,
-                    GDBusMethodInvocation *context,
-                    gboolean do_sleep)
-{
-	NMManagerPrivate *priv;
+impl_manager_sleep (NMDBusObject *obj,
+                    const NMDBusInterfaceInfoExtended *interface_info,
+                    const NMDBusMethodInfoExtended *method_info,
+                    GDBusConnection *connection,
+                    const char *sender,
+                    GDBusMethodInvocation *invocation,
+                    GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	GError *error = NULL;
 	gs_unref_object NMAuthSubject *subject = NULL;
-#if 0
-	NMAuthChain *chain;
-	const char *error_desc = NULL;
-#endif
+	gboolean do_sleep;
 
-	g_return_if_fail (NM_IS_MANAGER (self));
+	g_variant_get (parameters, "(b)", &do_sleep);
 
-	priv = NM_MANAGER_GET_PRIVATE (self);
-	subject = nm_auth_subject_new_unix_process_from_context (context);
+	subject = nm_auth_subject_new_unix_process_from_context (invocation);
 
 	if (priv->sleeping == do_sleep) {
 		error = g_error_new (NM_MANAGER_ERROR,
@@ -5026,7 +5190,7 @@ impl_manager_sleep (NMManager *self,
 		                     "Already %s", do_sleep ? "asleep" : "awake");
 		nm_audit_log_control_op (NM_AUDIT_OP_SLEEP_CONTROL, do_sleep ? "on" : "off", FALSE, subject,
 		                         error->message);
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_take_error (invocation, error);
 		return;
 	}
 
@@ -5040,22 +5204,8 @@ impl_manager_sleep (NMManager *self,
 	 */
 	_internal_sleep (self, do_sleep);
 	nm_audit_log_control_op (NM_AUDIT_OP_SLEEP_CONTROL, do_sleep ? "on" : "off", TRUE, subject, NULL);
-	g_dbus_method_invocation_return_value (context, NULL);
+	g_dbus_method_invocation_return_value (invocation, NULL);
 	return;
-
-#if 0
-	chain = nm_auth_chain_new (context, sleep_auth_done_cb, self, &error_desc);
-	if (chain) {
-		priv->auth_chains = g_slist_append (priv->auth_chains, chain);
-		nm_auth_chain_set_data (chain, "sleep", GUINT_TO_POINTER (do_sleep), NULL);
-		nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SLEEP_WAKE, TRUE);
-	} else {
-		error = g_error_new_literal (NM_MANAGER_ERROR,
-		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             error_desc);
-		g_dbus_method_invocation_take_error (context, error);
-	}
-#endif
 }
 
 static void
@@ -5131,21 +5281,25 @@ enable_net_done_cb (NMAuthChain *chain,
 		g_dbus_method_invocation_take_error (context, ret_error);
 	}
 
-	nm_auth_chain_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 
 static void
-impl_manager_enable (NMManager *self,
-                     GDBusMethodInvocation *context,
-                     gboolean enable)
-{
-	NMManagerPrivate *priv;
+impl_manager_enable (NMDBusObject *obj,
+                     const NMDBusInterfaceInfoExtended *interface_info,
+                     const NMDBusMethodInfoExtended *method_info,
+                     GDBusConnection *connection,
+                     const char *sender,
+                     GDBusMethodInvocation *invocation,
+                     GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthChain *chain;
 	GError *error = NULL;
+	gboolean enable;
 
-	g_return_if_fail (NM_IS_MANAGER (self));
-
-	priv = NM_MANAGER_GET_PRIVATE (self);
+	g_variant_get (parameters, "(b)", &enable);
 
 	if (priv->net_enabled == enable) {
 		error = g_error_new (NM_MANAGER_ERROR,
@@ -5154,7 +5308,7 @@ impl_manager_enable (NMManager *self,
 		goto done;
 	}
 
-	chain = nm_auth_chain_new_context (context, enable_net_done_cb, self);
+	chain = nm_auth_chain_new_context (invocation, enable_net_done_cb, self);
 	if (!chain) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
@@ -5168,7 +5322,7 @@ impl_manager_enable (NMManager *self,
 
 done:
 	if (error)
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_take_error (invocation, error);
 }
 
 /* Permissions */
@@ -5235,23 +5389,28 @@ get_permissions_done_cb (NMAuthChain *chain,
 		                                       g_variant_new ("(a{ss})", &results));
 	}
 
-	nm_auth_chain_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 
 static void
-impl_manager_get_permissions (NMManager *self,
-                              GDBusMethodInvocation *context)
-{
+impl_manager_get_permissions (NMDBusObject *obj,
+                              const NMDBusInterfaceInfoExtended *interface_info,
+                              const NMDBusMethodInfoExtended *method_info,
+                              GDBusConnection *connection,
+                              const char *sender,
+                              GDBusMethodInvocation *invocation,
+                              GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthChain *chain;
-	GError *error = NULL;
 
-	chain = nm_auth_chain_new_context (context, get_permissions_done_cb, self);
+	chain = nm_auth_chain_new_context (invocation, get_permissions_done_cb, self);
 	if (!chain) {
-		error = g_error_new_literal (NM_MANAGER_ERROR,
-		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Unable to authenticate request.");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_MANAGER_ERROR,
+		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                                               "Unable to authenticate request.");
 		return;
 	}
 
@@ -5275,83 +5434,123 @@ impl_manager_get_permissions (NMManager *self,
 }
 
 static void
-impl_manager_get_state (NMManager *self,
-                        GDBusMethodInvocation *context)
+impl_manager_state (NMDBusObject *obj,
+                    const NMDBusInterfaceInfoExtended *interface_info,
+                    const NMDBusMethodInfoExtended *method_info,
+                    GDBusConnection *connection,
+                    const char *sender,
+                    GDBusMethodInvocation *invocation,
+                    GVariant *parameters)
 {
+	NMManager *self = NM_MANAGER (obj);
+
 	nm_manager_update_state (self);
-	g_dbus_method_invocation_return_value (context,
+	g_dbus_method_invocation_return_value (invocation,
 	                                       g_variant_new ("(u)", NM_MANAGER_GET_PRIVATE (self)->state));
 }
 
 static void
-impl_manager_set_logging (NMManager *self,
-                          GDBusMethodInvocation *context,
-                          const char *level,
-                          const char *domains)
-{
+impl_manager_set_logging (NMDBusObject *obj,
+                          const NMDBusInterfaceInfoExtended *interface_info,
+                          const NMDBusMethodInfoExtended *method_info,
+                          GDBusConnection *connection,
+                          const char *sender,
+                          GDBusMethodInvocation *invocation,
+                          GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
 	GError *error = NULL;
+	const char *level;
+	const char *domains;
 
 	/* 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
 	 * we'll be able to switch to polkit without breaking behavior.
 	 */
-	if (!nm_bus_manager_ensure_uid (nm_bus_manager_get (),
-	                                context,
-	                                G_MAXULONG,
-	                                NM_MANAGER_ERROR,
-	                                NM_MANAGER_ERROR_PERMISSION_DENIED))
+	if (!nm_dbus_manager_ensure_uid (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
+	                                 invocation,
+	                                 G_MAXULONG,
+	                                 NM_MANAGER_ERROR,
+	                                 NM_MANAGER_ERROR_PERMISSION_DENIED))
 		return;
 
+	g_variant_get (parameters, "(&s&s)", &level, &domains);
+
 	if (nm_logging_setup (level, domains, NULL, &error)) {
 		_LOGI (LOGD_CORE, "logging: level '%s' domains '%s'",
 		       nm_logging_level_to_string (), nm_logging_domains_to_string ());
 	}
 
 	if (error)
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_take_error (invocation, error);
 	else
-		g_dbus_method_invocation_return_value (context, NULL);
+		g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
 static void
-impl_manager_get_logging (NMManager *manager,
-                          GDBusMethodInvocation *context)
-{
-	g_dbus_method_invocation_return_value (context,
+impl_manager_get_logging (NMDBusObject *obj,
+                          const NMDBusInterfaceInfoExtended *interface_info,
+                          const NMDBusMethodInfoExtended *method_info,
+                          GDBusConnection *connection,
+                          const char *sender,
+                          GDBusMethodInvocation *invocation,
+                          GVariant *parameters)
+{
+	g_dbus_method_invocation_return_value (invocation,
 	                                       g_variant_new ("(ss)",
 	                                                      nm_logging_level_to_string (),
 	                                                      nm_logging_domains_to_string ()));
 }
 
 typedef struct {
-	guint remaining;
+	NMManager *self;
 	GDBusMethodInvocation *context;
-	NMConnectivityState state;
+	guint remaining;
 } ConnectivityCheckData;
 
 static void
-device_connectivity_done (NMDevice *device, NMConnectivityState state, gpointer user_data)
+device_connectivity_done (NMDevice *device,
+                          NMDeviceConnectivityHandle *handle,
+                          NMConnectivityState state,
+                          GError *error,
+                          gpointer user_data)
 {
 	ConnectivityCheckData *data = user_data;
+	NMManager *self;
+	NMManagerPrivate *priv;
 
-	data->remaining--;
+	nm_assert (data);
+	nm_assert (data->remaining > 0);
+	nm_assert (NM_IS_MANAGER (data->self));
 
-	/* We check if the state is already FULL so that we can provide the
-	 * response without waiting for slower devices that are not going to
-	 * affect the overall state anyway. */
+	data->remaining--;
 
-	if (data->state != NM_CONNECTIVITY_FULL) {
-		if (state > data->state)
-			data->state = state;
+	self = data->self;
+	priv = NM_MANAGER_GET_PRIVATE (self);
 
-		if (data->state == NM_CONNECTIVITY_FULL || !data->remaining) {
-			g_dbus_method_invocation_return_value (data->context,
-			                                       g_variant_new ("(u)", data->state));
-		}
+	if (   data->context
+	    && (   data->remaining == 0
+	        || (   state == NM_CONNECTIVITY_FULL
+	            && priv->connectivity_state == NM_CONNECTIVITY_FULL))) {
+		/* despite having a @handle and @state returned by the requests, we always
+		 * return the current connectivity_state. That is, because the connectivity_state
+		 * and the answer to the connectivity check shall agree.
+		 *
+		 * However, if one of the requests (early) returns full connectivity and agrees with
+		 * the accumulated connectivity state, we no longer have to wait. The result is set.
+		 *
+		 * This also works well, because NMDevice first emits change signals to its own
+		 * connectivity state, which is then taken into account for the accumulated global
+		 * state. All this happens, before the callback is invoked. */
+		g_dbus_method_invocation_return_value (g_steal_pointer (&data->context),
+		                                       g_variant_new ("(u)",
+		                                                      (guint) priv->connectivity_state));
 	}
 
-	if (!data->remaining)
+	if (data->remaining == 0) {
+		g_object_unref (self);
 		g_slice_free (ConnectivityCheckData, data);
+	}
 }
 
 static void
@@ -5365,7 +5564,7 @@ check_connectivity_auth_done_cb (NMAuthChain *chain,
 	GError *error = NULL;
 	NMAuthCallResult result;
 	ConnectivityCheckData *data;
-	const GSList *devices;
+	NMDevice *device;
 
 	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
 
@@ -5381,39 +5580,59 @@ check_connectivity_auth_done_cb (NMAuthChain *chain,
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
 		                             "Not authorized to recheck connectivity");
-	} else {
-		/* it's allowed */
-		data = g_slice_new0 (ConnectivityCheckData);
-		data->context = context;
+	}
 
-		for (devices = priv->devices; devices; devices = devices->next) {
+	if (error) {
+		g_dbus_method_invocation_take_error (context, error);
+		goto out;
+	}
+
+	data = g_slice_new (ConnectivityCheckData);
+	data->self = g_object_ref (self);
+	data->context = context;
+	data->remaining = 0;
+
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
+		if (nm_device_check_connectivity (device,
+		                                  device_connectivity_done,
+		                                  data))
 			data->remaining++;
-			nm_device_check_connectivity (NM_DEVICE (devices->data),
-			                              device_connectivity_done,
-			                              data);
-		}
 	}
 
-	if (error)
-		g_dbus_method_invocation_take_error (context, error);
-	nm_auth_chain_unref (chain);
+	if (data->remaining == 0) {
+		/* call the handler at least once. */
+		data->remaining = 1;
+		device_connectivity_done (NULL,
+		                          NULL,
+		                          NM_CONNECTIVITY_UNKNOWN,
+		                          NULL,
+		                          data);
+		/* @data got destroyed. */
+	}
+
+out:
+	nm_auth_chain_destroy (chain);
 }
 
 static void
-impl_manager_check_connectivity (NMManager *self,
-                                 GDBusMethodInvocation *context)
-{
+impl_manager_check_connectivity (NMDBusObject *obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *method_info,
+                                 GDBusConnection *connection,
+                                 const char *sender,
+                                 GDBusMethodInvocation *invocation,
+                                 GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthChain *chain;
-	GError *error = NULL;
 
-	/* Validate the request */
-	chain = nm_auth_chain_new_context (context, check_connectivity_auth_done_cb, self);
+	chain = nm_auth_chain_new_context (invocation, check_connectivity_auth_done_cb, self);
 	if (!chain) {
-		error = g_error_new_literal (NM_MANAGER_ERROR,
-		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Unable to authenticate request.");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_return_error_literal(invocation,
+		                                              NM_MANAGER_ERROR,
+		                                              NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                                              "Unable to authenticate request.");
 		return;
 	}
 
@@ -5430,15 +5649,14 @@ start_factory (NMDeviceFactory *factory, gpointer user_data)
 void
 nm_manager_write_device_state (NMManager *self)
 {
-	const GSList *devices;
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMDevice *device;
 	gs_unref_hashtable GHashTable *seen_ifindexes = NULL;
 	gint nm_owned;
 
-	seen_ifindexes = g_hash_table_new (NULL, NULL);
+	seen_ifindexes = g_hash_table_new (nm_direct_hash, NULL);
 
-	for (devices = priv->devices; devices; devices = devices->next) {
-		NMDevice *device = NM_DEVICE (devices->data);
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
 		int ifindex;
 		gboolean managed;
 		NMConfigDeviceStateManagedType managed_type;
@@ -5579,10 +5797,10 @@ void
 nm_manager_stop (NMManager *self)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMDevice *device;
 
-	/* Remove all devices */
-	while (priv->devices)
-		remove_device (self, NM_DEVICE (priv->devices->data), TRUE, TRUE);
+	while ((device = c_list_first_entry (&priv->devices_lst_head, NMDevice, devices_lst)))
+		remove_device (self, device, TRUE, TRUE);
 
 	_active_connection_cleanup (self);
 
@@ -5594,21 +5812,20 @@ handle_firmware_changed (gpointer user_data)
 {
 	NMManager *self = NM_MANAGER (user_data);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	GSList *iter;
+	NMDevice *device;
 
 	priv->fw_changed_id = 0;
 
 	/* Try to re-enable devices with missing firmware */
-	for (iter = priv->devices; iter; iter = iter->next) {
-		NMDevice *candidate = NM_DEVICE (iter->data);
-		NMDeviceState state = nm_device_get_state (candidate);
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
+		NMDeviceState state = nm_device_get_state (device);
 
-		if (   nm_device_get_firmware_missing (candidate)
+		if (   nm_device_get_firmware_missing (device)
 		    && (state == NM_DEVICE_STATE_UNAVAILABLE)) {
-			_LOG2I (LOGD_CORE, candidate, "firmware may now be available");
+			_LOG2I (LOGD_CORE, device, "firmware may now be available");
 
 			/* Re-set unavailable state to try bringing the device up again */
-			nm_device_state_changed (candidate,
+			nm_device_state_changed (device,
 			                         NM_DEVICE_STATE_UNAVAILABLE,
 			                         NM_DEVICE_STATE_REASON_NONE);
 		}
@@ -5727,318 +5944,133 @@ policy_activating_device_changed (GObject *object, GParamSpec *pspec, gpointer u
 	}
 }
 
-#define NM_PERM_DENIED_ERROR "org.freedesktop.NetworkManager.PermissionDenied"
+/*****************************************************************************/
 
 typedef struct {
 	NMManager *self;
-	GDBusConnection *connection;
-	GDBusMessage *message;
-	NMAuthSubject *subject;
-	const char *permission;
-	const char *audit_op;
-	char *audit_prop_value;
-	GType interface_type;
-	const char *glib_propname;
-} PropertyFilterData;
+	NMDBusObject *obj;
+	const NMDBusInterfaceInfoExtended *interface_info;
+	const NMDBusPropertyInfoExtended *property_info;
+	GVariant *value;
+	guint64 export_version_id;
+} DBusSetPropertyHandle;
 
-static void
-free_property_filter_data (PropertyFilterData *pfd)
-{
-	g_object_unref (pfd->self);
-	g_object_unref (pfd->connection);
-	g_object_unref (pfd->message);
-	g_clear_object (&pfd->subject);
-	g_free (pfd->audit_prop_value);
-	g_slice_free (PropertyFilterData, pfd);
-}
+#define NM_PERM_DENIED_ERROR "org.freedesktop.NetworkManager.PermissionDenied"
 
 static void
-prop_set_auth_done_cb (NMAuthChain *chain,
-                       GError *error,
-                       GDBusMethodInvocation *context, /* NULL */
-                       gpointer user_data)
+_dbus_set_property_auth_cb (NMAuthChain *chain,
+                            GError *error,
+                            GDBusMethodInvocation *invocation,
+                            gpointer user_data)
 {
-	PropertyFilterData *pfd = user_data;
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (pfd->self);
+	DBusSetPropertyHandle *handle_data = user_data;
+	gs_unref_object NMDBusObject *obj = handle_data->obj;
+	const NMDBusInterfaceInfoExtended *interface_info = handle_data->interface_info;
+	const NMDBusPropertyInfoExtended *property_info = handle_data->property_info;
+	gs_unref_variant GVariant *value = handle_data->value;
+	guint64 export_version_id = handle_data->export_version_id;
+	gs_unref_object NMManager *self = handle_data->self;
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthCallResult result;
-	GDBusMessage *reply = NULL;
-	const char *error_message;
-	gs_unref_object NMExportedObject *object = NULL;
-	const NMGlobalDnsConfig *global_dns;
-	gs_unref_variant GVariant *value = NULL;
-	GVariant *args;
+	const char *error_name = NULL;
+	const char *error_message = NULL;
+	GValue gvalue;
+
+	g_slice_free (DBusSetPropertyHandle, handle_data);
 
 	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
-	result = nm_auth_chain_get_result (chain, pfd->permission);
-	if (error || (result != NM_AUTH_CALL_RESULT_YES)) {
-		reply = g_dbus_message_new_method_error_literal (pfd->message,
-		                                                 NM_PERM_DENIED_ERROR,
-		                                                 (error_message = "Not authorized to perform this operation"));
-		if (error)
-			error_message = error->message;
-		goto done;
-	}
+	result = nm_auth_chain_get_result (chain, property_info->writable.permission);
 
-	object = NM_EXPORTED_OBJECT (nm_bus_manager_get_registered_object (priv->dbus_mgr,
-	                                                                   g_dbus_message_get_path (pfd->message)));
-	if (!object) {
-		reply = g_dbus_message_new_method_error_literal (pfd->message,
-		                                                 "org.freedesktop.DBus.Error.UnknownObject",
-		                                                 (error_message = "Object doesn't exist."));
-		goto done;
+	if (   error
+	    || result != NM_AUTH_CALL_RESULT_YES) {
+		error_name = NM_PERM_DENIED_ERROR;
+		error_message = error ? error->message : "Not authorized to perform this operation";
+		goto out;
 	}
 
-	/* do some extra type checking... */
-	if (!nm_exported_object_get_interface_by_type (object, pfd->interface_type)) {
-		reply = g_dbus_message_new_method_error_literal (pfd->message,
-		                                                 "org.freedesktop.DBus.Error.InvalidArgs",
-		                                                 (error_message = "Object is of unexpected type."));
-		goto done;
+	if (export_version_id != nm_dbus_object_get_export_version_id (obj)) {
+		error_name = "org.freedesktop.DBus.Error.UnknownObject";
+		error_message = "Object was deleted while authenticating";
+		goto out;
 	}
 
-	args = g_dbus_message_get_body (pfd->message);
-	g_variant_get (args, "(&s&sv)", NULL, NULL, &value);
-	g_assert (pfd->glib_propname);
+	/* Handle some properties specially *sigh* */
+	if (   interface_info == &interface_info_manager
+	    && nm_streq (property_info->property_name, NM_MANAGER_GLOBAL_DNS_CONFIGURATION)) {
+		const NMGlobalDnsConfig *global_dns;
 
-	if (!strcmp (pfd->glib_propname, NM_MANAGER_GLOBAL_DNS_CONFIGURATION)) {
-		g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("a{sv}")));
 		global_dns = nm_config_data_get_global_dns_config (nm_config_get_data (priv->config));
-
-		if (global_dns && !nm_global_dns_config_is_internal (global_dns)) {
-			reply = g_dbus_message_new_method_error_literal (pfd->message,
-			                                                 NM_PERM_DENIED_ERROR,
-			                                                 (error_message = "Global DNS configuration already set via configuration file"));
-			goto done;
+		if (   global_dns
+		    && !nm_global_dns_config_is_internal (global_dns)) {
+			error_name = NM_PERM_DENIED_ERROR;
+			error_message = "Global DNS configuration already set via configuration file";
+			goto out;
 		}
-		/* ... but set the property on the @object itself. It would be correct to set the property
-		 * on the skeleton interface, but as it is now, the result is the same. */
-		g_object_set (object, pfd->glib_propname, value, NULL);
-	} else if (!strcmp (pfd->glib_propname, NM_DEVICE_STATISTICS_REFRESH_RATE_MS)) {
-		g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32));
-		/* the same here */
-		g_object_set (object, pfd->glib_propname, (guint) g_variant_get_uint32 (value), NULL);
-	} else {
-		g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN));
-		/* the same here */
-		g_object_set (object, pfd->glib_propname, g_variant_get_boolean (value), NULL);
 	}
 
-	reply = g_dbus_message_new_method_reply (pfd->message);
-	g_dbus_message_set_body (reply, g_variant_new_tuple (NULL, 0));
-	error_message = NULL;
-done:
-	nm_audit_log_control_op (pfd->audit_op, pfd->audit_prop_value, !error_message, pfd->subject, error_message);
-
-	g_dbus_connection_send_message (pfd->connection, reply,
-	                                G_DBUS_SEND_MESSAGE_FLAGS_NONE,
-	                                NULL, NULL);
-	g_object_unref (reply);
-	nm_auth_chain_unref (chain);
+	g_dbus_gvariant_to_gvalue (value, &gvalue);
+	g_object_set_property (G_OBJECT (obj), property_info->property_name, &gvalue);
+	g_value_unset (&gvalue);
 
-	free_property_filter_data (pfd);
+out:
+	nm_audit_log_control_op (property_info->writable.audit_op,
+	                         property_info->property_name,
+	                         !error_message,
+	                         nm_auth_chain_get_subject (chain),
+	                         error_message);
+	if (error_message)
+		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);
 }
 
-static gboolean
-do_set_property_check (gpointer user_data)
+void
+nm_manager_dbus_set_property_handle (NMDBusObject *obj,
+                                     const NMDBusInterfaceInfoExtended *interface_info,
+                                     const NMDBusPropertyInfoExtended *property_info,
+                                     GDBusConnection *connection,
+                                     const char *sender,
+                                     GDBusMethodInvocation *invocation,
+                                     GVariant *value,
+                                     gpointer user_data)
 {
-	PropertyFilterData *pfd = user_data;
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (pfd->self);
-	GDBusMessage *reply = NULL;
+	NMManager *self = user_data;
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthChain *chain;
 	const char *error_message = NULL;
+	gs_unref_object NMAuthSubject *subject = NULL;
+	DBusSetPropertyHandle *handle_data;
 
-	pfd->subject = nm_auth_subject_new_unix_process_from_message (pfd->connection, pfd->message);
-	if (!pfd->subject) {
-		reply = g_dbus_message_new_method_error_literal (pfd->message,
-		                                                 NM_PERM_DENIED_ERROR,
-		                                                 (error_message = "Could not determine request UID."));
-		goto out;
+	subject = nm_auth_subject_new_unix_process_from_context (invocation);
+	if (!subject) {
+		error_message = "Could not determine request UID";
+		goto err;
 	}
 
-	/* Validate the user request */
-	chain = nm_auth_chain_new_subject (pfd->subject, NULL, prop_set_auth_done_cb, pfd);
-	if (!chain) {
-		reply = g_dbus_message_new_method_error_literal (pfd->message,
-		                                                 NM_PERM_DENIED_ERROR,
-		                                                 (error_message = "Could not authenticate request."));
-		goto out;
-	}
+	handle_data = g_slice_new0 (DBusSetPropertyHandle);
+	handle_data->self = g_object_ref (self);
+	handle_data->obj = g_object_ref (obj);
+	handle_data->interface_info = interface_info;
+	handle_data->property_info = property_info;
+	handle_data->value = g_variant_ref (value);
+	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, pfd->permission, TRUE);
-
-out:
-	if (reply) {
-		nm_audit_log_control_op (pfd->audit_op, pfd->audit_prop_value, FALSE, pfd->subject, error_message);
-		g_dbus_connection_send_message (pfd->connection, reply,
-		                                G_DBUS_SEND_MESSAGE_FLAGS_NONE,
-		                                NULL, NULL);
-		g_object_unref (reply);
-		free_property_filter_data (pfd);
-	}
-
-	return FALSE;
-}
-
-static GDBusMessage *
-prop_filter (GDBusConnection *connection,
-             GDBusMessage *message,
-             gboolean incoming,
-             gpointer user_data)
-{
-	gs_unref_object NMManager *self = NULL;
-	GVariant *args;
-	const char *propiface = NULL;
-	const char *propname = NULL;
-	const char *glib_propname = NULL, *permission = NULL;
-	const char *audit_op = NULL;
-	GType interface_type = G_TYPE_INVALID;
-	PropertyFilterData *pfd;
-	const GVariantType *expected_type = G_VARIANT_TYPE_BOOLEAN;
-	gs_unref_variant GVariant *value = NULL;
-
-	self = g_weak_ref_get (user_data);
-	if (!self)
-		return message;
-
-	/* The sole purpose of this function is to validate property accesses on the
-	 * NMManager object since gdbus doesn't give us this functionality.
-	 */
-
-	/* Only filter org.freedesktop.DBus.Properties.Set calls */
-	if (   !incoming
-	    || g_dbus_message_get_message_type (message) != G_DBUS_MESSAGE_TYPE_METHOD_CALL
-	    || g_strcmp0 (g_dbus_message_get_interface (message), DBUS_INTERFACE_PROPERTIES) != 0
-	    || g_strcmp0 (g_dbus_message_get_member (message), "Set") != 0)
-		return message;
-
-	args = g_dbus_message_get_body (message);
-	if (!g_variant_is_of_type (args, G_VARIANT_TYPE ("(ssv)")))
-		return message;
-	g_variant_get (args, "(&s&sv)", &propiface, &propname, &value);
-
-	/* Only filter calls to filtered properties, on existing objects */
-	if (!strcmp (propiface, NM_DBUS_INTERFACE)) {
-		if (!strcmp (propname, "WirelessEnabled")) {
-			glib_propname = NM_MANAGER_WIRELESS_ENABLED;
-			permission = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI;
-			audit_op = NM_AUDIT_OP_RADIO_CONTROL;
-		} else if (!strcmp (propname, "WwanEnabled")) {
-			glib_propname = NM_MANAGER_WWAN_ENABLED;
-			permission = NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN;
-			audit_op = NM_AUDIT_OP_RADIO_CONTROL;
-		} else if (!strcmp (propname, "WimaxEnabled")) {
-			glib_propname = NM_MANAGER_WIMAX_ENABLED;
-			permission = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX;
-			audit_op = NM_AUDIT_OP_RADIO_CONTROL;
-		} else if (!strcmp (propname, "GlobalDnsConfiguration")) {
-			glib_propname = NM_MANAGER_GLOBAL_DNS_CONFIGURATION;
-			permission = NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS;
-			audit_op = NM_AUDIT_OP_NET_CONTROL;
-			expected_type = G_VARIANT_TYPE ("a{sv}");
-		} else if (!strcmp (propname, "ConnectivityCheckEnabled")) {
-			glib_propname = NM_MANAGER_CONNECTIVITY_CHECK_ENABLED;
-			permission = NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK;
-			audit_op = NM_AUDIT_OP_NET_CONTROL;
-		} else
-			return message;
-		interface_type = NMDBUS_TYPE_MANAGER_SKELETON;
-	} else if (!strcmp (propiface, NM_DBUS_INTERFACE_DEVICE)) {
-		if (!strcmp (propname, "Autoconnect")) {
-			glib_propname = NM_DEVICE_AUTOCONNECT;
-			permission = NM_AUTH_PERMISSION_NETWORK_CONTROL;
-			audit_op = NM_AUDIT_OP_DEVICE_AUTOCONNECT;
-		} else if (!strcmp (propname, "Managed")) {
-			glib_propname = NM_DEVICE_MANAGED;
-			permission = NM_AUTH_PERMISSION_NETWORK_CONTROL;
-			audit_op = NM_AUDIT_OP_DEVICE_MANAGED;
-		} else
-			return message;
-		interface_type = NMDBUS_TYPE_DEVICE_SKELETON;
-	} else if (!strcmp (propiface, NM_DBUS_INTERFACE_DEVICE_STATISTICS)) {
-		if (!strcmp (propname, "RefreshRateMs")) {
-			glib_propname = NM_DEVICE_STATISTICS_REFRESH_RATE_MS;
-			permission = NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS;
-			audit_op = NM_AUDIT_OP_STATISTICS;
-			expected_type = G_VARIANT_TYPE ("u");
-		} else
-			return message;
-		interface_type = NMDBUS_TYPE_DEVICE_SKELETON;
-	} else
-		return message;
-
-	if (!g_variant_is_of_type (value, expected_type))
-		return message;
-
-	/* This filter function is called from a gdbus worker thread which we can't
-	 * make other D-Bus calls from. In particular, we cannot call
-	 * org.freedesktop.DBus.GetConnectionUnixUser to find the remote UID.
-	 */
-	pfd = g_slice_new0 (PropertyFilterData);
-	pfd->self = self;
-	self = NULL;
-	pfd->connection = g_object_ref (connection);
-	pfd->message = message;
-	pfd->permission = permission;
-	pfd->interface_type = interface_type;
-	pfd->glib_propname = glib_propname;
-	pfd->audit_op = audit_op;
-	if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) {
-		pfd->audit_prop_value = g_strdup_printf ("%s:%d", pfd->glib_propname,
-		                                         g_variant_get_boolean (value));
-	} else
-		pfd->audit_prop_value = g_strdup (pfd->glib_propname);
-
-	g_idle_add (do_set_property_check, pfd);
-
-	return NULL;
-}
-
-/*****************************************************************************/
-
-static int
-_set_prop_filter_free2 (gpointer user_data)
-{
-	g_slice_free (GWeakRef, user_data);
-	return G_SOURCE_REMOVE;
-}
-
-static void
-_set_prop_filter_free (gpointer user_data)
-{
-	g_weak_ref_clear (user_data);
-
-	/* Delay the final deletion of the user_data. There is a race when
-	 * calling g_dbus_connection_remove_filter() that the callback and user_data
-	 * might have been copied and being executed after the destroy function
-	 * runs (bgo #704568).
-	 * This doesn't really fix the race, but it should work well enough. */
-	g_timeout_add_seconds (2, _set_prop_filter_free2, user_data);
-}
-
-static void
-_set_prop_filter (NMManager *self, GDBusConnection *connection)
-{
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-
-	nm_assert ((!priv->prop_filter.connection) == (!priv->prop_filter.id));
-
-	if (priv->prop_filter.connection == connection)
-		return;
-
-	if (priv->prop_filter.connection) {
-		g_dbus_connection_remove_filter (priv->prop_filter.connection, priv->prop_filter.id);
-		priv->prop_filter.id = 0;
-		g_clear_object (&priv->prop_filter.connection);
-	}
-	if (connection) {
-		GWeakRef *wptr;
+	nm_auth_chain_add_call (chain, property_info->writable.permission, TRUE);
+	return;
 
-		wptr = g_slice_new (GWeakRef);
-		g_weak_ref_init  (wptr, self);
-		priv->prop_filter.id = g_dbus_connection_add_filter (connection, prop_filter, wptr, _set_prop_filter_free);
-		priv->prop_filter.connection = g_object_ref (connection);
-	}
+err:
+	nm_audit_log_control_op (property_info->writable.audit_op,
+	                         property_info->property_name,
+	                         FALSE,
+	                         invocation,
+	                         error_message);
+	g_dbus_method_invocation_return_error_literal (invocation,
+	                                               G_DBUS_ERROR,
+	                                               G_DBUS_ERROR_AUTH_FAILED,
+	                                               error_message);
 }
 
 /*****************************************************************************/
@@ -6049,7 +6081,7 @@ _checkpoint_mgr_get (NMManager *self, gboolean create_as_needed)
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 
 	if (G_UNLIKELY (!priv->checkpoint_mgr) && create_as_needed)
-		priv->checkpoint_mgr = nm_checkpoint_manager_new (self);
+		priv->checkpoint_mgr = nm_checkpoint_manager_new (self, obj_properties[PROP_CHECKPOINTS]);
 	return priv->checkpoint_mgr;
 }
 
@@ -6068,13 +6100,15 @@ checkpoint_auth_done_cb (NMAuthChain *chain,
 	GVariant *variant = NULL;
 	GError *error = NULL;
 	const char *arg = NULL;
+	guint32 add_timeout;
 
 	op = nm_auth_chain_get_data (chain, "audit-op");
 	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
 	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK);
 
-	if (   nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_DESTROY)
-	    || nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_ROLLBACK))
+	if (NM_IN_STRSET (op, NM_AUDIT_OP_CHECKPOINT_DESTROY,
+	                      NM_AUDIT_OP_CHECKPOINT_ROLLBACK,
+	                      NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT))
 		arg = checkpoint_path = nm_auth_chain_get_data (chain, "checkpoint_path");
 
 	if (auth_error) {
@@ -6098,7 +6132,7 @@ checkpoint_auth_done_cb (NMAuthChain *chain,
 			                                           (NMCheckpointCreateFlags) flags,
 			                                           &error);
 			if (checkpoint) {
-				arg = nm_exported_object_get_path (NM_EXPORTED_OBJECT (checkpoint));
+				arg = nm_dbus_object_get_path (NM_DBUS_OBJECT (checkpoint));
 				variant = g_variant_new ("(o)", arg);
 			}
 		} else if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_DESTROY)) {
@@ -6107,6 +6141,10 @@ checkpoint_auth_done_cb (NMAuthChain *chain,
 		} else if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_ROLLBACK)) {
 			nm_checkpoint_manager_rollback (_checkpoint_mgr_get (self, TRUE),
 			                                checkpoint_path, &variant, &error);
+		} else if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT)) {
+			add_timeout = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "add_timeout"));
+			nm_checkpoint_manager_adjust_rollback_timeout (_checkpoint_mgr_get (self, TRUE),
+			                                               checkpoint_path, add_timeout, &error);
 		} else
 			g_return_if_reached ();
 	}
@@ -6119,63 +6157,71 @@ checkpoint_auth_done_cb (NMAuthChain *chain,
 	else
 		g_dbus_method_invocation_return_value (context, variant);
 
-
-	nm_auth_chain_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 
 static void
-impl_manager_checkpoint_create (NMManager *self,
-                                GDBusMethodInvocation *context,
-                                const char *const *devices,
-                                guint32 rollback_timeout,
-                                guint32 flags)
-{
-	NMManagerPrivate *priv;
+impl_manager_checkpoint_create (NMDBusObject *obj,
+                                const NMDBusInterfaceInfoExtended *interface_info,
+                                const NMDBusMethodInfoExtended *method_info,
+                                GDBusConnection *connection,
+                                const char *sender,
+                                GDBusMethodInvocation *invocation,
+                                GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthChain *chain;
-	GError *error = NULL;
+	char **devices;
+	guint32 rollback_timeout;
+	guint32 flags;
 
 	G_STATIC_ASSERT_EXPR (sizeof (flags) <= sizeof (NMCheckpointCreateFlags));
-	g_return_if_fail (NM_IS_MANAGER (self));
-	priv = NM_MANAGER_GET_PRIVATE (self);
 
-	chain = nm_auth_chain_new_context (context, checkpoint_auth_done_cb, self);
+	chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self);
 	if (!chain) {
-		error = g_error_new_literal (NM_MANAGER_ERROR,
-		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Unable to authenticate request.");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_MANAGER_ERROR,
+		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                                               "Unable to authenticate request.");
 		return;
 	}
 
+	g_variant_get (parameters, "(^aouu)", &devices, &rollback_timeout, &flags);
+
 	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
 	nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_CREATE, NULL);
-	nm_auth_chain_set_data (chain, "devices", g_strdupv ((char **) devices), (GDestroyNotify) g_strfreev);
+	nm_auth_chain_set_data (chain, "devices", devices, (GDestroyNotify) g_strfreev);
 	nm_auth_chain_set_data (chain, "flags",  GUINT_TO_POINTER (flags), NULL);
 	nm_auth_chain_set_data (chain, "timeout", GUINT_TO_POINTER (rollback_timeout), NULL);
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE);
 }
 
 static void
-impl_manager_checkpoint_destroy (NMManager *self,
-                                 GDBusMethodInvocation *context,
-                                 const char *checkpoint_path)
-{
-	NMManagerPrivate *priv;
-	GError *error = NULL;
+impl_manager_checkpoint_destroy (NMDBusObject *obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *method_info,
+                                 GDBusConnection *connection,
+                                 const char *sender,
+                                 GDBusMethodInvocation *invocation,
+                                 GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthChain *chain;
+	const char *checkpoint_path;
 
-	g_return_if_fail (NM_IS_MANAGER (self));
-	priv = NM_MANAGER_GET_PRIVATE (self);
-
-	chain = nm_auth_chain_new_context (context, checkpoint_auth_done_cb, self);
+	chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self);
 	if (!chain) {
-		error = g_error_new_literal (NM_MANAGER_ERROR,
-		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Unable to authenticate request.");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_MANAGER_ERROR,
+		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                                               "Unable to authenticate request.");
 		return;
 	}
 
+	g_variant_get (parameters, "(&o)", &checkpoint_path);
+
 	priv->auth_chains = g_slist_append (priv->auth_chains, 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);
@@ -6183,39 +6229,79 @@ impl_manager_checkpoint_destroy (NMManager *self,
 }
 
 static void
-impl_manager_checkpoint_rollback (NMManager *self,
-                                  GDBusMethodInvocation *context,
-                                  const char *checkpoint_path)
-{
-	NMManagerPrivate *priv;
-	GError *error = NULL;
+impl_manager_checkpoint_rollback (NMDBusObject *obj,
+                                  const NMDBusInterfaceInfoExtended *interface_info,
+                                  const NMDBusMethodInfoExtended *method_info,
+                                  GDBusConnection *connection,
+                                  const char *sender,
+                                  GDBusMethodInvocation *invocation,
+                                  GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthChain *chain;
+	const char *checkpoint_path;
 
-	g_return_if_fail (NM_IS_MANAGER (self));
-	priv = NM_MANAGER_GET_PRIVATE (self);
-
-	chain = nm_auth_chain_new_context (context, checkpoint_auth_done_cb, self);
+	chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self);
 	if (!chain) {
-		error = g_error_new_literal (NM_MANAGER_ERROR,
-		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Unable to authenticate request.");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_MANAGER_ERROR,
+		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                                               "Unable to authenticate request.");
 		return;
 	}
 
+	g_variant_get (parameters, "(&o)", &checkpoint_path);
+
 	priv->auth_chains = g_slist_append (priv->auth_chains, 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);
 }
 
+static void
+impl_manager_checkpoint_adjust_rollback_timeout (NMDBusObject *obj,
+                                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                                 const NMDBusMethodInfoExtended *method_info,
+                                                 GDBusConnection *connection,
+                                                 const char *sender,
+                                                 GDBusMethodInvocation *invocation,
+                                                 GVariant *parameters)
+{
+	NMManager *self = NM_MANAGER (obj);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMAuthChain *chain;
+	const char *checkpoint_path;
+	guint32 add_timeout;
+
+	chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self);
+	if (!chain) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_MANAGER_ERROR,
+		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                                               "Unable to authenticate request.");
+		return;
+	}
+
+	g_variant_get (parameters, "(&ou)", &checkpoint_path, &add_timeout);
+
+	priv->auth_chains = g_slist_append (priv->auth_chains, 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);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE);
+}
+
 /*****************************************************************************/
 
 static void
 auth_mgr_changed (NMAuthManager *auth_manager, gpointer user_data)
 {
 	/* Let clients know they should re-check their authorization */
-	g_signal_emit (NM_MANAGER (user_data), signals[CHECK_PERMISSIONS], 0);
+	nm_dbus_object_emit_signal (user_data,
+	                            &interface_info_manager,
+	                            &signal_info_check_permissions,
+	                            "()");
 }
 
 #define KERN_RFKILL_OP_CHANGE_ALL 3
@@ -6340,14 +6426,6 @@ periodic_update_active_connection_timestamps (gpointer user_data)
 	return G_SOURCE_CONTINUE;
 }
 
-static void
-dbus_connection_changed_cb (NMBusManager *dbus_mgr,
-                            GDBusConnection *connection,
-                            gpointer user_data)
-{
-	_set_prop_filter (NM_MANAGER (user_data), connection);
-}
-
 /*****************************************************************************/
 
 void
@@ -6414,8 +6492,7 @@ nm_manager_setup (void)
 	nm_singleton_instance_register ();
 	_LOGD (LOGD_CORE, "setup %s singleton (%p)", "NMManager", singleton_instance);
 
-	nm_exported_object_export ((NMExportedObject *) self);
-
+	nm_dbus_object_export (NM_DBUS_OBJECT (self));
 	return self;
 }
 
@@ -6428,11 +6505,9 @@ constructed (GObject *object)
 
 	G_OBJECT_CLASS (nm_manager_parent_class)->constructed (object);
 
-	_set_prop_filter (self, nm_bus_manager_get_connection (priv->dbus_mgr));
-
 	priv->settings = nm_settings_new ();
 
-	nm_exported_object_export (NM_EXPORTED_OBJECT (priv->settings));
+	nm_dbus_object_export (NM_DBUS_OBJECT (priv->settings));
 
 	g_signal_connect (priv->settings, "notify::" NM_SETTINGS_STARTUP_COMPLETE,
 	                  G_CALLBACK (settings_startup_complete_changed), self);
@@ -6500,6 +6575,7 @@ nm_manager_init (NMManager *self)
 	GFile *file;
 
 	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->delete_volatile_connection_lst_head);
 
@@ -6531,12 +6607,6 @@ nm_manager_init (NMManager *self)
 	priv->state = NM_STATE_DISCONNECTED;
 	priv->startup = TRUE;
 
-	priv->dbus_mgr = g_object_ref (nm_bus_manager_get ());
-	g_signal_connect (priv->dbus_mgr,
-	                  NM_BUS_MANAGER_DBUS_CONNECTION_CHANGED,
-	                  G_CALLBACK (dbus_connection_changed_cb),
-	                  self);
-
 	/* sleep/wake handling */
 	priv->sleep_monitor = nm_sleep_monitor_new ();
 	g_signal_connect (priv->sleep_monitor, NM_SLEEP_MONITOR_SLEEPING,
@@ -6571,13 +6641,7 @@ nm_manager_init (NMManager *self)
 	priv->timestamp_update_id = g_timeout_add_seconds (300, (GSourceFunc) periodic_update_active_connection_timestamps, self);
 
 	priv->metered = NM_METERED_UNKNOWN;
-	priv->sleep_devices = g_hash_table_new (g_direct_hash, g_direct_equal);
-}
-
-static gboolean
-device_is_real (GObject *device, gpointer user_data)
-{
-	return nm_device_is_real (NM_DEVICE (device));
+	priv->sleep_devices = g_hash_table_new (nm_direct_hash, NULL);
 }
 
 static void
@@ -6592,7 +6656,6 @@ get_property (GObject *object, guint prop_id,
 	const char *path;
 	NMActiveConnection *ac;
 	GPtrArray *ptrarr;
-	gboolean vbool;
 
 	switch (prop_id) {
 	case PROP_VERSION:
@@ -6634,7 +6697,7 @@ get_property (GObject *object, guint prop_id,
 	case PROP_ACTIVE_CONNECTIONS:
 		ptrarr = g_ptr_array_new ();
 		c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
-			path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (ac));
+			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (ac));
 			if (path)
 				g_ptr_array_add (ptrarr, g_strdup (path));
 		}
@@ -6649,15 +6712,10 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_boolean (value, nm_config_data_get_connectivity_uri (config_data) != NULL);
 		break;
 	case PROP_CONNECTIVITY_CHECK_ENABLED:
-#if WITH_CONCHECK
-		vbool = nm_connectivity_check_enabled (nm_connectivity_get ());
-#else
-		vbool = FALSE;
-#endif
-		g_value_set_boolean (value, vbool);
+		g_value_set_boolean (value, concheck_enabled (self, NULL));
 		break;
 	case PROP_PRIMARY_CONNECTION:
-		nm_utils_g_value_set_object_path (value, priv->primary_connection);
+		nm_dbus_utils_g_value_set_object_path (value, priv->primary_connection);
 		break;
 	case PROP_PRIMARY_CONNECTION_TYPE:
 		type = NULL;
@@ -6671,13 +6729,15 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_string (value, type ? type : "");
 		break;
 	case PROP_ACTIVATING_CONNECTION:
-		nm_utils_g_value_set_object_path (value, priv->activating_connection);
+		nm_dbus_utils_g_value_set_object_path (value, priv->activating_connection);
 		break;
 	case PROP_SLEEPING:
 		g_value_set_boolean (value, priv->sleeping);
 		break;
 	case PROP_DEVICES:
-		nm_utils_g_value_set_object_path_array (value, priv->devices, device_is_real, NULL);
+		g_value_take_boxed (value,
+		                    nm_utils_strv_make_deep_copied (_get_devices_paths (self,
+		                                                                        FALSE)));
 		break;
 	case PROP_METERED:
 		g_value_set_uint (value, priv->metered);
@@ -6688,7 +6748,16 @@ get_property (GObject *object, guint prop_id,
 		nm_global_dns_config_to_dbus (dns_config, value);
 		break;
 	case PROP_ALL_DEVICES:
-		nm_utils_g_value_set_object_path_array (value, priv->devices, NULL, NULL);
+		g_value_take_boxed (value,
+		                    nm_utils_strv_make_deep_copied (_get_devices_paths (self,
+		                                                                        TRUE)));
+		break;
+	case PROP_CHECKPOINTS:
+		g_value_take_boxed (value,
+		                    priv->checkpoint_mgr
+		                    ? nm_utils_strv_make_deep_copied (nm_checkpoint_manager_get_checkpoint_paths (priv->checkpoint_mgr,
+		                                                                                                  NULL))
+		                    : NULL);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -6755,11 +6824,6 @@ dispose (GObject *object)
 	CList *iter, *iter_safe;
 	NMActiveConnection *ac, *ac_safe;
 
-	nm_clear_g_source (&priv->delete_volatile_connection_idle_id);
-	_delete_volatile_connection_all (self, FALSE);
-	nm_assert (!priv->delete_volatile_connection_idle_id);
-	nm_assert (c_list_is_empty (&priv->delete_volatile_connection_lst_head));
-
 	g_signal_handlers_disconnect_by_func (priv->platform,
 	                                      G_CALLBACK (platform_link_cb),
 	                                      self);
@@ -6771,14 +6835,18 @@ dispose (GObject *object)
 		g_slice_free (PlatformLinkCbData, data);
 	}
 
-	g_slist_free_full (priv->auth_chains, (GDestroyNotify) nm_auth_chain_unref);
+	g_slist_free_full (priv->auth_chains, (GDestroyNotify) nm_auth_chain_destroy);
 	priv->auth_chains = NULL;
 
 	nm_clear_g_source (&priv->devices_inited_id);
 
-	if (priv->checkpoint_mgr) {
-		nm_checkpoint_manager_destroy_all (priv->checkpoint_mgr, NULL);
-		g_clear_pointer (&priv->checkpoint_mgr, nm_checkpoint_manager_unref);
+	g_clear_pointer (&priv->checkpoint_mgr, nm_checkpoint_manager_free);
+
+	if (priv->concheck_mgr) {
+		g_signal_handlers_disconnect_by_func (priv->concheck_mgr,
+		                                      G_CALLBACK (concheck_config_changed_cb),
+		                                      self);
+		g_clear_object (&priv->concheck_mgr);
 	}
 
 	if (priv->auth_mgr) {
@@ -6788,7 +6856,7 @@ dispose (GObject *object)
 		g_clear_object (&priv->auth_mgr);
 	}
 
-	g_assert (priv->devices == NULL);
+	nm_assert (c_list_is_empty (&priv->devices_lst_head));
 
 	nm_clear_g_source (&priv->ac_cleanup_id);
 
@@ -6825,13 +6893,6 @@ dispose (GObject *object)
 
 	g_clear_object (&priv->vpn_manager);
 
-	/* Unregister property filter */
-	if (priv->dbus_mgr) {
-		g_signal_handlers_disconnect_by_func (priv->dbus_mgr, dbus_connection_changed_cb, self);
-		g_clear_object (&priv->dbus_mgr);
-	}
-	_set_prop_filter (self, NULL);
-
 	sleep_devices_clear (self);
 	g_clear_pointer (&priv->sleep_devices, g_hash_table_unref);
 
@@ -6854,6 +6915,11 @@ dispose (GObject *object)
 		g_clear_object (&priv->rfkill_mgr);
 	}
 
+	nm_clear_g_source (&priv->delete_volatile_connection_idle_id);
+	_delete_volatile_connection_all (self, FALSE);
+	nm_assert (!priv->delete_volatile_connection_idle_id);
+	nm_assert (c_list_is_empty (&priv->delete_volatile_connection_lst_head));
+
 	nm_device_factory_manager_for_each_factory (_deinit_device_factory, self);
 
 	nm_clear_g_source (&priv->timestamp_update_id);
@@ -6875,22 +6941,274 @@ finalize (GObject *object)
 	g_object_unref (priv->platform);
 }
 
+static const GDBusSignalInfo signal_info_check_permissions = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"CheckPermissions",
+);
+
+static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"StateChanged",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("state", "u"),
+	),
+);
+
+static const GDBusSignalInfo signal_info_device_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"DeviceAdded",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("device_path", "o"),
+	),
+);
+
+static const GDBusSignalInfo signal_info_device_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"DeviceRemoved",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("device_path", "o"),
+	),
+);
+
+static const NMDBusInterfaceInfoExtended interface_info_manager = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Reload",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("flags", "u"),
+					),
+				),
+				.handle = impl_manager_reload,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetDevices",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("devices", "ao"),
+					),
+				),
+				.handle = impl_manager_get_devices,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetAllDevices",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("devices", "ao"),
+					),
+				),
+				.handle = impl_manager_get_all_devices,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetDeviceByIpIface",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("iface", "s"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("device", "o"),
+					),
+				),
+				.handle = impl_manager_get_device_by_ip_iface,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"ActivateConnection",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connection",      "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("device",          "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("specific_object", "o"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"),
+					),
+				),
+				.handle = impl_manager_activate_connection,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"AddAndActivateConnection",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connection",      "a{sa{sv}}"),
+						NM_DEFINE_GDBUS_ARG_INFO ("device",          "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("specific_object", "o"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("path",              "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"),
+					),
+				),
+				.handle = impl_manager_add_and_activate_connection,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"DeactivateConnection",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"),
+					),
+				),
+				.handle = impl_manager_deactivate_connection,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Sleep",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("sleep", "b"),
+					),
+				),
+				.handle = impl_manager_sleep,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Enable",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("enable", "b"),
+					),
+				),
+				.handle = impl_manager_enable,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetPermissions",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("permissions", "a{ss}"),
+					),
+				),
+				.handle = impl_manager_get_permissions,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"SetLogging",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("level",   "s"),
+						NM_DEFINE_GDBUS_ARG_INFO ("domains", "s"),
+					),
+				),
+				.handle = impl_manager_set_logging,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetLogging",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("level",   "s"),
+						NM_DEFINE_GDBUS_ARG_INFO ("domains", "s"),
+					),
+				),
+				.handle = impl_manager_get_logging,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"CheckConnectivity",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connectivity", "u"),
+					),
+				),
+				.handle = impl_manager_check_connectivity,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"state",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("state", "u"),
+					),
+				),
+				.handle = impl_manager_state,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"CheckpointCreate",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("devices",          "ao"),
+						NM_DEFINE_GDBUS_ARG_INFO ("rollback_timeout", "u"),
+						NM_DEFINE_GDBUS_ARG_INFO ("flags",            "u"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"),
+					),
+				),
+				.handle = impl_manager_checkpoint_create,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"CheckpointDestroy",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"),
+					),
+				),
+				.handle = impl_manager_checkpoint_destroy,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"CheckpointRollback",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("result", "a{su}"),
+					),
+				),
+				.handle = impl_manager_checkpoint_rollback,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"CheckpointAdjustRollbackTimeout",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("add_timeout", "u"),
+					),
+				),
+				.handle = impl_manager_checkpoint_adjust_rollback_timeout,
+			),
+		),
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+			&signal_info_check_permissions,
+			&signal_info_state_changed,
+			&signal_info_device_added,
+			&signal_info_device_removed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Devices",                    "ao",    NM_MANAGER_DEVICES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("AllDevices",                 "ao",    NM_MANAGER_ALL_DEVICES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Checkpoints",                "ao",    NM_MANAGER_CHECKPOINTS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("NetworkingEnabled",          "b",     NM_MANAGER_NETWORKING_ENABLED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("WirelessEnabled",            "b",     NM_MANAGER_WIRELESS_ENABLED,              NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI,               NM_AUDIT_OP_RADIO_CONTROL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("WirelessHardwareEnabled",    "b",     NM_MANAGER_WIRELESS_HARDWARE_ENABLED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("WwanEnabled",                "b",     NM_MANAGER_WWAN_ENABLED,                  NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN,               NM_AUDIT_OP_RADIO_CONTROL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("WwanHardwareEnabled",        "b",     NM_MANAGER_WWAN_HARDWARE_ENABLED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("WimaxEnabled",               "b",     NM_MANAGER_WIMAX_ENABLED,                 NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX,              NM_AUDIT_OP_RADIO_CONTROL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("WimaxHardwareEnabled",       "b",     NM_MANAGER_WIMAX_HARDWARE_ENABLED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("ActiveConnections",          "ao",    NM_MANAGER_ACTIVE_CONNECTIONS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("PrimaryConnection",          "o",     NM_MANAGER_PRIMARY_CONNECTION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("PrimartConnectionType",      "s",     NM_MANAGER_PRIMARY_CONNECTION_TYPE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Metered",                    "u",     NM_MANAGER_METERED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("ActivatingConnection",       "o",     NM_MANAGER_ACTIVATING_CONNECTION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Startup",                    "b",     NM_MANAGER_STARTUP),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Version",                    "s",     NM_MANAGER_VERSION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Capabilities",               "u",     NM_MANAGER_CAPABILITIES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("State",                      "u",     NM_MANAGER_STATE),
+			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_READWRITABLE_L ("GlobalDnsConfiguration",     "a{sv}", NM_MANAGER_GLOBAL_DNS_CONFIGURATION,      NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS,        NM_AUDIT_OP_NET_CONTROL),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_manager_class_init (NMManagerClass *manager_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (manager_class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (manager_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (manager_class);
 
-	exported_object_class->export_path = NM_DBUS_PATH;
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC (NM_DBUS_PATH);
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_manager);
 
-	/* virtual methods */
 	object_class->constructed = constructed;
 	object_class->set_property = set_property;
 	object_class->get_property = get_property;
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 
-	/* properties */
 	obj_properties[PROP_VERSION] =
 	    g_param_spec_string (NM_MANAGER_VERSION, "", "",
 	                         NULL,
@@ -6898,11 +7216,11 @@ nm_manager_class_init (NMManagerClass *manager_class)
 	                         G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_CAPABILITIES] =
-		g_param_spec_variant (NM_MANAGER_CAPABILITIES, "", "",
-		                      G_VARIANT_TYPE ("au"),
-		                      NULL,
-		                      G_PARAM_READABLE |
-		                      G_PARAM_STATIC_STRINGS);
+	    g_param_spec_variant (NM_MANAGER_CAPABILITIES, "", "",
+	                          G_VARIANT_TYPE ("au"),
+	                          NULL,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_STATE] =
 	    g_param_spec_uint (NM_MANAGER_STATE, "", "",
@@ -7053,11 +7371,17 @@ nm_manager_class_init (NMManagerClass *manager_class)
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_CHECKPOINTS] =
+	    g_param_spec_boxed (NM_MANAGER_CHECKPOINTS, "", "",
+	                        G_TYPE_STRV,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	/* signals */
 
-	/* D-Bus exported; emitted only for realized devices */
+	/* emitted only for realized devices */
 	signals[DEVICE_ADDED] =
 	    g_signal_new (NM_MANAGER_DEVICE_ADDED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
@@ -7073,7 +7397,7 @@ nm_manager_class_init (NMManagerClass *manager_class)
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_OBJECT);
 
-	/* D-Bus exported; emitted only for realized devices when a device
+	/* emitted only for realized devices when a device
 	 * becomes unrealized or removed */
 	signals[DEVICE_REMOVED] =
 	    g_signal_new (NM_MANAGER_DEVICE_REMOVED,
@@ -7090,20 +7414,6 @@ nm_manager_class_init (NMManagerClass *manager_class)
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_OBJECT);
 
-	signals[STATE_CHANGED] =
-	    g_signal_new (NM_MANAGER_STATE_CHANGED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1, G_TYPE_UINT);
-
-	signals[CHECK_PERMISSIONS] =
-	    g_signal_new (NM_MANAGER_CHECK_PERMISSIONS,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 0);
-
 	signals[ACTIVE_CONNECTION_ADDED] =
 	    g_signal_new (NM_MANAGER_ACTIVE_CONNECTION_ADDED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
@@ -7124,25 +7434,4 @@ nm_manager_class_init (NMManagerClass *manager_class)
 	                  G_SIGNAL_RUN_FIRST,
 	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 0);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (manager_class),
-	                                        NMDBUS_TYPE_MANAGER_SKELETON,
-	                                        "Reload", impl_manager_reload,
-	                                        "GetDevices", impl_manager_get_devices,
-	                                        "GetAllDevices", impl_manager_get_all_devices,
-	                                        "GetDeviceByIpIface", impl_manager_get_device_by_ip_iface,
-	                                        "ActivateConnection", impl_manager_activate_connection,
-	                                        "AddAndActivateConnection", impl_manager_add_and_activate_connection,
-	                                        "DeactivateConnection", impl_manager_deactivate_connection,
-	                                        "Sleep", impl_manager_sleep,
-	                                        "Enable", impl_manager_enable,
-	                                        "GetPermissions", impl_manager_get_permissions,
-	                                        "SetLogging", impl_manager_set_logging,
-	                                        "GetLogging", impl_manager_get_logging,
-	                                        "CheckConnectivity", impl_manager_check_connectivity,
-	                                        "state", impl_manager_get_state,
-	                                        "CheckpointCreate", impl_manager_checkpoint_create,
-	                                        "CheckpointDestroy", impl_manager_checkpoint_destroy,
-	                                        "CheckpointRollback", impl_manager_checkpoint_rollback,
-	                                        NULL);
 }
diff --git a/src/nm-manager.h b/src/nm-manager.h
index b4587e08..54444532 100644
--- a/src/nm-manager.h
+++ b/src/nm-manager.h
@@ -22,9 +22,9 @@
 #ifndef __NETWORKMANAGER_MANAGER_H__
 #define __NETWORKMANAGER_MANAGER_H__
 
-#include "nm-exported-object.h"
 #include "settings/nm-settings-connection.h"
-#include "nm-utils/c-list.h"
+#include "c-list/src/c-list.h"
+#include "nm-dbus-manager.h"
 
 #define NM_TYPE_MANAGER            (nm_manager_get_type ())
 #define NM_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MANAGER, NMManager))
@@ -55,18 +55,16 @@
 #define NM_MANAGER_METERED "metered"
 #define NM_MANAGER_GLOBAL_DNS_CONFIGURATION "global-dns-configuration"
 #define NM_MANAGER_ALL_DEVICES "all-devices"
+#define NM_MANAGER_CHECKPOINTS "checkpoints"
 
 /* Not exported */
 #define NM_MANAGER_SLEEPING "sleeping"
 
-/* signals */
-#define NM_MANAGER_CHECK_PERMISSIONS         "check-permissions"
+/* Signals */
 #define NM_MANAGER_DEVICE_ADDED              "device-added"
 #define NM_MANAGER_DEVICE_REMOVED            "device-removed"
-#define NM_MANAGER_STATE_CHANGED             "state-changed"
 #define NM_MANAGER_USER_PERMISSIONS_CHANGED  "user-permissions-changed"
 
-/* Internal signals */
 #define NM_MANAGER_ACTIVE_CONNECTION_ADDED   "active-connection-added"
 #define NM_MANAGER_ACTIVE_CONNECTION_REMOVED "active-connection-removed"
 #define NM_MANAGER_CONFIGURE_QUIT            "configure-quit"
@@ -85,13 +83,14 @@ gboolean      nm_manager_start                         (NMManager *manager,
                                                         GError **error);
 void          nm_manager_stop                          (NMManager *manager);
 NMState       nm_manager_get_state                     (NMManager *manager);
+
 const CList * nm_manager_get_active_connections        (NMManager *manager);
 
 #define nm_manager_for_each_active_connection(manager, iter, tmp_list) \
 	for (tmp_list = nm_manager_get_active_connections (manager), \
 	     iter = c_list_entry (tmp_list->next, NMActiveConnection, active_connections_lst); \
 	     ({ \
-	         gboolean _has_next = (&iter->active_connections_lst != tmp_list); \
+	         const gboolean _has_next = (&iter->active_connections_lst != tmp_list); \
 	         \
 	         if (!_has_next) \
 	             iter = NULL; \
@@ -107,7 +106,19 @@ void          nm_manager_write_device_state (NMManager *manager);
 
 /* Device handling */
 
-const GSList *      nm_manager_get_devices             (NMManager *manager);
+const CList *       nm_manager_get_devices             (NMManager *manager);
+
+#define nm_manager_for_each_device(manager, iter, tmp_list) \
+	for (tmp_list = nm_manager_get_devices (manager), \
+	     iter = c_list_entry (tmp_list->next, NMDevice, devices_lst); \
+	     ({ \
+	         const gboolean _has_next = (&iter->devices_lst != tmp_list); \
+	         \
+	         if (!_has_next) \
+	             iter = NULL; \
+	         _has_next; \
+	    }); \
+	    iter = c_list_entry (iter->devices_lst.next, NMDevice, devices_lst))
 
 NMDevice *          nm_manager_get_device_by_ifindex   (NMManager *manager,
                                                         int ifindex);
@@ -136,6 +147,7 @@ NMActiveConnection *nm_manager_activate_connection     (NMManager *manager,
                                                         NMDevice *device,
                                                         NMAuthSubject *subject,
                                                         NMActivationType activation_type,
+                                                        NMActivationReason activation_reason,
                                                         GError **error);
 
 gboolean            nm_manager_deactivate_connection   (NMManager *manager,
@@ -152,4 +164,13 @@ gboolean            nm_manager_remove_device (NMManager *self,
                                               const char *ifname,
                                               NMDeviceType device_type);
 
+void nm_manager_dbus_set_property_handle (NMDBusObject *obj,
+                                          const NMDBusInterfaceInfoExtended *interface_info,
+                                          const NMDBusPropertyInfoExtended *property_info,
+                                          GDBusConnection *connection,
+                                          const char *sender,
+                                          GDBusMethodInvocation *invocation,
+                                          GVariant *value,
+                                          gpointer user_data);
+
 #endif /* __NETWORKMANAGER_MANAGER_H__ */
diff --git a/src/nm-netns.c b/src/nm-netns.c
index 96ab2b35..5952a490 100644
--- a/src/nm-netns.c
+++ b/src/nm-netns.c
@@ -38,7 +38,6 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 typedef struct {
 	NMPlatform *platform;
 	NMPNetns *platform_netns;
-	bool log_with_ptr;
 } NMNetnsPrivate;
 
 struct _NMNetns {
@@ -113,13 +112,10 @@ constructed (GObject *object)
 {
 	NMNetns *self = NM_NETNS (object);
 	NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE (self);
-	gboolean log_with_ptr;
 
 	if (!priv->platform)
 		g_return_if_reached ();
 
-	log_with_ptr = nm_platform_get_log_with_ptr (priv->platform);
-
 	priv->platform_netns = nm_platform_netns_get (priv->platform);
 
 	G_OBJECT_CLASS (nm_netns_parent_class)->constructed (object);
diff --git a/src/nm-pacrunner-manager.c b/src/nm-pacrunner-manager.c
index 36f517a2..caae8b20 100644
--- a/src/nm-pacrunner-manager.c
+++ b/src/nm-pacrunner-manager.c
@@ -27,7 +27,7 @@
 #include "nm-proxy-config.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
-#include "nm-utils/c-list.h"
+#include "c-list/src/c-list.h"
 
 #define PACRUNNER_DBUS_SERVICE "org.pacrunner"
 #define PACRUNNER_DBUS_INTERFACE "org.pacrunner.Manager"
@@ -466,19 +466,18 @@ static void
 pacrunner_remove_done (GObject *source, GAsyncResult *res, gpointer user_data)
 {
 	Config *config = user_data;
-	NMPacrunnerManager *self;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *ret = NULL;
 
 	ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
-	if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	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;
+	}
 
-	self = NM_PACRUNNER_MANAGER (config->manager_maybe_dangling);
-	if (!ret)
-		_LOG2D (config, "remove failed: %s", error->message);
-	else
-		_LOG2D (config, "removed");
+	_LOG2D (config, "removed");
 
 out:
 	config_unref (config);
diff --git a/src/nm-policy.c b/src/nm-policy.c
index b73e04cb..55b6caf6 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -337,7 +337,7 @@ device_ip6_prefix_delegated (NMDevice *device,
 		/* Allocate a delegation delegation for new prefix. */
 		g_array_set_size (priv->ip6_prefix_delegations, i + 1);
 		delegation = &g_array_index (priv->ip6_prefix_delegations, IP6PrefixDelegation, i);
-		delegation->subnets = g_hash_table_new (NULL, NULL);
+		delegation->subnets = g_hash_table_new (nm_direct_hash, NULL);
 		delegation->next_subnet = 0;
 	}
 
@@ -385,7 +385,8 @@ get_best_ip_device (NMPolicy *self,
                     gboolean fully_activated)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	const GSList *iter;
+	const CList *tmp_lst;
+	NMDevice *device;
 	NMDevice *best_device;
 	NMDevice *prev_device;
 	guint32 best_metric = G_MAXUINT32;
@@ -400,8 +401,7 @@ get_best_ip_device (NMPolicy *self,
 	              ? (fully_activated ? priv->default_device4 : priv->activating_device4)
 	              : (fully_activated ? priv->default_device6 : priv->activating_device6);
 
-	for (iter = nm_manager_get_devices (priv->manager); iter; iter = iter->next) {
-		NMDevice *device = NM_DEVICE (iter->data);
+	nm_manager_for_each_device (priv->manager, device, tmp_lst) {
 		NMDeviceState state;
 		const NMPObject *r;
 		NMConnection *connection;
@@ -418,7 +418,7 @@ get_best_ip_device (NMPolicy *self,
 
 		r = nm_device_get_best_default_route (device, addr_family);
 		if (r) {
-			/* XXX: the best route might have rt_source NM_IP_CONFIG_SOURCE_VPN,
+			/* NOTE: the best route might have rt_source NM_IP_CONFIG_SOURCE_VPN,
 			 * which means it was injected by a VPN, not added by device.
 			 *
 			 * In this case, is it really the best device? Why do we even need the best
@@ -461,15 +461,15 @@ static gboolean
 all_devices_not_active (NMPolicy *self)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	const GSList *iter = nm_manager_get_devices (priv->manager);
+	const CList *tmp_lst;
+	NMDevice *device;
 
-	while (iter != NULL) {
+	nm_manager_for_each_device (priv->manager, device, tmp_lst) {
 		NMDeviceState state;
 
-		state = nm_device_get_state (NM_DEVICE (iter->data));
+		state = nm_device_get_state (device);
 		if (   state <= NM_DEVICE_STATE_DISCONNECTED
 		    || state >= NM_DEVICE_STATE_DEACTIVATING) {
-			iter = g_slist_next (iter);
 			continue;
 		}
 		return FALSE;
@@ -1065,22 +1065,19 @@ update_ip_dns (NMPolicy *self, int addr_family)
 	gpointer ip_config;
 	const char *ip_iface = NULL;
 	NMVpnConnection *vpn = NULL;
-	NMDnsIPConfigType dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE;
 
 	nm_assert_addr_family (addr_family);
 
 	ip_config = get_best_ip_config (self, addr_family, &ip_iface, NULL, NULL, &vpn);
 	if (ip_config) {
-		if (vpn)
-			dns_type = NM_DNS_IP_CONFIG_TYPE_VPN;
-
 		/* Tell the DNS manager this config is preferred by re-adding it with
 		 * a different IP config type.
 		 */
-		nm_dns_manager_add_ip_config (NM_POLICY_GET_PRIVATE (self)->dns_manager,
-		                              ip_iface,
+		nm_dns_manager_set_ip_config (NM_POLICY_GET_PRIVATE (self)->dns_manager,
 		                              ip_config,
-		                              dns_type);
+		                              vpn
+		                                ? NM_DNS_IP_CONFIG_TYPE_VPN
+		                                : NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE);
 	}
 
 	if (addr_family == AF_INET6)
@@ -1195,7 +1192,7 @@ auto_activate_device (NMPolicy *self,
 	gs_free NMSettingsConnection **connections = NULL;
 	guint i, len;
 	gs_free_error GError *error = NULL;
-	NMAuthSubject *subject;
+	gs_unref_object NMAuthSubject *subject = NULL;
 	NMActiveConnection *ac;
 
 	nm_assert (NM_IS_POLICY (self));
@@ -1255,6 +1252,7 @@ auto_activate_device (NMPolicy *self,
 	                                     device,
 	                                     subject,
 	                                     NM_ACTIVATION_TYPE_MANAGED,
+	                                     NM_ACTIVATION_REASON_AUTOCONNECT,
 	                                     &error);
 	if (!ac) {
 		_LOGI (LOGD_DEVICE, "connection '%s' auto-activation failed: %s",
@@ -1271,13 +1269,11 @@ auto_activate_device (NMPolicy *self,
 	 * activation fails in early stages without changing device
 	 * state.
 	 */
-	if (nm_g_hash_table_add (priv->pending_active_connections, ac)) {
+	if (g_hash_table_add (priv->pending_active_connections, ac)) {
 		g_signal_connect (ac, NM_ACTIVE_CONNECTION_STATE_CHANGED,
 		                  G_CALLBACK (pending_ac_state_changed), g_object_ref (self));
 		g_object_weak_ref (G_OBJECT (ac), (GWeakNotify) pending_ac_gone, self);
 	}
-
-	g_object_unref (subject);
 }
 
 static gboolean
@@ -1361,7 +1357,7 @@ process_secondaries (NMPolicy *self,
 			if (connected) {
 				_LOGD (LOGD_DEVICE, "secondary connection '%s' succeeded; active path '%s'",
 				       nm_active_connection_get_settings_connection_id (active),
-				       nm_exported_object_get_path (NM_EXPORTED_OBJECT (active)));
+				       nm_dbus_object_get_path (NM_DBUS_OBJECT (active)));
 
 				/* Secondary connection activated */
 				secondary_data->secondaries = g_slist_remove (secondary_data->secondaries, secondary_active);
@@ -1377,7 +1373,7 @@ process_secondaries (NMPolicy *self,
 			} else {
 				_LOGD (LOGD_DEVICE, "secondary connection '%s' failed; active path '%s'",
 				       nm_active_connection_get_settings_connection_id (active),
-				       nm_exported_object_get_path (NM_EXPORTED_OBJECT (active)));
+				       nm_dbus_object_get_path (NM_DBUS_OBJECT (active)));
 
 				/* Secondary connection failed -> do not watch other connections */
 				priv->pending_secondaries = g_slist_remove (priv->pending_secondaries, secondary_data);
@@ -1673,10 +1669,11 @@ activate_secondary_connections (NMPolicy *self,
 		ac = nm_manager_activate_connection (priv->manager,
 		                                     settings_con,
 		                                     NULL,
-		                                     nm_exported_object_get_path (NM_EXPORTED_OBJECT (req)),
+		                                     nm_dbus_object_get_path (NM_DBUS_OBJECT (req)),
 		                                     device,
 		                                     nm_active_connection_get_subject (NM_ACTIVE_CONNECTION (req)),
 		                                     NM_ACTIVATION_TYPE_MANAGED,
+		                                     nm_active_connection_get_activation_reason (NM_ACTIVE_CONNECTION (req)),
 		                                     &error);
 		if (ac)
 			secondary_ac_list = g_slist_append (secondary_ac_list, g_object_ref (ac));
@@ -1711,7 +1708,6 @@ device_state_changed (NMDevice *device,
 	NMPolicy *self = _PRIV_TO_SELF (priv);
 	NMActiveConnection *ac;
 	NMSettingsConnection *connection = nm_device_get_settings_connection (device);
-	const char *ip_iface = nm_device_get_ip_iface (device);
 	NMIP4Config *ip4_config;
 	NMIP6Config *ip6_config;
 	NMSettingConnection *s_con = NULL;
@@ -1808,10 +1804,10 @@ device_state_changed (NMDevice *device,
 
 		ip4_config = nm_device_get_ip4_config (device);
 		if (ip4_config)
-			nm_dns_manager_add_ip_config (priv->dns_manager, ip_iface, ip4_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+			nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip4_config), NM_DNS_IP_CONFIG_TYPE_DEFAULT);
 		ip6_config = nm_device_get_ip6_config (device);
 		if (ip6_config)
-			nm_dns_manager_add_ip_config (priv->dns_manager, ip_iface, ip6_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+			nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip6_config), NM_DNS_IP_CONFIG_TYPE_DEFAULT);
 
 		update_routing_and_dns (self, FALSE);
 
@@ -1904,50 +1900,24 @@ device_state_changed (NMDevice *device,
 }
 
 static void
-device_ip4_config_changed (NMDevice *device,
-                           NMIP4Config *new_config,
-                           NMIP4Config *old_config,
-                           gpointer user_data)
+device_ip_config_changed (NMDevice *device,
+                          NMIPConfig *new_config,
+                          NMIPConfig *old_config,
+                          gpointer user_data)
 {
 	NMPolicyPrivate *priv = user_data;
 	NMPolicy *self = _PRIV_TO_SELF (priv);
-	const char *ip_iface = nm_device_get_ip_iface (device);
-
-	nm_dns_manager_begin_updates (priv->dns_manager, __func__);
-
-	/* We catch already all the IP events registering on the device state changes but
-	 * the ones where the IP changes but the device state keep stable (i.e., activated):
-	 * ignore IP config changes but when the device is in activated state.
-	 * Prevents unecessary changes to DNS information.
-	 */
-	if (nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) {
-		if (old_config != new_config) {
-			if (old_config)
-				nm_dns_manager_remove_ip_config (priv->dns_manager, old_config);
-			if (new_config)
-				nm_dns_manager_add_ip_config (priv->dns_manager, ip_iface, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
-		}
-		update_ip_dns (self, AF_INET);
-		update_ip4_routing (self, TRUE);
-		update_system_hostname (self, "ip4 conf");
-	} else {
-		/* Old configs get removed immediately */
-		if (old_config)
-			nm_dns_manager_remove_ip_config (priv->dns_manager, old_config);
-	}
+	int addr_family;
 
-	nm_dns_manager_end_updates (priv->dns_manager, __func__);
-}
+	nm_assert (new_config || old_config);
+	nm_assert (!new_config || NM_IS_IP_CONFIG (new_config));
+	nm_assert (!old_config || NM_IS_IP_CONFIG (old_config));
 
-static void
-device_ip6_config_changed (NMDevice *device,
-                           NMIP6Config *new_config,
-                           NMIP6Config *old_config,
-                           gpointer user_data)
-{
-	NMPolicyPrivate *priv = user_data;
-	NMPolicy *self = _PRIV_TO_SELF (priv);
-	const char *ip_iface = nm_device_get_ip_iface (device);
+	if (new_config) {
+		addr_family = nm_ip_config_get_addr_family (new_config);
+		nm_assert (!old_config || addr_family == nm_ip_config_get_addr_family (old_config));
+	} else
+		addr_family = nm_ip_config_get_addr_family (old_config);
 
 	nm_dns_manager_begin_updates (priv->dns_manager, __func__);
 
@@ -1958,18 +1928,24 @@ device_ip6_config_changed (NMDevice *device,
 	 */
 	if (nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) {
 		if (old_config != new_config) {
-			if (old_config)
-				nm_dns_manager_remove_ip_config (priv->dns_manager, old_config);
 			if (new_config)
-				nm_dns_manager_add_ip_config (priv->dns_manager, ip_iface, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+				nm_dns_manager_set_ip_config (priv->dns_manager, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+			if (old_config)
+				nm_dns_manager_set_ip_config (priv->dns_manager, old_config, NM_DNS_IP_CONFIG_TYPE_REMOVED);
 		}
-		update_ip_dns (self, AF_INET6);
-		update_ip6_routing (self, TRUE);
-		update_system_hostname (self, "ip6 conf");
+		update_ip_dns (self, addr_family);
+		if (addr_family == AF_INET)
+			update_ip4_routing (self, TRUE);
+		else
+			update_ip6_routing (self, TRUE);
+		update_system_hostname (self,
+		                        addr_family == AF_INET
+		                          ? "ip4 conf"
+		                          : "ip6 conf");
 	} else {
 		/* Old configs get removed immediately */
 		if (old_config)
-			nm_dns_manager_remove_ip_config (priv->dns_manager, old_config);
+			nm_dns_manager_set_ip_config (priv->dns_manager, old_config, NM_DNS_IP_CONFIG_TYPE_REMOVED);
 	}
 
 	nm_dns_manager_end_updates (priv->dns_manager, __func__);
@@ -2012,8 +1988,8 @@ devices_list_register (NMPolicy *self, NMDevice *device)
 
 	/* Connect state-changed with _after, so that the handler is invoked after other handlers. */
 	g_signal_connect_after (device, NM_DEVICE_STATE_CHANGED,          (GCallback) device_state_changed, priv);
-	g_signal_connect       (device, NM_DEVICE_IP4_CONFIG_CHANGED,     (GCallback) device_ip4_config_changed, priv);
-	g_signal_connect       (device, NM_DEVICE_IP6_CONFIG_CHANGED,     (GCallback) device_ip6_config_changed, priv);
+	g_signal_connect       (device, NM_DEVICE_IP4_CONFIG_CHANGED,     (GCallback) device_ip_config_changed, priv);
+	g_signal_connect       (device, NM_DEVICE_IP6_CONFIG_CHANGED,     (GCallback) device_ip_config_changed, priv);
 	g_signal_connect       (device, NM_DEVICE_IP6_PREFIX_DELEGATED,   (GCallback) device_ip6_prefix_delegated, priv);
 	g_signal_connect       (device, NM_DEVICE_IP6_SUBNET_NEEDED,      (GCallback) device_ip6_subnet_needed, priv);
 	g_signal_connect       (device, "notify::" NM_DEVICE_AUTOCONNECT, (GCallback) device_autoconnect_changed, priv);
@@ -2030,7 +2006,7 @@ device_added (NMManager *manager, NMDevice *device, gpointer user_data)
 
 	priv = NM_POLICY_GET_PRIVATE (self);
 
-	if (!nm_g_hash_table_add (priv->devices, device))
+	if (!g_hash_table_add (priv->devices, device))
 		g_return_if_reached ();
 
 	devices_list_register (self, device);
@@ -2068,21 +2044,16 @@ vpn_connection_activated (NMPolicy *self, NMVpnConnection *vpn)
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 	NMIP4Config *ip4_config;
 	NMIP6Config *ip6_config;
-	const char *ip_iface;
 
 	nm_dns_manager_begin_updates (priv->dns_manager, __func__);
 
-	ip_iface = nm_vpn_connection_get_ip_iface (vpn, TRUE);
-
-	/* Add the VPN connection's IP configs from DNS */
-
 	ip4_config = nm_vpn_connection_get_ip4_config (vpn);
 	if (ip4_config)
-		nm_dns_manager_add_ip_config (priv->dns_manager, ip_iface, ip4_config, NM_DNS_IP_CONFIG_TYPE_VPN);
+		nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip4_config), NM_DNS_IP_CONFIG_TYPE_VPN);
 
 	ip6_config = nm_vpn_connection_get_ip6_config (vpn);
 	if (ip6_config)
-		nm_dns_manager_add_ip_config (priv->dns_manager, ip_iface, ip6_config, NM_DNS_IP_CONFIG_TYPE_VPN);
+		nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip6_config), NM_DNS_IP_CONFIG_TYPE_VPN);
 
 	update_routing_and_dns (self, TRUE);
 
@@ -2099,16 +2070,12 @@ vpn_connection_deactivated (NMPolicy *self, NMVpnConnection *vpn)
 	nm_dns_manager_begin_updates (priv->dns_manager, __func__);
 
 	ip4_config = nm_vpn_connection_get_ip4_config (vpn);
-	if (ip4_config) {
-		/* Remove the VPN connection's IP4 config from DNS */
-		nm_dns_manager_remove_ip_config (priv->dns_manager, ip4_config);
-	}
+	if (ip4_config)
+		nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip4_config), NM_DNS_IP_CONFIG_TYPE_REMOVED);
 
 	ip6_config = nm_vpn_connection_get_ip6_config (vpn);
-	if (ip6_config) {
-		/* Remove the VPN connection's IP6 config from DNS */
-		nm_dns_manager_remove_ip_config (priv->dns_manager, ip6_config);
-	}
+	if (ip6_config)
+		nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip6_config), NM_DNS_IP_CONFIG_TYPE_REMOVED);
 
 	update_routing_and_dns (self, TRUE);
 
@@ -2148,6 +2115,7 @@ vpn_connection_retry_after_failure (NMVpnConnection *vpn, NMPolicy *self)
 	                                     NULL,
 	                                     nm_active_connection_get_subject (ac),
 	                                     NM_ACTIVATION_TYPE_MANAGED,
+	                                     nm_active_connection_get_activation_reason (ac),
 	                                     &error)) {
 		_LOGW (LOGD_DEVICE, "VPN '%s' reconnect failed: %s",
 		       nm_settings_connection_get_id (connection),
@@ -2217,12 +2185,13 @@ schedule_activate_all_cb (gpointer user_data)
 {
 	NMPolicy *self = user_data;
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	const GSList *iter;
+	const CList *tmp_lst;
+	NMDevice *device;
 
 	priv->schedule_activate_all_id = 0;
 
-	for (iter = nm_manager_get_devices (priv->manager); iter; iter = g_slist_next (iter))
-		schedule_activate_check (self, iter->data);
+	nm_manager_for_each_device (priv->manager, device, tmp_lst)
+		schedule_activate_check (self, device);
 
 	return G_SOURCE_REMOVE;
 }
@@ -2256,7 +2225,8 @@ firewall_state_changed (NMFirewallManager *manager,
 {
 	NMPolicy *self = (NMPolicy *) user_data;
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	const GSList *iter;
+	const CList *tmp_lst;
+	NMDevice *device;
 
 	if (initialized_now) {
 		/* the firewall manager was initializing, but all requests
@@ -2269,8 +2239,8 @@ firewall_state_changed (NMFirewallManager *manager,
 		return;
 
 	/* add interface of each device to correct zone */
-	for (iter = nm_manager_get_devices (priv->manager); iter; iter = g_slist_next (iter))
-		nm_device_update_firewall_zone (iter->data);
+	nm_manager_for_each_device (priv->manager, device, tmp_lst)
+		nm_device_update_firewall_zone (device);
 }
 
 static void
@@ -2315,14 +2285,13 @@ connection_updated (NMSettings *settings,
 {
 	NMPolicyPrivate *priv = user_data;
 	NMPolicy *self = _PRIV_TO_SELF (priv);
-	const GSList *iter;
+	const CList *tmp_lst;
 	NMDevice *device = NULL;
+	NMDevice *dev;
 
 	if (by_user) {
 		/* find device with given connection */
-		for (iter = nm_manager_get_devices (priv->manager); iter; iter = g_slist_next (iter)) {
-			NMDevice *dev = NM_DEVICE (iter->data);
-
+		nm_manager_for_each_device (priv->manager, dev, tmp_lst) {
 			if (nm_device_get_settings_connection (dev) == connection) {
 				device = dev;
 				break;
@@ -2350,10 +2319,9 @@ _deactivate_if_active (NMPolicy *self, NMSettingsConnection *connection)
 	nm_assert (NM_IS_SETTINGS_CONNECTION (connection));
 
 	nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
-		NMActiveConnectionState state = nm_active_connection_get_state (ac);
 
 		if (   nm_active_connection_get_settings_connection (ac) == connection
-		    && (state <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) {
+		    && (nm_active_connection_get_state (ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) {
 			if (!nm_manager_deactivate_connection (priv->manager,
 			                                       ac,
 			                                       NM_DEVICE_STATE_REASON_CONNECTION_REMOVED,
@@ -2388,7 +2356,7 @@ connection_flags_changed (NMSettings *settings,
 	NMPolicy *self = _PRIV_TO_SELF (priv);
 
 	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
-	                  NM_SETTINGS_CONNECTION_FLAGS_VISIBLE)) {
+	                  NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) {
 		if (!nm_settings_connection_autoconnect_is_blocked (connection))
 			schedule_activate_all (self);
 	} else
@@ -2520,8 +2488,8 @@ nm_policy_init (NMPolicy *self)
 	else /* default - full mode */
 		priv->hostname_mode = NM_POLICY_HOSTNAME_MODE_FULL;
 
-	priv->devices = g_hash_table_new (NULL, NULL);
-	priv->pending_active_connections = g_hash_table_new (NULL, NULL);
+	priv->devices = g_hash_table_new (nm_direct_hash, NULL);
+	priv->pending_active_connections = g_hash_table_new (nm_direct_hash, NULL);
 	priv->ip6_prefix_delegations = g_array_new (FALSE, FALSE, sizeof (IP6PrefixDelegation));
 	g_array_set_clear_func (priv->ip6_prefix_delegations, clear_ip6_prefix_delegation);
 }
diff --git a/src/nm-session-monitor.c b/src/nm-session-monitor.c
index 20781bd4..13ccbd48 100644
--- a/src/nm-session-monitor.c
+++ b/src/nm-session-monitor.c
@@ -137,7 +137,10 @@ st_sd_init (NMSessionMonitor *monitor)
 static void
 st_sd_finalize (NMSessionMonitor *monitor)
 {
-	g_clear_pointer (&monitor->sd.monitor, sd_login_monitor_unref);
+	if (monitor->sd.monitor) {
+		sd_login_monitor_unref (monitor->sd.monitor);
+		monitor->sd.monitor = NULL;
+	}
 	g_source_remove (monitor->sd.watch);
 }
 #endif /* SESSION_TRACKING_SYSTEMD */
@@ -258,7 +261,7 @@ ck_init (NMSessionMonitor *monitor)
 
 	if (g_file_query_exists (file, NULL)) {
 		if ((monitor->ck.monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error))) {
-			monitor->ck.cache = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
+			monitor->ck.cache = g_hash_table_new_full (nm_direct_hash, NULL, NULL, g_free);
 			g_signal_connect (monitor->ck.monitor,
 			                  "changed",
 			                  G_CALLBACK (ck_changed),
diff --git a/src/nm-test-utils-core.h b/src/nm-test-utils-core.h
index 58beadcd..da24992e 100644
--- a/src/nm-test-utils-core.h
+++ b/src/nm-test-utils-core.h
@@ -30,6 +30,14 @@
 
 /*****************************************************************************/
 
+#define NMTST_EXPECT_NM(level, msg)     NMTST_EXPECT ("NetworkManager", level, msg)
+
+#define NMTST_EXPECT_NM_ERROR(msg)      NMTST_EXPECT_NM (G_LOG_LEVEL_MESSAGE, "*<error> [*] "msg)
+#define NMTST_EXPECT_NM_WARN(msg)       NMTST_EXPECT_NM (G_LOG_LEVEL_MESSAGE, "*<warn>  [*] "msg)
+#define NMTST_EXPECT_NM_INFO(msg)       NMTST_EXPECT_NM (G_LOG_LEVEL_INFO,    "*<info>  [*] "msg)
+#define NMTST_EXPECT_NM_DEBUG(msg)      NMTST_EXPECT_NM (G_LOG_LEVEL_DEBUG,   "*<debug> [*] "msg)
+#define NMTST_EXPECT_NM_TRACE(msg)      NMTST_EXPECT_NM (G_LOG_LEVEL_DEBUG,   "*<trace> [*] "msg)
+
 static inline void
 nmtst_init_with_logging (int *argc, char ***argv, const char *log_level, const char *log_domains)
 {
@@ -313,18 +321,6 @@ nmtst_ip4_config_new (int ifindex)
 	return nm_ip4_config_new (multi_idx, ifindex);
 }
 
-static inline NMIP4Config *
-nmtst_ip4_config_clone (NMIP4Config *config)
-{
-	NMIP4Config *copy;
-
-	g_assert (config);
-	copy = nm_ip4_config_new (nm_ip4_config_get_multi_idx (config), -1);
-	g_assert (copy);
-	nm_ip4_config_replace (copy, config, NULL);
-	return copy;
-}
-
 #endif
 
 
@@ -340,18 +336,6 @@ nmtst_ip6_config_new (int ifindex)
 	return nm_ip6_config_new (multi_idx, ifindex);
 }
 
-static inline NMIP6Config *
-nmtst_ip6_config_clone (NMIP6Config *config)
-{
-	NMIP6Config *copy;
-
-	g_assert (config);
-	copy = nm_ip6_config_new (nm_ip6_config_get_multi_idx (config), -1);
-	g_assert (copy);
-	nm_ip6_config_replace (copy, config, NULL);
-	return copy;
-}
-
 #endif
 
 #endif /* __NM_TEST_UTILS_CORE_H__ */
diff --git a/src/nm-types.h b/src/nm-types.h
index 02163f87..e1991c28 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -21,24 +21,23 @@
 #ifndef __NETWORKMANAGER_TYPES_H__
 #define __NETWORKMANAGER_TYPES_H__
 
-#ifdef __NM_UTILS_PRIVATE_H__
-#error "nm-utils-private.h" must not be used outside of libnm-core/. Do you want "nm-core-internal.h"?
+#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON)
+#error Cannot use this header.
 #endif
 
 #define _NM_SD_MAX_CLIENT_ID_LEN (sizeof (guint32) + 128)
 
 /* core */
-typedef struct _NMExportedObject     NMExportedObject;
+typedef struct _NMDBusObject         NMDBusObject;
 typedef struct _NMActiveConnection   NMActiveConnection;
 typedef struct _NMAuditManager       NMAuditManager;
 typedef struct _NMVpnConnection      NMVpnConnection;
 typedef struct _NMActRequest         NMActRequest;
 typedef struct _NMAuthSubject        NMAuthSubject;
-typedef struct _NMBusManager         NMBusManager;
+typedef struct _NMDBusManager        NMDBusManager;
 typedef struct _NMConfig             NMConfig;
 typedef struct _NMConfigData         NMConfigData;
-typedef struct _NMArpingManager      NMArpingManager;
-typedef struct _NMConnectionProvider NMConnectionProvider;
+typedef struct _NMAcdManager         NMAcdManager;
 typedef struct _NMConnectivity       NMConnectivity;
 typedef struct _NMDevice             NMDevice;
 typedef struct _NMDhcp4Config        NMDhcp4Config;
@@ -76,6 +75,13 @@ typedef enum {
 } NMActivationType;
 
 typedef enum {
+	NM_ACTIVATION_REASON_UNSET = 0,
+	NM_ACTIVATION_REASON_AUTOCONNECT = 1,
+	NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES = 2,
+	NM_ACTIVATION_REASON_USER_REQUEST = 3,
+} NMActivationReason;
+
+typedef enum {
 	/* In priority order; higher number == higher priority */
 
 	NM_IP_CONFIG_SOURCE_UNKNOWN                 = 0,
@@ -95,6 +101,7 @@ typedef enum {
 	NM_IP_CONFIG_SOURCE_KERNEL,
 	NM_IP_CONFIG_SOURCE_SHARED,
 	NM_IP_CONFIG_SOURCE_IP4LL,
+	NM_IP_CONFIG_SOURCE_IP6LL,
 	NM_IP_CONFIG_SOURCE_PPP,
 	NM_IP_CONFIG_SOURCE_WWAN,
 	NM_IP_CONFIG_SOURCE_VPN,
@@ -157,7 +164,6 @@ typedef enum {
 	NM_LINK_TYPE_OPENVSWITCH,
 	NM_LINK_TYPE_PPP,
 	NM_LINK_TYPE_SIT,
-	NM_LINK_TYPE_TAP,
 	NM_LINK_TYPE_TUN,
 	NM_LINK_TYPE_VETH,
 	NM_LINK_TYPE_VLAN,
@@ -191,6 +197,7 @@ typedef enum {
 	NMP_OBJECT_TYPE_LNK_MACVLAN,
 	NMP_OBJECT_TYPE_LNK_MACVTAP,
 	NMP_OBJECT_TYPE_LNK_SIT,
+	NMP_OBJECT_TYPE_LNK_TUN,
 	NMP_OBJECT_TYPE_LNK_VLAN,
 	NMP_OBJECT_TYPE_LNK_VXLAN,
 
@@ -231,4 +238,6 @@ typedef struct _NMSettingsConnection NMSettingsConnection;
 /* utils */
 typedef struct _NMUtilsIPv6IfaceId   NMUtilsIPv6IfaceId;
 
+#define NM_SETTING_CONNECTION_MDNS_UNKNOWN ((NMSettingConnectionMdns) -42)
+
 #endif  /* NM_TYPES_H */
diff --git a/src/org.freedesktop.NetworkManager.conf b/src/org.freedesktop.NetworkManager.conf
index 6be1feb6..fa74b280 100644
--- a/src/org.freedesktop.NetworkManager.conf
+++ b/src/org.freedesktop.NetworkManager.conf
@@ -30,7 +30,7 @@
                 <allow send_destination="org.fedoraproject.FirewallD1"/>
 
                 <!-- Allow the custom name for the dnsmasq instance spawned by NM
-                     from the dns dnsmasq plugin to own it's dbus name, and for
+                     from the dns dnsmasq plugin to own its dbus name, and for
                      messages to be sent to it.
                  -->
                 <allow own="org.freedesktop.NetworkManager.dnsmasq"/>
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index be430152..06dd7e13 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -938,7 +938,6 @@ wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs)
 static void
 wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean running)
 {
-	;
 }
 
 static guint32
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index e5961c7e..0ed8fa06 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -21,6 +21,7 @@
 
 #include "nm-linux-platform.h"
 
+#include <poll.h>
 #include <endian.h>
 #include <errno.h>
 #include <unistd.h>
@@ -36,14 +37,14 @@
 #include <linux/if_link.h>
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
-#include <netlink/netlink.h>
-#include <netlink/msg.h>
+#include <linux/ip6_tunnel.h>
 #include <libudev.h>
 
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-setting-vlan.h"
 
+#include "nm-netlink.h"
 #include "nm-core-utils.h"
 #include "nmp-object.h"
 #include "nmp-netns.h"
@@ -79,10 +80,6 @@ enum {
 
 #define VLAN_FLAG_MVRP 0x8
 
-/* nm-internal error codes for libnl. Make sure they don't overlap. */
-#define _NLE_NM_NOBUFS 500
-#define _NLE_MSG_TRUNC 501
-
 /*****************************************************************************/
 
 #define IFQDISCSIZ                      32
@@ -127,6 +124,18 @@ enum {
 #define IFLA_IPTUN_MAX                  (__IFLA_IPTUN_MAX - 1)
 #endif
 
+#define IFLA_TUN_UNSPEC                 0
+#define IFLA_TUN_OWNER                  1
+#define IFLA_TUN_GROUP                  2
+#define IFLA_TUN_TYPE                   3
+#define IFLA_TUN_PI                     4
+#define IFLA_TUN_VNET_HDR               5
+#define IFLA_TUN_PERSIST                6
+#define IFLA_TUN_MULTI_QUEUE            7
+#define IFLA_TUN_NUM_QUEUES             8
+#define IFLA_TUN_NUM_DISABLED_QUEUES    9
+#define __IFLA_TUN_MAX                  10
+#define IFLA_TUN_MAX (__IFLA_TUN_MAX - 1)
 
 static const gboolean RTA_PREF_SUPPORTED_AT_COMPILETIME = (RTA_MAX >= 20 /* RTA_PREF */);
 
@@ -283,7 +292,7 @@ typedef enum {
 
 #define FOR_EACH_DELAYED_ACTION(iflags, flags_all) \
 	for ((iflags) = (DelayedActionType) 0x1LL; (iflags) <= DELAYED_ACTION_TYPE_MAX; (iflags) <<= 1) \
-		if (NM_FLAGS_HAS (flags_all, iflags))
+		if (NM_FLAGS_ANY (flags_all, iflags))
 
 typedef enum {
 	/* Negative values are errors from kernel. Add dummy member to
@@ -297,13 +306,9 @@ typedef enum {
 	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL,
 	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT,
 	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING,
+	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS,
 } WaitForNlResponseResult;
 
-typedef void (*WaitForNlResponseCallback) (NMPlatform *platform,
-                                           guint32 seq_number,
-                                           WaitForNlResponseResult seq_result,
-                                           gpointer user_data);
-
 static void delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gpointer user_data);
 static gboolean delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink);
 static void do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const char *name);
@@ -328,7 +333,9 @@ wait_for_nl_response_to_plerr (WaitForNlResponseResult seq_result)
 }
 
 static const char *
-wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, gsize buf_size)
+wait_for_nl_response_to_string (WaitForNlResponseResult seq_result,
+                                const char *errmsg,
+                                char *buf, gsize buf_size)
 {
 	char *buf0 = buf;
 
@@ -343,8 +350,13 @@ wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, g
 		nm_utils_strbuf_append_str (&buf, &buf_size, "failure");
 		break;
 	default:
-		if (seq_result < 0)
-			nm_utils_strbuf_append (&buf, &buf_size, "failure %d (%s)", -((int) seq_result), g_strerror (-((int) seq_result)));
+		if (seq_result < 0) {
+			nm_utils_strbuf_append (&buf, &buf_size, "failure %d (%s%s%s)",
+			                        -((int) seq_result),
+			                        g_strerror (-((int) seq_result)),
+			                        errmsg ? " - " : "",
+			                        errmsg ?: "");
+		}
 		else
 			nm_utils_strbuf_append (&buf, &buf_size, "internal failure %d", (int) seq_result);
 		break;
@@ -538,8 +550,7 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_OPENVSWITCH,   "openvswitch", "openvswitch", NULL },
 	{ NM_LINK_TYPE_PPP,           "ppp",         NULL,          "ppp" },
 	{ NM_LINK_TYPE_SIT,           "sit",         "sit",         NULL },
-	{ NM_LINK_TYPE_TAP,           "tap",         NULL,          NULL },
-	{ NM_LINK_TYPE_TUN,           "tun",         NULL,          NULL },
+	{ NM_LINK_TYPE_TUN,           "tun",         "tun",         NULL },
 	{ NM_LINK_TYPE_VETH,          "veth",        "veth",        NULL },
 	{ NM_LINK_TYPE_VLAN,          "vlan",        "vlan",        "vlan" },
 	{ NM_LINK_TYPE_VXLAN,         "vxlan",       "vxlan",       "vxlan" },
@@ -800,36 +811,25 @@ _linktype_get_type (NMPlatform *platform,
 		    && !NM_IN_SET (obj->link.type, NM_LINK_TYPE_UNKNOWN, NM_LINK_TYPE_NONE)
 		    && nm_streq (ifname, obj->link.name)
 		    && (   !kind
-		        || !g_strcmp0 (kind, obj->link.kind))) {
+		        || nm_streq0 (kind, obj->link.kind))) {
 			nm_assert (obj->link.kind == g_intern_string (obj->link.kind));
 			*out_kind = obj->link.kind;
 			return obj->link.type;
 		}
 	}
 
+	/* we intern kind to not require us to keep the pointer alive. Essentially
+	 * leaking it in a global cache. That should be safe enough, because the
+	 * kind comes only from kernel messages, which depend on the number of
+	 * available drivers. So, there is not the danger that we leak uncontrolled
+	 * many kinds. */
 	*out_kind = g_intern_string (kind);
 
 	if (kind) {
 		for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
-			if (g_strcmp0 (kind, linktypes[i].rtnl_type) == 0)
+			if (nm_streq0 (kind, linktypes[i].rtnl_type)) {
 				return linktypes[i].nm_type;
-		}
-
-		if (!strcmp (kind, "tun")) {
-			NMPlatformTunProperties props;
-
-			if (   platform
-			    && nm_platform_link_tun_get_properties (platform, ifindex, &props)) {
-				if (!g_strcmp0 (props.mode, "tap"))
-					return NM_LINK_TYPE_TAP;
-				if (!g_strcmp0 (props.mode, "tun"))
-					return NM_LINK_TYPE_TUN;
 			}
-
-			/* try guessing the type using the link flags instead... */
-			if (flags & IFF_POINTOPOINT)
-				return NM_LINK_TYPE_TUN;
-			return NM_LINK_TYPE_TAP;
 		}
 	}
 
@@ -951,137 +951,6 @@ _nl_addattr_l (struct nlmsghdr *n,
 	return TRUE;
 }
 
-static void
-_nm_auto_nl_msg_cleanup (void *ptr)
-{
-	nlmsg_free (*((struct nl_msg **) ptr));
-}
-#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup)
-
-static const char *
-_nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len)
-{
-	const char *b;
-	const char *s;
-	guint flags, flags_before;
-	const char *prefix;
-
-	nm_utils_to_string_buffer_init (&buf, &len);
-	b = buf;
-
-	switch (hdr->nlmsg_type) {
-	case RTM_NEWLINK:    s = "RTM_NEWLINK";  break;
-	case RTM_DELLINK:    s = "RTM_DELLINK";  break;
-	case RTM_NEWADDR:    s = "RTM_NEWADDR";  break;
-	case RTM_DELADDR:    s = "RTM_DELADDR";  break;
-	case RTM_NEWROUTE:   s = "RTM_NEWROUTE"; break;
-	case RTM_DELROUTE:   s = "RTM_DELROUTE"; break;
-	case RTM_NEWQDISC:   s = "RTM_NEWQDISC"; break;
-	case RTM_DELQDISC:   s = "RTM_DELQDISC"; break;
-	case RTM_NEWTFILTER: s = "RTM_NEWTFILTER"; break;
-	case RTM_DELTFILTER: s = "RTM_DELTFILTER"; break;
-	case NLMSG_NOOP:     s = "NLMSG_NOOP"; break;
-	case NLMSG_ERROR:    s = "NLMSG_ERROR"; break;
-	case NLMSG_DONE:     s = "NLMSG_DONE"; break;
-	case NLMSG_OVERRUN:  s = "NLMSG_OVERRUN"; break;
-	default:             s = NULL;       break;
-	}
-
-	if (s)
-		nm_utils_strbuf_append_str (&buf, &len, s);
-	else
-		nm_utils_strbuf_append (&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type);
-
-	flags = hdr->nlmsg_flags;
-
-	if (!flags) {
-		nm_utils_strbuf_append_str (&buf, &len, ", flags 0");
-		goto flags_done;
-	}
-
-#define _F(f, n) \
-	G_STMT_START { \
-		if (NM_FLAGS_ALL (flags, f)) { \
-			flags &= ~(f); \
-			nm_utils_strbuf_append (&buf, &len, "%s%s", prefix, n); \
-			if (!flags) \
-				goto flags_done; \
-			prefix = ","; \
-		} \
-	} G_STMT_END
-
-	prefix = ", flags ";
-	flags_before = flags;
-	_F (NLM_F_REQUEST, "request");
-	_F (NLM_F_MULTI, "multi");
-	_F (NLM_F_ACK, "ack");
-	_F (NLM_F_ECHO, "echo");
-	_F (NLM_F_DUMP_INTR, "dump_intr");
-	_F (0x20 /*NLM_F_DUMP_FILTERED*/, "dump_filtered");
-
-	if (flags_before != flags)
-		prefix = ";";
-
-	switch (hdr->nlmsg_type) {
-	case RTM_NEWLINK:
-	case RTM_NEWADDR:
-	case RTM_NEWROUTE:
-	case RTM_NEWQDISC:
-	case RTM_NEWTFILTER:
-		_F (NLM_F_REPLACE, "replace");
-		_F (NLM_F_EXCL, "excl");
-		_F (NLM_F_CREATE, "create");
-		_F (NLM_F_APPEND, "append");
-		break;
-	case RTM_GETLINK:
-	case RTM_GETADDR:
-	case RTM_GETROUTE:
-	case RTM_DELQDISC:
-	case RTM_DELTFILTER:
-		_F (NLM_F_DUMP, "dump");
-		_F (NLM_F_ROOT, "root");
-		_F (NLM_F_MATCH, "match");
-		_F (NLM_F_ATOMIC, "atomic");
-		break;
-	}
-
-#undef _F
-
-	if (flags_before != flags)
-		prefix = ";";
-	nm_utils_strbuf_append (&buf, &len, "%s0x%04x", prefix, flags);
-
-flags_done:
-
-	nm_utils_strbuf_append (&buf, &len, ", seq %u", (unsigned) hdr->nlmsg_seq);
-
-	return b;
-}
-
-static int
-_nl_nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
-               const struct nla_policy *policy)
-{
-	return nla_parse (tb, maxtype, head, len, (struct nla_policy *) policy);
-}
-#define nla_parse(...) _nl_nla_parse(__VA_ARGS__)
-
-static int
-_nl_nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
-                 int maxtype, const struct nla_policy *policy)
-{
-	return nlmsg_parse (nlh, hdrlen, tb, maxtype, (struct nla_policy *) policy);
-}
-#define nlmsg_parse(...) _nl_nlmsg_parse(__VA_ARGS__)
-
-static int
-_nl_nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla,
-                      const struct nla_policy *policy)
-{
-	return nla_parse_nested (tb, maxtype, nla, (struct nla_policy *) policy);
-}
-#define nla_parse_nested(...) _nl_nla_parse_nested(__VA_ARGS__)
-
 /******************************************************************
  * NMPObject/netlink functions
  ******************************************************************/
@@ -1292,6 +1161,7 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 		[IFLA_IPTUN_ENCAP_LIMIT] = { .type = NLA_U8 },
 		[IFLA_IPTUN_FLOWINFO]    = { .type = NLA_U32 },
 		[IFLA_IPTUN_PROTO]       = { .type = NLA_U8 },
+		[IFLA_IPTUN_FLAGS]       = { .type = NLA_U32 },
 	};
 	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
 	int err;
@@ -1326,6 +1196,8 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 	}
 	if (tb[IFLA_IPTUN_PROTO])
 		props->proto = nla_get_u8 (tb[IFLA_IPTUN_PROTO]);
+	if (tb[IFLA_IPTUN_FLAGS])
+		props->flags = nla_get_u32 (tb[IFLA_IPTUN_FLAGS]);
 
 	return obj;
 }
@@ -1505,6 +1377,60 @@ _parse_lnk_sit (const char *kind, struct nlattr *info_data)
 
 /*****************************************************************************/
 
+static NMPObject *
+_parse_lnk_tun (const char *kind, struct nlattr *info_data)
+{
+	static const struct nla_policy policy[IFLA_TUN_MAX + 1] = {
+		[IFLA_TUN_OWNER]               = { .type = NLA_U32 },
+		[IFLA_TUN_GROUP]               = { .type = NLA_U32 },
+		[IFLA_TUN_TYPE]                = { .type = NLA_U8 },
+		[IFLA_TUN_PI]                  = { .type = NLA_U8 },
+		[IFLA_TUN_VNET_HDR]            = { .type = NLA_U8 },
+		[IFLA_TUN_PERSIST]             = { .type = NLA_U8 },
+		[IFLA_TUN_MULTI_QUEUE]         = { .type = NLA_U8 },
+		[IFLA_TUN_NUM_QUEUES]          = { .type = NLA_U32 },
+		[IFLA_TUN_NUM_DISABLED_QUEUES] = { .type = NLA_U32 },
+	};
+	struct nlattr *tb[IFLA_TUN_MAX + 1];
+	int err;
+	NMPObject *obj;
+	NMPlatformLnkTun *props;
+
+	if (!info_data || !nm_streq0 (kind, "tun"))
+		return NULL;
+
+	err = nla_parse_nested (tb, IFLA_TUN_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	if (!tb[IFLA_TUN_TYPE]) {
+		/* we require at least a type. */
+		return NULL;
+	}
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_TUN, NULL);
+	props = &obj->lnk_tun;
+
+	props->type = nla_get_u8 (tb[IFLA_TUN_TYPE]);
+
+	props->pi = !!nla_get_u8_cond (tb, IFLA_TUN_PI, FALSE);
+	props->vnet_hdr = !!nla_get_u8_cond (tb, IFLA_TUN_VNET_HDR, FALSE);
+	props->multi_queue = !!nla_get_u8_cond (tb, IFLA_TUN_MULTI_QUEUE, FALSE);
+	props->persist = !!nla_get_u8_cond (tb, IFLA_TUN_PERSIST, FALSE);
+
+	if (tb[IFLA_TUN_OWNER]) {
+		props->owner_valid = TRUE;
+		props->owner = nla_get_u32 (tb[IFLA_TUN_OWNER]);
+	}
+	if (tb[IFLA_TUN_GROUP]) {
+		props->group_valid = TRUE;
+		props->group = nla_get_u32 (tb[IFLA_TUN_GROUP]);
+	}
+	return obj;
+}
+
+/*****************************************************************************/
+
 static gboolean
 _vlan_qos_mapping_from_nla (struct nlattr *nlattr,
                             const NMVlanQosMapping **out_map,
@@ -1936,6 +1862,9 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	case NM_LINK_TYPE_SIT:
 		lnk_data = _parse_lnk_sit (nl_info_kind, nl_info_data);
 		break;
+	case NM_LINK_TYPE_TUN:
+		lnk_data = _parse_lnk_tun (nl_info_kind, nl_info_data);
+		break;
 	case NM_LINK_TYPE_VLAN:
 		lnk_data = _parse_lnk_vlan (nl_info_kind, nl_info_data);
 		break;
@@ -2665,8 +2594,7 @@ _nl_msg_new_link (int nlmsg_type,
 
 	nm_assert (NM_IN_SET (nlmsg_type, RTM_DELLINK, RTM_NEWLINK, RTM_GETLINK));
 
-	if (!(msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags)))
-		g_return_val_if_reached (NULL);
+	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
 
 	if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
@@ -2708,8 +2636,6 @@ _nl_msg_new_address (int nlmsg_type,
 	nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWADDR, RTM_DELADDR));
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
-	if (!msg)
-		g_return_val_if_reached (NULL);
 
 	if (scope == -1) {
 		/* Allow having scope unset, and detect the scope (including IPv4 compatibility hack). */
@@ -2822,8 +2748,6 @@ _nl_msg_new_route (int nlmsg_type,
 	nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE));
 
 	msg = nlmsg_alloc_simple (nlmsg_type, (int) nlmsgflags);
-	if (!msg)
-		g_return_val_if_reached (NULL);
 
 	if (nlmsg_append (msg, &rtmsg, sizeof (rtmsg), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
@@ -2921,8 +2845,6 @@ _nl_msg_new_qdisc (int nlmsg_type,
 	};
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
-	if (!msg)
-		return NULL;
 
 	if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
@@ -2997,8 +2919,6 @@ _nl_msg_new_tfilter (int nlmsg_type,
 	};
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
-	if (!msg)
-		return NULL;
 
 	if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
@@ -3040,6 +2960,7 @@ typedef struct {
 	DelayedActionWaitForNlResponseType response_type;
 	gint64 timeout_abs_ns;
 	WaitForNlResponseResult *out_seq_result;
+	char **out_errmsg;
 	union {
 		gint *out_refresh_all_in_progess;
 		NMPObject **out_route_get;
@@ -3500,7 +3421,7 @@ delayed_action_to_string_full (DelayedActionType action_type, gpointer user_data
 			                        (timeout < 0 ? -timeout : timeout) % NM_UTILS_NS_PER_SECOND,
 			                        (int) data->response_type,
 			                        data->seq_result ? ", " : "",
-			                        data->seq_result ? wait_for_nl_response_to_string (data->seq_result, b, sizeof (b)) : "");
+			                        data->seq_result ? wait_for_nl_response_to_string (data->seq_result, NULL, b, sizeof (b)) : "");
 		} else
 			nm_utils_strbuf_append_str (&buf, &buf_size, " (any)");
 		break;
@@ -3582,27 +3503,58 @@ delayed_action_wait_for_nl_response_complete (NMPlatform *platform,
 }
 
 static void
-delayed_action_wait_for_nl_response_complete_all (NMPlatform *platform,
-                                                  WaitForNlResponseResult fallback_result)
+delayed_action_wait_for_nl_response_complete_check (NMPlatform *platform,
+                                                    WaitForNlResponseResult force_result,
+                                                    guint32 *out_next_seq_number,
+                                                    gint64 *out_next_timeout_abs_ns,
+                                                    gint64 *p_now_ns)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	guint i;
+	guint32 next_seq_number = 0;
+	gint64 next_timeout_abs_ns = 0;
+	gint now_ns = 0;
+
+	for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; ) {
+		const DelayedActionWaitForNlResponseData *data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
+
+		if (data->seq_result)
+			delayed_action_wait_for_nl_response_complete (platform, i, data->seq_result);
+		else if (   p_now_ns
+		         && ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_ns ())) >= data->timeout_abs_ns)) {
+			/* the caller can optionally check for timeout by providing a p_now_ns argument. */
+			delayed_action_wait_for_nl_response_complete (platform, i, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT);
+		} else if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
+			delayed_action_wait_for_nl_response_complete (platform, i, force_result);
+		else {
+			if (   next_seq_number == 0
+			    || next_timeout_abs_ns > data->timeout_abs_ns) {
+				next_seq_number = data->seq_number;
+				next_timeout_abs_ns = data->timeout_abs_ns;
+			}
+			i++;
+		}
+	}
 
-	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
-		while (priv->delayed_action.list_wait_for_nl_response->len > 0) {
-			const DelayedActionWaitForNlResponseData *data;
-			guint idx = priv->delayed_action.list_wait_for_nl_response->len - 1;
-			WaitForNlResponseResult r;
-
-			data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, idx);
+	if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) {
+		nm_assert (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
+		nm_assert (priv->delayed_action.list_wait_for_nl_response->len == 0);
+	}
 
-			/* prefer the result that we already have. */
-			r = data->seq_result ? : fallback_result;
+	NM_SET_OUT (out_next_seq_number, next_seq_number);
+	NM_SET_OUT (out_next_timeout_abs_ns, next_timeout_abs_ns);
+	NM_SET_OUT (p_now_ns, now_ns);
+}
 
-			delayed_action_wait_for_nl_response_complete (platform, idx, r);
-		}
-	}
-	nm_assert (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
-	nm_assert (priv->delayed_action.list_wait_for_nl_response->len == 0);
+static void
+delayed_action_wait_for_nl_response_complete_all (NMPlatform *platform,
+                                                  WaitForNlResponseResult fallback_result)
+{
+	delayed_action_wait_for_nl_response_complete_check (platform,
+	                                                    fallback_result,
+	                                                    NULL,
+	                                                    NULL,
+	                                                    NULL);
 }
 
 /*****************************************************************************/
@@ -3785,6 +3737,7 @@ static void
 delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
                                               guint32 seq_number,
                                               WaitForNlResponseResult *out_seq_result,
+                                              char **out_errmsg,
                                               DelayedActionWaitForNlResponseType response_type,
                                               gpointer response_out_data)
 {
@@ -3792,6 +3745,7 @@ delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
 		.seq_number = seq_number,
 		.timeout_abs_ns = nm_utils_get_monotonic_timestamp_ns () + (200 * (NM_UTILS_NS_PER_SECOND / 1000)),
 		.out_seq_result = out_seq_result,
+		.out_errmsg = out_errmsg,
 		.response_type = response_type,
 		.response.out_data = response_out_data,
 	};
@@ -3976,40 +3930,46 @@ cache_on_change (NMPlatform *platform,
 		    && (obj_new && obj_new->_link.netlink.is_in_netlink)
 		    && (!obj_old || !obj_old->_link.netlink.is_in_netlink))
 		{
-			if (!obj_new->_link.netlink.lnk) {
+			gboolean re_request_link = FALSE;
+			const NMPlatformLnkTun *lnk_tun;
+
+			if (   !obj_new->_link.netlink.lnk
+			    && NM_IN_SET (obj_new->link.type, NM_LINK_TYPE_GRE,
+			                                      NM_LINK_TYPE_IP6TNL,
+			                                      NM_LINK_TYPE_INFINIBAND,
+			                                      NM_LINK_TYPE_MACVLAN,
+			                                      NM_LINK_TYPE_MACVLAN,
+			                                      NM_LINK_TYPE_SIT,
+			                                      NM_LINK_TYPE_TUN,
+			                                      NM_LINK_TYPE_VLAN,
+			                                      NM_LINK_TYPE_VXLAN)) {
 				/* certain link-types also come with a IFLA_INFO_DATA/lnk_data. It may happen that
 				 * kernel didn't send this notification, thus when we first learn about a link
 				 * that lacks an lnk_data we re-request it again.
 				 *
 				 * For example https://bugzilla.redhat.com/show_bug.cgi?id=1284001 */
-				switch (obj_new->link.type) {
-				case NM_LINK_TYPE_GRE:
-				case NM_LINK_TYPE_IP6TNL:
-				case NM_LINK_TYPE_INFINIBAND:
-				case NM_LINK_TYPE_MACVLAN:
-				case NM_LINK_TYPE_MACVTAP:
-				case NM_LINK_TYPE_SIT:
-				case NM_LINK_TYPE_VLAN:
-				case NM_LINK_TYPE_VXLAN:
-					delayed_action_schedule (platform,
-					                         DELAYED_ACTION_TYPE_REFRESH_LINK,
-					                         GINT_TO_POINTER (obj_new->link.ifindex));
-					break;
-				default:
-					break;
-				}
-			}
-			if (   obj_new->link.type == NM_LINK_TYPE_VETH
-			    && obj_new->link.parent == 0) {
+				re_request_link = TRUE;
+			} else if (   obj_new->link.type == NM_LINK_TYPE_TUN
+			           && obj_new->_link.netlink.lnk
+			           && (lnk_tun = &(obj_new->_link.netlink.lnk)->lnk_tun)
+			           && !lnk_tun->persist
+			           && lnk_tun->pi
+			           && !lnk_tun->vnet_hdr
+			           && !lnk_tun->multi_queue
+			           && !lnk_tun->owner_valid
+			           && !lnk_tun->group_valid) {
+				/* kernel has/had a know issue that the first notification for TUN device would
+				 * be sent with invalid parameters. The message looks like that kind, so refetch
+				 * it. */
+				re_request_link = TRUE;
+			} else if (   obj_new->link.type == NM_LINK_TYPE_VETH
+			           && obj_new->link.parent == 0) {
 				/* the initial notification when adding a veth pair can lack the parent/IFLA_LINK
 				 * (https://bugzilla.redhat.com/show_bug.cgi?id=1285827).
 				 * Request it again. */
-				delayed_action_schedule (platform,
-				                         DELAYED_ACTION_TYPE_REFRESH_LINK,
-				                         GINT_TO_POINTER (obj_new->link.ifindex));
-			}
-			if (   obj_new->link.type == NM_LINK_TYPE_ETHERNET
-			    && obj_new->link.addr.len == 0) {
+				re_request_link = TRUE;
+			} else if (   obj_new->link.type == NM_LINK_TYPE_ETHERNET
+			           && obj_new->link.addr.len == 0) {
 				/* Due to a kernel bug, we sometimes receive spurious NEWLINK
 				 * messages after a wifi interface has disappeared. Since the
 				 * link is not present anymore we can't determine its type and
@@ -4017,6 +3977,9 @@ cache_on_change (NMPlatform *platform,
 				 * specified.  Request the link again to check if it really
 				 * exists.  https://bugzilla.redhat.com/show_bug.cgi?id=1302037
 				 */
+				re_request_link = TRUE;
+			}
+			if (re_request_link) {
 				delayed_action_schedule (platform,
 				                         DELAYED_ACTION_TYPE_REFRESH_LINK,
 				                         GINT_TO_POINTER (obj_new->link.ifindex));
@@ -4067,8 +4030,10 @@ cache_on_change (NMPlatform *platform,
 static guint32
 _nlh_seq_next_get (NMLinuxPlatformPrivate *priv)
 {
-	/* generate a new sequence number, but skip zero. */
-	return priv->nlh_seq_next++ ?: priv->nlh_seq_next++;
+	/* generate a new sequence number, but never return zero.
+	 * Wrapping numbers are not a problem, because we don't rely
+	 * on strictly increasing sequence numbers. */
+	return (++priv->nlh_seq_next) ?: (++priv->nlh_seq_next);
 }
 
 /**
@@ -4079,12 +4044,13 @@ _nlh_seq_next_get (NMLinuxPlatformPrivate *priv)
  * @response_type:
  * @response_out_data:
  *
- * Returns: 0 on success or a negative errno. Beware, it's an errno, not nlerror.
+ * Returns: 0 on success or a negative errno.
  */
 static int
 _nl_send_nlmsghdr (NMPlatform *platform,
                    struct nlmsghdr *nlhdr,
                    WaitForNlResponseResult *out_seq_result,
+                   char **out_errmsg,
                    DelayedActionWaitForNlResponseType response_type,
                    gpointer response_out_data)
 {
@@ -4129,7 +4095,7 @@ again:
 		}
 	}
 
-	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result,
+	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result, out_errmsg,
 	                                              response_type, response_out_data);
 	return 0;
 }
@@ -4148,6 +4114,7 @@ static int
 _nl_send_nlmsg (NMPlatform *platform,
                 struct nl_msg *nlmsg,
                 WaitForNlResponseResult *out_seq_result,
+                char **out_errmsg,
                 DelayedActionWaitForNlResponseType response_type,
                 gpointer response_out_data)
 {
@@ -4166,7 +4133,7 @@ _nl_send_nlmsg (NMPlatform *platform,
 		return nle;
 	}
 
-	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result,
+	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result, out_errmsg,
 	                                              response_type, response_out_data);
 	return 0;
 }
@@ -4204,7 +4171,7 @@ do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const cha
 	                          0,
 	                          0);
 	if (nlmsg) {
-		nle = _nl_send_nlmsg (platform, nlmsg, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+		nle = _nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 		if (nle < 0) {
 			_LOGE ("do-request-link: %d %s: failed sending netlink request \"%s\" (%d)",
 			       ifindex, name ?: "",
@@ -4263,8 +4230,6 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		 * because we need the sequence number.
 		 */
 		nlmsg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP);
-		if (!nlmsg)
-			continue;
 
 		if (   klass->obj_type == NMP_OBJECT_TYPE_QDISC
 		    || klass->obj_type == NMP_OBJECT_TYPE_TFILTER) {
@@ -4281,7 +4246,7 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		if (nle < 0)
 			continue;
 
-		if (_nl_send_nlmsg (platform, nlmsg, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progess) < 0) {
+		if (_nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progess) < 0) {
 			nm_assert (*out_refresh_all_in_progess > 0);
 			*out_refresh_all_in_progess -= 1;
 		}
@@ -4325,7 +4290,7 @@ event_seq_check_refresh_all (NMPlatform *platform, guint32 seq_number)
 }
 
 static void
-event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResult seq_result)
+event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResult seq_result, const char *msg)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	DelayedActionWaitForNlResponseData *data;
@@ -4344,11 +4309,13 @@ event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResu
 				/* We potentially receive many parts partial responses for the same sequence number.
 				 * Thus, we only remember the result, and collect it later. */
 				if (data->seq_result < 0) {
-					/* we already saw an error for this seqence number.
+					/* we already saw an error for this sequence number.
 					 * Preserve it. */
 				} else if (   seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN
 				           || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
 					data->seq_result = seq_result;
+				if (data->out_errmsg && !*data->out_errmsg)
+					*data->out_errmsg = g_strdup (msg);
 				return;
 			}
 		}
@@ -4391,7 +4358,7 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	obj = nmp_object_new_from_nl (platform, cache, msg, id_only);
 	if (!obj) {
 		_LOGT ("event-notification: %s: ignore",
-		       _nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
+		       nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 		return;
 	}
 
@@ -4409,7 +4376,7 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	}
 
 	_LOGT ("event-notification: %s%s: %s",
-	       _nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)),
+	       nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)),
 	       is_dump ? ", in-dump" : "",
 	       nmp_object_to_string (obj,
 	                             id_only ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC,
@@ -4547,13 +4514,14 @@ do_add_link_with_lookup (NMPlatform *platform,
 {
 	const NMPObject *obj = NULL;
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	gs_free char *errmsg = NULL;
 	int nle;
 	char s_buf[256];
 	NMPCache *cache = nm_platform_get_cache (platform);
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
 		       name,
@@ -4573,7 +4541,7 @@ do_add_link_with_lookup (NMPlatform *platform,
 	        "do-add-link[%s/%s]: %s",
 	        name,
 	        nm_link_type_to_string (link_type),
-	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
+	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)));
 
 	if (out_link) {
 		obj = nmp_cache_lookup_link_full (cache, 0, name, FALSE, link_type, NULL, NULL);
@@ -4590,6 +4558,7 @@ do_add_addrroute (NMPlatform *platform,
                   gboolean suppress_netlink_failure)
 {
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	gs_free char *errmsg = NULL;
 	int nle;
 	char s_buf[256];
 
@@ -4599,7 +4568,7 @@ do_add_addrroute (NMPlatform *platform,
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)",
 		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
@@ -4620,7 +4589,7 @@ do_add_addrroute (NMPlatform *platform,
 	        "do-add-%s[%s]: %s",
 	        NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 	        nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
+	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)));
 
 	if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) {
 		/* In rare cases, the object is not yet ready as we received the ACK from
@@ -4641,6 +4610,7 @@ static gboolean
 do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
 {
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	gs_free char *errmsg = NULL;
 	int nle;
 	char s_buf[256];
 	gboolean success;
@@ -4648,7 +4618,7 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)",
 		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
@@ -4680,7 +4650,7 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 	        "do-delete-%s[%s]: %s%s",
 	        NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 	        nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
+	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)),
 	        log_detail);
 
 	if (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id),
@@ -4711,6 +4681,7 @@ do_change_link (NMPlatform *platform,
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	int nle;
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	gs_free char *errmsg = NULL;
 	char s_buf[256];
 	NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS;
 	NMLogLevel log_level = LOGL_DEBUG;
@@ -4726,7 +4697,7 @@ do_change_link (NMPlatform *platform,
 	}
 
 retry:
-	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		log_level = LOGL_ERR;
 		log_detail_free = g_strdup_printf (", failure sending netlink request: %s (%d)",
@@ -4783,7 +4754,7 @@ out:
 	        "do-change-link[%d]: %s changing link: %s%s",
 	        ifindex,
 	        log_result,
-	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
+	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)),
 	        log_detail);
 	return result;
 }
@@ -4860,6 +4831,12 @@ link_refresh (NMPlatform *platform, int ifindex)
 	return !!nm_platform_link_get_obj (platform, ifindex, TRUE);
 }
 
+static void
+refresh_all (NMPlatform *platform, NMPObjectType obj_type)
+{
+	do_request_one_type (platform, obj_type);
+}
+
 static gboolean
 link_set_netns (NMPlatform *platform,
                 int ifindex,
@@ -5392,6 +5369,7 @@ link_ip6tnl_add (NMPlatform *platform,
 	            & IP6_FLOWINFO_TCLASS_MASK;
 	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLOWINFO, htonl (flowinfo));
 	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PROTO, props->proto);
+	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLAGS, props->flags);
 
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
@@ -5615,6 +5593,62 @@ nla_put_failure:
 }
 
 static gboolean
+link_tun_add (NMPlatform *platform,
+              const char *name,
+              const NMPlatformLnkTun *props,
+              const NMPlatformLink **out_link,
+              int *out_fd)
+{
+	const NMPObject *obj;
+	struct ifreq ifr = { };
+	nm_auto_close int fd = -1;
+
+	nm_assert (NM_IN_SET (props->type, IFF_TAP, IFF_TUN));
+	nm_assert (props->persist || out_fd);
+
+	fd = open ("/dev/net/tun", O_RDWR | O_CLOEXEC);
+	if (fd < 0)
+		return FALSE;
+
+	nm_utils_ifname_cpy (ifr.ifr_name, name);
+	ifr.ifr_flags =   ((short) props->type)
+	                | ((short) IFF_TUN_EXCL)
+	                | (!props->pi          ? (short) IFF_NO_PI          : (short) 0)
+	                | ( props->vnet_hdr    ? (short) IFF_VNET_HDR       : (short) 0)
+	                | ( props->multi_queue ? (short) NM_IFF_MULTI_QUEUE : (short) 0);
+	if (ioctl (fd, TUNSETIFF, &ifr))
+		return FALSE;
+
+	if (props->owner_valid) {
+		if (ioctl (fd, TUNSETOWNER, (uid_t) props->owner))
+			return FALSE;
+	}
+
+	if (props->group_valid) {
+		if (ioctl (fd, TUNSETGROUP, (gid_t) props->group))
+			return FALSE;
+	}
+
+	if (props->persist) {
+		if (ioctl (fd, TUNSETPERSIST, 1))
+			return FALSE;
+	}
+
+	do_request_link (platform, 0, name);
+	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform),
+	                                  0, name, FALSE,
+	                                  NM_LINK_TYPE_TUN,
+	                                  NULL, NULL);
+
+	if (!obj)
+		return FALSE;
+
+	NM_SET_OUT (out_link, &obj->link);
+	NM_SET_OUT (out_fd, nm_steal_fd (&fd));
+	return TRUE;
+}
+
+static gboolean
 link_vxlan_add (NMPlatform *platform,
                 const char *name,
                 const NMPlatformLnkVxlan *props,
@@ -5854,64 +5888,6 @@ link_vlan_change (NMPlatform *platform,
 	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
-static int
-tun_add (NMPlatform *platform, const char *name, gboolean tap,
-         gint64 owner, gint64 group, gboolean pi, gboolean vnet_hdr,
-         gboolean multi_queue, const NMPlatformLink **out_link)
-{
-	const NMPObject *obj;
-	struct ifreq ifr = { };
-	int fd;
-
-	fd = open ("/dev/net/tun", O_RDWR | O_CLOEXEC);
-	if (fd < 0)
-		return FALSE;
-
-	nm_utils_ifname_cpy (ifr.ifr_name, name);
-	ifr.ifr_flags = tap ? IFF_TAP : IFF_TUN;
-
-	if (!pi)
-		ifr.ifr_flags |= IFF_NO_PI;
-	if (vnet_hdr)
-		ifr.ifr_flags |= IFF_VNET_HDR;
-	if (multi_queue)
-		ifr.ifr_flags |= NM_IFF_MULTI_QUEUE;
-
-	if (ioctl (fd, TUNSETIFF, &ifr)) {
-		nm_close (fd);
-		return FALSE;
-	}
-
-	if (owner >= 0 && owner < G_MAXINT32) {
-		if (ioctl (fd, TUNSETOWNER, (uid_t) owner)) {
-			nm_close (fd);
-			return FALSE;
-		}
-	}
-
-	if (group >= 0 && group < G_MAXINT32) {
-		if (ioctl (fd, TUNSETGROUP, (gid_t) group)) {
-			nm_close (fd);
-			return FALSE;
-		}
-	}
-
-	if (ioctl (fd, TUNSETPERSIST, 1)) {
-		nm_close (fd);
-		return FALSE;
-	}
-	do_request_link (platform, 0, name);
-	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform),
-	                                  0, name, FALSE,
-	                                  tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN,
-	                                  NULL, NULL);
-	if (out_link)
-		*out_link = obj ? &obj->link : NULL;
-
-	nm_close (fd);
-	return !!obj;
-}
-
 static gboolean
 link_enslave (NMPlatform *platform, int master, int slave)
 {
@@ -6453,7 +6429,7 @@ ip_route_get (NMPlatform *platform,
 		}
 
 		seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-		nle = _nl_send_nlmsghdr (platform, &req.n, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, &route);
+		nle = _nl_send_nlmsghdr (platform, &req.n, &seq_result, NULL, DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, &route);
 		if (nle < 0) {
 			_LOGE ("get-route: failure sending netlink request \"%s\" (%d)",
 			       g_strerror (-nle), -nle);
@@ -6492,6 +6468,7 @@ qdisc_add (NMPlatform *platform,
            const NMPlatformQdisc *qdisc)
 {
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	gs_free char *errmsg = NULL;
 	int nle;
 	char s_buf[256];
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -6500,7 +6477,7 @@ qdisc_add (NMPlatform *platform,
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_nlmsg (platform, msg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, msg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-qdisc: failed sending netlink request \"%s\" (%d)",
 		      nl_geterror (nle), -nle);
@@ -6515,7 +6492,7 @@ qdisc_add (NMPlatform *platform,
 	            ? LOGL_DEBUG
 	            : LOGL_WARN,
 	        "do-add-qdisc: %s",
-	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
+	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)));
 
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
 		return NM_PLATFORM_ERROR_SUCCESS;
@@ -6531,6 +6508,7 @@ tfilter_add (NMPlatform *platform,
              const NMPlatformTfilter *tfilter)
 {
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	gs_free char *errmsg = NULL;
 	int nle;
 	char s_buf[256];
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -6539,7 +6517,7 @@ tfilter_add (NMPlatform *platform,
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_nlmsg (platform, msg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	nle = _nl_send_nlmsg (platform, msg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-tfilter: failed sending netlink request \"%s\" (%d)",
 		      nl_geterror (nle), -nle);
@@ -6554,7 +6532,7 @@ tfilter_add (NMPlatform *platform,
 	            ? LOGL_DEBUG
 	            : LOGL_WARN,
 	        "do-add-tfilter: %s",
-	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
+	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)));
 
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
 		return NM_PLATFORM_ERROR_SUCCESS;
@@ -6585,15 +6563,12 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	struct nl_sock *sk = priv->nlh;
-	int n, err = 0, multipart = 0, interrupted = 0;
+	int n;
+	int err = 0;
+	gboolean multipart = 0;
+	gboolean interrupted = FALSE;
 	struct nlmsghdr *hdr;
 	WaitForNlResponseResult seq_result;
-
-	/*
-	nla is passed on to not only to nl_recv() but may also be passed
-	to a function pointer provided by the caller which may or may not
-	initialize the variable. Thomas Graf.
-	*/
 	struct sockaddr_nl nla = {0};
 	nm_auto_free struct ucred *creds = NULL;
 	nm_auto_free unsigned char *buf = NULL;
@@ -6601,56 +6576,28 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 continue_reading:
 	g_clear_pointer (&buf, free);
 	g_clear_pointer (&creds, free);
-	errno = 0;
 	n = nl_recv (sk, &nla, &buf, &creds);
 
 	if (n <= 0) {
-		/* workaround libnl3 <= 3.2.15 returning danling pointers in case nl_recv()
-		 * fails. Fixed by libnl3 69468517d0de1675d80f24661ff57a5dbac7275c. */
-		buf = NULL;
-		creds = NULL;
-	}
-
-	switch (n) {
-	case 0:
-		/* Work around a libnl bug fixed in 3.2.22 (375a6294) */
-		if (errno == EAGAIN) {
-			/* EAGAIN is equal to EWOULDBLOCK. If it would not be, we'd have to
-			 * workaround libnl3 mapping EWOULDBLOCK to -NLE_FAILURE. */
-			G_STATIC_ASSERT (EAGAIN == EWOULDBLOCK);
-			n = -NLE_AGAIN;
-		}
-		break;
-	case -NLE_MSG_TRUNC: {
-		int buf_size;
-
-		/* the message receive buffer was too small. We lost one message, which
-		 * is unfortunate. Try to double the buffer size for the next time. */
-		buf_size = nl_socket_get_msg_buf_size (sk);
-		if (buf_size < 512*1024) {
-			buf_size *= 2;
-			_LOGT ("netlink: recvmsg: increase message buffer size for recvmsg() to %d bytes", buf_size);
-			if (nl_socket_set_msg_buf_size (sk, buf_size) < 0)
-				nm_assert_not_reached ();
-			if (!handle_events)
-				goto continue_reading;
-		}
-		n = -_NLE_MSG_TRUNC;
-		break;
-	}
-	case -NLE_NOMEM:
-		if (errno == ENOBUFS) {
-			/* we are very much interested in a overrun of the receive buffer.
-			 * nl_recv() maps all kinds of errors to NLE_NOMEM, so check also
-			 * for errno explicitly. And if so, hack our own return code to signal
-			 * the overrun. */
-			n = -_NLE_NM_NOBUFS;
+
+		if (n == -NLE_MSG_TRUNC) {
+			int buf_size;
+
+			/* the message receive buffer was too small. We lost one message, which
+			 * is unfortunate. Try to double the buffer size for the next time. */
+			buf_size = nl_socket_get_msg_buf_size (sk);
+			if (buf_size < 512*1024) {
+				buf_size *= 2;
+				_LOGT ("netlink: recvmsg: increase message buffer size for recvmsg() to %d bytes", buf_size);
+				if (nl_socket_set_msg_buf_size (sk, buf_size) < 0)
+					nm_assert_not_reached ();
+				if (!handle_events)
+					goto continue_reading;
+			}
 		}
-		break;
-	}
 
-	if (n <= 0)
 		return n;
+	}
 
 	hdr = (struct nlmsghdr *) buf;
 	while (nlmsg_ok (hdr, n)) {
@@ -6659,12 +6606,9 @@ continue_reading:
 		gboolean process_valid_msg = FALSE;
 		guint32 seq_number;
 		char buf_nlmsghdr[400];
+		const char *extack_msg = NULL;
 
-		msg = nlmsg_convert (hdr);
-		if (!msg) {
-			err = -NLE_NOMEM;
-			goto out;
-		}
+		msg = nlmsg_alloc_convert (hdr);
 
 		nlmsg_set_proto (msg, NETLINK_ROUTE);
 		nlmsg_set_src (msg, &nla);
@@ -6679,13 +6623,13 @@ continue_reading:
 		}
 
 		_LOGt ("netlink: recvmsg: new message %s",
-		       _nl_nlmsghdr_to_str (hdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
+		       nl_nlmsghdr_to_str (hdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 
 		if (creds)
 			nlmsg_set_creds (msg, creds);
 
 		if (hdr->nlmsg_flags & NLM_F_MULTI)
-			multipart = 1;
+			multipart = TRUE;
 
 		if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
 			/*
@@ -6693,7 +6637,7 @@ continue_reading:
 			 * all messages until a NLMSG_DONE is
 			 * received and report the inconsistency.
 			 */
-			interrupted = 1;
+			interrupted = TRUE;
 		}
 
 		/* Other side wishes to see an ack for this message */
@@ -6708,7 +6652,7 @@ continue_reading:
 			 * usually the end of a message and therefore we slip
 			 * out of the loop by default. the user may overrule
 			 * this action by skipping this packet. */
-			multipart = 0;
+			multipart = FALSE;
 			seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
 		} else if (hdr->nlmsg_type == NLMSG_NOOP) {
 			/* Message to be ignored, the default action is to
@@ -6735,10 +6679,28 @@ continue_reading:
 			} else if (e->error) {
 				int errsv = e->error > 0 ? e->error : -e->error;
 
+				if (   NM_FLAGS_HAS (hdr->nlmsg_flags, NLM_F_ACK_TLVS)
+				    && hdr->nlmsg_len >= sizeof (*e) + e->msg.nlmsg_len) {
+					static const struct nla_policy policy[NLMSGERR_ATTR_MAX + 1] = {
+						[NLMSGERR_ATTR_MSG]     = { .type = NLA_STRING },
+						[NLMSGERR_ATTR_OFFS]    = { .type = NLA_U32 },
+					};
+					struct nlattr *tb[NLMSGERR_ATTR_MAX + 1];
+					struct nlattr *tlvs;
+
+					tlvs = (struct nlattr *) ((char *) e + sizeof (*e) + e->msg.nlmsg_len - NLMSG_HDRLEN);
+					if (!nla_parse (tb, NLMSGERR_ATTR_MAX, tlvs,
+					                hdr->nlmsg_len - sizeof (*e) - e->msg.nlmsg_len, policy)) {
+						if (tb[NLMSGERR_ATTR_MSG])
+							extack_msg = nla_get_string (tb[NLMSGERR_ATTR_MSG]);
+					}
+				}
+
 				/* Error message reported back from kernel. */
-				_LOGD ("netlink: recvmsg: error message from kernel: %s (%d) for request %d",
+				_LOGD ("netlink: recvmsg: error message from kernel: %s (%d)%s%s%s for request %d",
 				       strerror (errsv),
 				       errsv,
+				       NM_PRINT_FMT_QUOTED (extack_msg, " \"", extack_msg, "\"", ""),
 				       nlmsg_hdr (msg)->nlmsg_seq);
 				seq_result = -errsv;
 			} else
@@ -6767,7 +6729,7 @@ continue_reading:
 			seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
 		}
 
-		event_seq_check (platform, seq_number, seq_result);
+		event_seq_check (platform, seq_number, seq_result, extack_msg);
 
 		if (abort_parsing)
 			goto stop;
@@ -6787,9 +6749,9 @@ stop:
 		 * Repeat reading. */
 		goto continue_reading;
 	}
-out:
+
 	if (interrupted)
-		err = -NLE_DUMP_INTR;
+		return -NLE_DUMP_INTR;
 	return err;
 }
 
@@ -6800,46 +6762,50 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 {
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int r, nle;
+	int r;
 	struct pollfd pfd;
 	gboolean any = FALSE;
-	gint64 now_ns;
 	int timeout_ms;
-	guint i;
 	struct {
 		guint32 seq_number;
 		gint64 timeout_abs_ns;
-	} data_next;
+		gint64 now_ns;
+	} next;
 
-	if (!nm_platform_netns_push (platform, &netns))
+	if (!nm_platform_netns_push (platform, &netns)) {
+		delayed_action_wait_for_nl_response_complete_all (platform,
+		                                                  WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS);
 		return FALSE;
+	}
 
-	while (TRUE) {
-
-		while (TRUE) {
+	for (;;) {
+		for (;;) {
+			int nle;
 
 			nle = event_handler_recvmsgs (platform, TRUE);
 
 			if (nle < 0) {
 				switch (nle) {
-				case -NLE_AGAIN:
+				case -EAGAIN:
 					goto after_read;
 				case -NLE_DUMP_INTR:
 					_LOGD ("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
 					break;
-				case -_NLE_MSG_TRUNC:
-				case -_NLE_NM_NOBUFS:
+				case -NLE_MSG_TRUNC:
+				case -ENOBUFS:
 					_LOGI ("netlink: read: %s. Need to resynchronize platform cache",
 					       ({
 					            const char *_reason = "unknown";
 					            switch (nle) {
-					            case -_NLE_MSG_TRUNC: _reason = "message truncated";       break;
-					            case -_NLE_NM_NOBUFS: _reason = "too many netlink events"; break;
+					            case -NLE_MSG_TRUNC: _reason = "message truncated";       break;
+					            case -ENOBUFS:       _reason = "too many netlink events"; break;
 					            }
 					            _reason;
 					       }));
 					event_handler_recvmsgs (platform, FALSE);
-					delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC);
+					delayed_action_wait_for_nl_response_complete_all (platform,
+					                                                  WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC);
+
 					delayed_action_schedule (platform,
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
@@ -6863,39 +6829,23 @@ after_read:
 		if (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
 			return any;
 
-		now_ns = 0;
-		data_next.seq_number = 0;
-		data_next.timeout_abs_ns = 0;
-
-		for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; ) {
-			DelayedActionWaitForNlResponseData *data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
-
-			if (data->seq_result)
-				delayed_action_wait_for_nl_response_complete (platform, i, data->seq_result);
-			else if ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_ns ())) > data->timeout_abs_ns)
-				delayed_action_wait_for_nl_response_complete (platform, i, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT);
-			else {
-				i++;
-
-				if (   data_next.seq_number == 0
-				    || data_next.timeout_abs_ns > data->timeout_abs_ns) {
-					data_next.seq_number = data->seq_number;
-					data_next.timeout_abs_ns = data->timeout_abs_ns;
-				}
-			}
-		}
+		delayed_action_wait_for_nl_response_complete_check (platform,
+		                                                    WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN,
+		                                                    &next.seq_number,
+		                                                    &next.timeout_abs_ns,
+		                                                    &next.now_ns);
 
 		if (   !wait_for_acks
 		    || !NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
 			return any;
 
-		nm_assert (data_next.seq_number);
-		nm_assert (data_next.timeout_abs_ns > 0);
-		nm_assert (now_ns > 0);
+		nm_assert (next.seq_number);
+		nm_assert (next.now_ns > 0);
+		nm_assert (next.timeout_abs_ns > next.now_ns);
 
-		_LOGT ("netlink: read: wait for ACK for sequence number %u...", data_next.seq_number);
+		_LOGT ("netlink: read: wait for ACK for sequence number %u...", next.seq_number);
 
-		timeout_ms = (data_next.timeout_abs_ns - now_ns) / (NM_UTILS_NS_PER_SECOND / 1000);
+		timeout_ms = (next.timeout_abs_ns - next.now_ns) / (NM_UTILS_NS_PER_SECOND / 1000);
 
 		memset (&pfd, 0, sizeof (pfd));
 		pfd.fd = nl_socket_get_fd (priv->nlh);
@@ -6906,6 +6856,7 @@ after_read:
 			/* timeout and there is nothing to read. */
 			goto after_read;
 		}
+
 		if (r < 0) {
 			int errsv = errno;
 
@@ -7047,11 +6998,10 @@ nm_linux_platform_init (NMLinuxPlatform *self)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (self);
 
-	priv->nlh_seq_next = 1;
 	priv->delayed_action.list_master_connected = g_ptr_array_new ();
 	priv->delayed_action.list_refresh_link = g_ptr_array_new ();
 	priv->delayed_action.list_wait_for_nl_response = g_array_new (FALSE, TRUE, sizeof (DelayedActionWaitForNlResponseData));
-	priv->wifi_data = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) wifi_utils_deinit);
+	priv->wifi_data = g_hash_table_new_full (nm_direct_hash, NULL, NULL, (GDestroyNotify) wifi_utils_unref);
 }
 
 static void
@@ -7097,6 +7047,10 @@ constructed (GObject *_object)
 	nle = nl_socket_set_buffer_size (priv->nlh, 8*1024*1024, 0);
 	g_assert (!nle);
 
+	nle = nl_socket_set_ext_ack (priv->nlh, TRUE);
+	if (nle)
+		_LOGD ("could not enable extended acks on netlink socket");
+
 	/* explicitly set the msg buffer size and disable MSG_PEEK.
 	 * If we later encounter NLE_MSG_TRUNC, we will adjust the buffer size. */
 	nl_socket_disable_msg_peek (priv->nlh);
@@ -7176,7 +7130,8 @@ dispose (GObject *object)
 
 	_LOGD ("dispose");
 
-	delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING);
+	delayed_action_wait_for_nl_response_complete_all (platform,
+	                                                  WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING);
 
 	priv->delayed_action.flags = DELAYED_ACTION_TYPE_NONE;
 	g_ptr_array_set_size (priv->delayed_action.list_master_connected, 0);
@@ -7226,6 +7181,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_add = link_add;
 	platform_class->link_delete = link_delete;
 
+	platform_class->refresh_all = refresh_all;
 	platform_class->link_refresh = link_refresh;
 
 	platform_class->link_set_netns = link_set_netns;
@@ -7264,8 +7220,6 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_vlan_change = link_vlan_change;
 	platform_class->link_vxlan_add = link_vxlan_add;
 
-	platform_class->tun_add = tun_add;
-
 	platform_class->infiniband_partition_add = infiniband_partition_add;
 	platform_class->infiniband_partition_delete = infiniband_partition_delete;
 
@@ -7290,6 +7244,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_macvlan_add = link_macvlan_add;
 	platform_class->link_ipip_add = link_ipip_add;
 	platform_class->link_sit_add = link_sit_add;
+	platform_class->link_tun_add = link_tun_add;
 
 	platform_class->object_delete = object_delete;
 	platform_class->ip4_address_add = ip4_address_add;
diff --git a/src/platform/nm-netlink.c b/src/platform/nm-netlink.c
new file mode 100644
index 00000000..4cb19780
--- /dev/null
+++ b/src/platform/nm-netlink.c
@@ -0,0 +1,1491 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-platform.c - Handle runtime kernel networking configuration
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-netlink.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+/*****************************************************************************/
+
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+/*****************************************************************************/
+
+#define NL_SOCK_PASSCRED        (1<<1)
+#define NL_MSG_PEEK             (1<<3)
+#define NL_MSG_PEEK_EXPLICIT    (1<<4)
+#define NL_NO_AUTO_ACK          (1<<5)
+
+#ifndef NETLINK_EXT_ACK
+#define NETLINK_EXT_ACK         11
+#endif
+
+#define NL_MSG_CRED_PRESENT 1
+
+struct nl_msg {
+	int                     nm_protocol;
+	int                     nm_flags;
+	struct sockaddr_nl      nm_src;
+	struct sockaddr_nl      nm_dst;
+	struct ucred            nm_creds;
+	struct nlmsghdr *       nm_nlh;
+	size_t                  nm_size;
+	int                     nm_refcnt;
+};
+
+struct nl_sock {
+	struct sockaddr_nl      s_local;
+	struct sockaddr_nl      s_peer;
+	int                     s_fd;
+	int                     s_proto;
+	unsigned int            s_seq_next;
+	unsigned int            s_seq_expect;
+	int                     s_flags;
+	size_t                  s_bufsize;
+};
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror, int,
+	NM_UTILS_LOOKUP_DEFAULT (NULL),
+	NM_UTILS_LOOKUP_ITEM (NLE_UNSPEC,          "NLE_UNSPEC"),
+	NM_UTILS_LOOKUP_ITEM (NLE_BUG,             "NLE_BUG"),
+	NM_UTILS_LOOKUP_ITEM (NLE_NATIVE_ERRNO,    "NLE_NATIVE_ERRNO"),
+
+	NM_UTILS_LOOKUP_ITEM (NLE_ATTRSIZE,        "NLE_ATTRSIZE"),
+	NM_UTILS_LOOKUP_ITEM (NLE_BAD_SOCK,        "NLE_BAD_SOCK"),
+	NM_UTILS_LOOKUP_ITEM (NLE_DUMP_INTR,       "NLE_DUMP_INTR"),
+	NM_UTILS_LOOKUP_ITEM (NLE_MSG_OVERFLOW,    "NLE_MSG_OVERFLOW"),
+	NM_UTILS_LOOKUP_ITEM (NLE_MSG_TOOSHORT,    "NLE_MSG_TOOSHORT"),
+	NM_UTILS_LOOKUP_ITEM (NLE_MSG_TRUNC,       "NLE_MSG_TRUNC"),
+	NM_UTILS_LOOKUP_ITEM (NLE_SEQ_MISMATCH,    "NLE_SEQ_MISMATCH"),
+)
+
+const char *
+nl_geterror (int err)
+{
+	const char *s;
+
+	err = nl_errno (err);
+
+	if (err >= _NLE_BASE) {
+		s = _geterror (err);
+		if (s)
+			return s;
+	}
+	return g_strerror (err);
+}
+
+/*****************************************************************************/
+
+NM_UTILS_ENUM2STR_DEFINE (nl_nlmsgtype2str, int,
+	NM_UTILS_ENUM2STR (NLMSG_NOOP,    "NOOP"),
+	NM_UTILS_ENUM2STR (NLMSG_ERROR,   "ERROR"),
+	NM_UTILS_ENUM2STR (NLMSG_DONE,    "DONE"),
+	NM_UTILS_ENUM2STR (NLMSG_OVERRUN, "OVERRUN"),
+);
+
+NM_UTILS_FLAGS2STR_DEFINE (nl_nlmsg_flags2str, int,
+	NM_UTILS_FLAGS2STR (NLM_F_REQUEST, "REQUEST"),
+	NM_UTILS_FLAGS2STR (NLM_F_MULTI,   "MULTI"),
+	NM_UTILS_FLAGS2STR (NLM_F_ACK,     "ACK"),
+	NM_UTILS_FLAGS2STR (NLM_F_ECHO,    "ECHO"),
+	NM_UTILS_FLAGS2STR (NLM_F_ROOT,    "ROOT"),
+	NM_UTILS_FLAGS2STR (NLM_F_MATCH,   "MATCH"),
+	NM_UTILS_FLAGS2STR (NLM_F_ATOMIC,  "ATOMIC"),
+	NM_UTILS_FLAGS2STR (NLM_F_REPLACE, "REPLACE"),
+	NM_UTILS_FLAGS2STR (NLM_F_EXCL,    "EXCL"),
+	NM_UTILS_FLAGS2STR (NLM_F_CREATE,  "CREATE"),
+	NM_UTILS_FLAGS2STR (NLM_F_APPEND,  "APPEND"),
+);
+
+/*****************************************************************************/
+
+const char *
+nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len)
+{
+	const char *b;
+	const char *s;
+	guint flags, flags_before;
+	const char *prefix;
+
+	if (!nm_utils_to_string_buffer_init_null (hdr, &buf, &len))
+		return buf;
+
+	b = buf;
+
+	switch (hdr->nlmsg_type) {
+	case RTM_NEWLINK:    s = "RTM_NEWLINK";  break;
+	case RTM_DELLINK:    s = "RTM_DELLINK";  break;
+	case RTM_NEWADDR:    s = "RTM_NEWADDR";  break;
+	case RTM_DELADDR:    s = "RTM_DELADDR";  break;
+	case RTM_NEWROUTE:   s = "RTM_NEWROUTE"; break;
+	case RTM_DELROUTE:   s = "RTM_DELROUTE"; break;
+	case RTM_NEWQDISC:   s = "RTM_NEWQDISC"; break;
+	case RTM_DELQDISC:   s = "RTM_DELQDISC"; break;
+	case RTM_NEWTFILTER: s = "RTM_NEWTFILTER"; break;
+	case RTM_DELTFILTER: s = "RTM_DELTFILTER"; break;
+	case NLMSG_NOOP:     s = "NLMSG_NOOP"; break;
+	case NLMSG_ERROR:    s = "NLMSG_ERROR"; break;
+	case NLMSG_DONE:     s = "NLMSG_DONE"; break;
+	case NLMSG_OVERRUN:  s = "NLMSG_OVERRUN"; break;
+	default:             s = NULL;       break;
+	}
+
+	if (s)
+		nm_utils_strbuf_append_str (&buf, &len, s);
+	else
+		nm_utils_strbuf_append (&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type);
+
+	flags = hdr->nlmsg_flags;
+
+	if (!flags) {
+		nm_utils_strbuf_append_str (&buf, &len, ", flags 0");
+		goto flags_done;
+	}
+
+#define _F(f, n) \
+	G_STMT_START { \
+		if (NM_FLAGS_ALL (flags, f)) { \
+			flags &= ~(f); \
+			nm_utils_strbuf_append (&buf, &len, "%s%s", prefix, n); \
+			if (!flags) \
+				goto flags_done; \
+			prefix = ","; \
+		} \
+	} G_STMT_END
+
+	prefix = ", flags ";
+	flags_before = flags;
+	_F (NLM_F_REQUEST, "request");
+	_F (NLM_F_MULTI, "multi");
+	_F (NLM_F_ACK, "ack");
+	_F (NLM_F_ECHO, "echo");
+	_F (NLM_F_DUMP_INTR, "dump_intr");
+	_F (0x20 /*NLM_F_DUMP_FILTERED*/, "dump_filtered");
+
+	if (flags_before != flags)
+		prefix = ";";
+
+	switch (hdr->nlmsg_type) {
+	case RTM_NEWLINK:
+	case RTM_NEWADDR:
+	case RTM_NEWROUTE:
+	case RTM_NEWQDISC:
+	case RTM_NEWTFILTER:
+		_F (NLM_F_REPLACE, "replace");
+		_F (NLM_F_EXCL, "excl");
+		_F (NLM_F_CREATE, "create");
+		_F (NLM_F_APPEND, "append");
+		break;
+	case RTM_GETLINK:
+	case RTM_GETADDR:
+	case RTM_GETROUTE:
+	case RTM_DELQDISC:
+	case RTM_DELTFILTER:
+		_F (NLM_F_DUMP, "dump");
+		_F (NLM_F_ROOT, "root");
+		_F (NLM_F_MATCH, "match");
+		_F (NLM_F_ATOMIC, "atomic");
+		break;
+	}
+
+#undef _F
+
+	if (flags_before != flags)
+		prefix = ";";
+	nm_utils_strbuf_append (&buf, &len, "%s0x%04x", prefix, flags);
+
+flags_done:
+
+	nm_utils_strbuf_append (&buf, &len, ", seq %u", (unsigned) hdr->nlmsg_seq);
+
+	return b;
+}
+
+/*****************************************************************************/
+
+struct nlmsghdr *
+nlmsg_hdr (struct nl_msg *n)
+{
+	return n->nm_nlh;
+}
+
+void *
+nlmsg_reserve (struct nl_msg *n, size_t len, int pad)
+{
+	char *buf = (char *) n->nm_nlh;
+	size_t nlmsg_len = n->nm_nlh->nlmsg_len;
+	size_t tlen;
+
+	if (len > n->nm_size)
+		return NULL;
+
+	tlen = pad ? ((len + (pad - 1)) & ~(pad - 1)) : len;
+
+	if ((tlen + nlmsg_len) > n->nm_size)
+		return NULL;
+
+	buf += nlmsg_len;
+	n->nm_nlh->nlmsg_len += tlen;
+
+	if (tlen > len)
+		memset(buf + len, 0, tlen - len);
+
+	return buf;
+}
+
+/*****************************************************************************/
+
+static int
+ get_default_page_size (void)
+{
+	static int val = 0;
+	int v;
+
+	if (G_UNLIKELY (val == 0)) {
+		v = getpagesize ();
+		g_assert (v > 0);
+		val = v;
+	}
+	return val;
+}
+
+struct nlattr *
+nla_reserve (struct nl_msg *msg, int attrtype, int attrlen)
+{
+	struct nlattr *nla;
+	int tlen;
+
+	if (attrlen < 0)
+		return NULL;
+
+	tlen = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) + nla_total_size(attrlen);
+
+	if (tlen > msg->nm_size)
+		return NULL;
+
+	nla = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
+	nla->nla_type = attrtype;
+	nla->nla_len = nla_attr_size(attrlen);
+
+	if (attrlen)
+		memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen));
+	msg->nm_nlh->nlmsg_len = tlen;
+
+	return nla;
+}
+
+struct nl_msg *
+nlmsg_alloc_size (size_t len)
+{
+	struct nl_msg *nm;
+
+	if (len < sizeof (struct nlmsghdr))
+		len = sizeof (struct nlmsghdr);
+
+	nm = g_slice_new0 (struct nl_msg);
+
+	nm->nm_refcnt = 1;
+	nm->nm_protocol = -1;
+	nm->nm_size = len;
+	nm->nm_nlh = g_malloc0 (len);
+	nm->nm_nlh->nlmsg_len = nlmsg_total_size (0);
+	return nm;
+}
+
+/**
+ * Allocate a new netlink message with the default maximum payload size.
+ *
+ * Allocates a new netlink message without any further payload. The
+ * maximum payload size defaults to PAGESIZE or as otherwise specified
+ * with nlmsg_set_default_size().
+ *
+ * @return Newly allocated netlink message or NULL.
+ */
+struct nl_msg *
+nlmsg_alloc (void)
+{
+	return nlmsg_alloc_size (get_default_page_size ());
+}
+
+/**
+ * Allocate a new netlink message with maximum payload size specified.
+ */
+struct nl_msg *
+nlmsg_alloc_inherit (struct nlmsghdr *hdr)
+{
+	struct nl_msg *nm;
+
+	nm = nlmsg_alloc ();
+	if (hdr) {
+		struct nlmsghdr *new = nm->nm_nlh;
+
+		new->nlmsg_type = hdr->nlmsg_type;
+		new->nlmsg_flags = hdr->nlmsg_flags;
+		new->nlmsg_seq = hdr->nlmsg_seq;
+		new->nlmsg_pid = hdr->nlmsg_pid;
+	}
+
+	return nm;
+}
+
+struct nl_msg *
+nlmsg_alloc_convert (struct nlmsghdr *hdr)
+{
+	struct nl_msg *nm;
+
+	nm = nlmsg_alloc_size (NLMSG_ALIGN (hdr->nlmsg_len));
+	memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len);
+	return nm;
+}
+
+struct nl_msg *
+nlmsg_alloc_simple (int nlmsgtype, int flags)
+{
+	struct nlmsghdr nlh = {
+		.nlmsg_type = nlmsgtype,
+		.nlmsg_flags = flags,
+	};
+
+	return nlmsg_alloc_inherit (&nlh);
+}
+
+int
+nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad)
+{
+	void *tmp;
+
+	tmp = nlmsg_reserve (n, len, pad);
+	if (tmp == NULL)
+		return -ENOMEM;
+
+	memcpy(tmp, data, len);
+	return 0;
+}
+
+int
+nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
+             int maxtype, const struct nla_policy *policy)
+{
+	if (!nlmsg_valid_hdr(nlh, hdrlen))
+		return -NLE_MSG_TOOSHORT;
+
+	return nla_parse (tb, maxtype, nlmsg_attrdata(nlh, hdrlen),
+	                  nlmsg_attrlen(nlh, hdrlen), policy);
+}
+
+struct nlmsghdr *
+nlmsg_put (struct nl_msg *n, uint32_t pid, uint32_t seq,
+           int type, int payload, int flags)
+{
+	struct nlmsghdr *nlh;
+
+	if (n->nm_nlh->nlmsg_len < NLMSG_HDRLEN)
+		g_return_val_if_reached (NULL);
+
+	nlh = (struct nlmsghdr *) n->nm_nlh;
+	nlh->nlmsg_type = type;
+	nlh->nlmsg_flags = flags;
+	nlh->nlmsg_pid = pid;
+	nlh->nlmsg_seq = seq;
+
+	if (payload > 0 &&
+	    nlmsg_reserve(n, payload, NLMSG_ALIGNTO) == NULL)
+		return NULL;
+
+	return nlh;
+}
+
+uint64_t
+nla_get_u64 (const struct nlattr *nla)
+{
+	uint64_t tmp = 0;
+
+	if (nla && nla_len(nla) >= sizeof (tmp))
+		memcpy(&tmp, nla_data(nla), sizeof (tmp));
+
+	return tmp;
+}
+
+size_t
+nla_strlcpy (char *dst, const struct nlattr *nla, size_t dstsize)
+{
+	size_t srclen = nla_len(nla);
+	const char *src = nla_data(nla);
+
+	if (srclen > 0 && src[srclen - 1] == '\0')
+		srclen--;
+
+	if (dstsize > 0) {
+		size_t len = (srclen >= dstsize) ? dstsize - 1 : srclen;
+
+		memset(dst, 0, dstsize);
+		memcpy(dst, src, len);
+	}
+
+	return srclen;
+}
+
+int
+nla_memcpy (void *dest, const struct nlattr *src, int count)
+{
+	int minlen;
+
+	if (!src)
+		return 0;
+
+	minlen = NM_MIN (count, (int) nla_len (src));
+	memcpy(dest, nla_data(src), minlen);
+
+	return minlen;
+}
+
+int
+nla_put (struct nl_msg *msg, int attrtype, int datalen, const void *data)
+{
+	struct nlattr *nla;
+
+	nla = nla_reserve(msg, attrtype, datalen);
+	if (!nla) {
+		if (datalen < 0)
+			g_return_val_if_reached (-NLE_BUG);
+
+		return -ENOMEM;
+	}
+
+	if (datalen > 0)
+		memcpy (nla_data(nla), data, datalen);
+
+	return 0;
+}
+
+struct nlattr *
+nla_find (const struct nlattr *head, int len, int attrtype)
+{
+	const struct nlattr *nla;
+	int rem;
+
+	nla_for_each_attr (nla, head, len, rem) {
+		if (nla_type (nla) == attrtype)
+			return (struct nlattr*)nla;
+	}
+
+	return NULL;
+}
+
+void
+nla_nest_cancel (struct nl_msg *msg, const struct nlattr *attr)
+{
+	ssize_t len;
+
+	len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) attr;
+	if (len < 0)
+		g_return_if_reached ();
+	else if (len > 0) {
+		msg->nm_nlh->nlmsg_len -= len;
+		memset(nlmsg_tail(msg->nm_nlh), 0, len);
+	}
+}
+
+struct nlattr *
+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)
+		return NULL;
+
+	return start;
+}
+
+static int
+_nest_end (struct nl_msg *msg, struct nlattr *start, int keep_empty)
+{
+	size_t pad, len;
+
+	len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) start;
+
+	if (   len > USHRT_MAX
+	    || (!keep_empty && len == NLA_HDRLEN)) {
+		/*
+		 * Max nlattr size exceeded or empty nested attribute, trim the
+		 * attribute header again
+		 */
+		nla_nest_cancel(msg, start);
+
+		/* Return error only if nlattr size was exceeded */
+		return (len == NLA_HDRLEN) ? 0 : -NLE_ATTRSIZE;
+	}
+
+	start->nla_len = len;
+
+	pad = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len;
+	if (pad > 0) {
+		/*
+		 * Data inside attribute does not end at a alignment boundry.
+		 * Pad accordingly and accoun for the additional space in
+		 * the message. nlmsg_reserve() may never fail in this situation,
+		 * the allocate message buffer must be a multiple of NLMSG_ALIGNTO.
+		 */
+		if (!nlmsg_reserve(msg, pad, 0))
+			g_return_val_if_reached (-NLE_BUG);
+	}
+
+	return 0;
+}
+
+int
+nla_nest_end (struct nl_msg *msg, struct nlattr *start)
+{
+	return _nest_end (msg, start, 0);
+}
+
+static const uint16_t nla_attr_minlen[NLA_TYPE_MAX+1] = {
+	[NLA_U8]        = sizeof (uint8_t),
+	[NLA_U16]       = sizeof (uint16_t),
+	[NLA_U32]       = sizeof (uint32_t),
+	[NLA_U64]       = sizeof (uint64_t),
+	[NLA_STRING]    = 1,
+	[NLA_FLAG]      = 0,
+};
+
+static int
+validate_nla (const struct nlattr *nla, int maxtype,
+              const struct nla_policy *policy)
+{
+	const struct nla_policy *pt;
+	unsigned int minlen = 0;
+	int type = nla_type(nla);
+
+	if (type < 0 || type > maxtype)
+		return 0;
+
+	pt = &policy[type];
+
+	if (pt->type > NLA_TYPE_MAX)
+		g_return_val_if_reached (-NLE_BUG);
+
+	if (pt->minlen)
+		minlen = pt->minlen;
+	else if (pt->type != NLA_UNSPEC)
+		minlen = nla_attr_minlen[pt->type];
+
+	if (nla_len(nla) < minlen)
+		return -NLE_UNSPEC;
+
+	if (pt->maxlen && nla_len(nla) > pt->maxlen)
+		return -NLE_UNSPEC;
+
+	if (pt->type == NLA_STRING) {
+		const char *data = nla_data(nla);
+		if (data[nla_len(nla) - 1] != '\0')
+			return -NLE_UNSPEC;
+	}
+
+	return 0;
+}
+
+int
+nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
+           const struct nla_policy *policy)
+{
+	struct nlattr *nla;
+	int rem, err;
+
+	memset(tb, 0, sizeof (struct nlattr *) * (maxtype + 1));
+
+	nla_for_each_attr(nla, head, len, rem) {
+		int type = nla_type(nla);
+
+		if (type > maxtype)
+			continue;
+
+		if (policy) {
+			err = validate_nla(nla, maxtype, policy);
+			if (err < 0)
+				goto errout;
+		}
+
+		tb[type] = nla;
+	}
+
+	err = 0;
+errout:
+	return err;
+}
+
+/*****************************************************************************/
+
+void nlmsg_free (struct nl_msg *msg)
+{
+	if (!msg)
+		return;
+
+	if (msg->nm_refcnt < 1)
+		g_return_if_reached ();
+
+	msg->nm_refcnt--;
+
+	if (msg->nm_refcnt <= 0) {
+		g_free (msg->nm_nlh);
+		g_slice_free (struct nl_msg, msg);
+	}
+}
+
+int
+nlmsg_get_proto (struct nl_msg *msg)
+{
+	return msg->nm_protocol;
+}
+
+void
+nlmsg_set_proto (struct nl_msg *msg, int protocol)
+{
+	msg->nm_protocol = protocol;
+}
+
+void
+nlmsg_set_src (struct nl_msg *msg, struct sockaddr_nl *addr)
+{
+	memcpy (&msg->nm_src, addr, sizeof (*addr));
+}
+
+struct ucred *
+nlmsg_get_creds (struct nl_msg *msg)
+{
+	if (msg->nm_flags & NL_MSG_CRED_PRESENT)
+		return &msg->nm_creds;
+	return NULL;
+}
+
+void
+nlmsg_set_creds (struct nl_msg *msg, struct ucred *creds)
+{
+	memcpy (&msg->nm_creds, creds, sizeof (*creds));
+	msg->nm_flags |= NL_MSG_CRED_PRESENT;
+}
+
+/*****************************************************************************/
+
+void *
+genlmsg_put (struct nl_msg *msg, uint32_t port, uint32_t seq, int family,
+             int hdrlen, int flags, uint8_t cmd, uint8_t version)
+{
+	struct nlmsghdr *nlh;
+	struct genlmsghdr hdr = {
+		.cmd = cmd,
+		.version = version,
+	};
+
+	nlh = nlmsg_put (msg, port, seq, family, GENL_HDRLEN + hdrlen, flags);
+	if (nlh == NULL)
+		return NULL;
+
+	memcpy (nlmsg_data (nlh), &hdr, sizeof (hdr));
+
+	return (char *) nlmsg_data (nlh) + GENL_HDRLEN;
+}
+
+void *
+genlmsg_data (const struct genlmsghdr *gnlh)
+{
+	return ((unsigned char *) gnlh + GENL_HDRLEN);
+}
+
+void *
+genlmsg_user_hdr (const struct genlmsghdr *gnlh)
+{
+	return genlmsg_data (gnlh);
+}
+
+struct genlmsghdr *
+genlmsg_hdr (struct nlmsghdr *nlh)
+{
+	return nlmsg_data (nlh);
+}
+
+void *
+genlmsg_user_data (const struct genlmsghdr *gnlh, const int hdrlen)
+{
+	return (char *) genlmsg_user_hdr (gnlh) + NLMSG_ALIGN (hdrlen);
+}
+
+struct nlattr *
+genlmsg_attrdata (const struct genlmsghdr *gnlh, int hdrlen)
+{
+	return genlmsg_user_data (gnlh, hdrlen);
+}
+
+int
+genlmsg_len (const struct genlmsghdr *gnlh)
+{
+	const struct nlmsghdr *nlh;
+
+	nlh = (const struct nlmsghdr *) ((const unsigned char *) gnlh - NLMSG_HDRLEN);
+	return (nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN);
+}
+
+int
+genlmsg_attrlen (const struct genlmsghdr *gnlh, int hdrlen)
+{
+	return genlmsg_len (gnlh) - NLMSG_ALIGN (hdrlen);
+}
+
+int
+genlmsg_valid_hdr (struct nlmsghdr *nlh, int hdrlen)
+{
+	struct genlmsghdr *ghdr;
+
+	if (!nlmsg_valid_hdr (nlh, GENL_HDRLEN))
+		return 0;
+
+	ghdr = nlmsg_data (nlh);
+	if (genlmsg_len (ghdr) < NLMSG_ALIGN (hdrlen))
+		return 0;
+
+	return 1;
+}
+
+int
+genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
+               int maxtype, const struct nla_policy *policy)
+{
+	struct genlmsghdr *ghdr;
+
+	if (!genlmsg_valid_hdr (nlh, hdrlen))
+		return -NLE_MSG_TOOSHORT;
+
+	ghdr = nlmsg_data (nlh);
+	return nla_parse (tb, maxtype, genlmsg_attrdata (ghdr, hdrlen),
+	                  genlmsg_attrlen (ghdr, hdrlen), policy);
+}
+
+static int
+_genl_parse_getfamily (struct nl_msg *msg, void *arg)
+{
+	static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
+		[CTRL_ATTR_FAMILY_ID]    = { .type = NLA_U16 },
+		[CTRL_ATTR_FAMILY_NAME]  = { .type = NLA_STRING,
+		                            .maxlen = GENL_NAMSIZ },
+		[CTRL_ATTR_VERSION]      = { .type = NLA_U32 },
+		[CTRL_ATTR_HDRSIZE]      = { .type = NLA_U32 },
+		[CTRL_ATTR_MAXATTR]      = { .type = NLA_U32 },
+		[CTRL_ATTR_OPS]          = { .type = NLA_NESTED },
+		[CTRL_ATTR_MCAST_GROUPS] = { .type = NLA_NESTED },
+	};
+	struct nlattr *tb[CTRL_ATTR_MAX+1];
+	struct nlmsghdr *nlh = nlmsg_hdr (msg);
+	gint32 *response_data = arg;
+
+	if (genlmsg_parse (nlh, 0, tb, CTRL_ATTR_MAX, ctrl_policy))
+		return NL_SKIP;
+
+	if (tb[CTRL_ATTR_FAMILY_ID])
+		*response_data = nla_get_u16 (tb[CTRL_ATTR_FAMILY_ID]);
+
+	return NL_STOP;
+}
+
+int
+genl_ctrl_resolve (struct nl_sock *sk, const char *name)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	int result = -ENOMEM;
+	gint32 response_data = -1;
+	const struct nl_cb cb = {
+		.valid_cb = _genl_parse_getfamily,
+		.valid_arg = &response_data,
+	};
+
+	msg = nlmsg_alloc ();
+
+	if (!genlmsg_put (msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL,
+	                  0, 0, CTRL_CMD_GETFAMILY, 1))
+		goto out;
+
+	if (nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name) < 0)
+		goto out;
+
+	result = nl_send_auto (sk, msg);
+	if (result < 0)
+		goto out;
+
+	result = nl_recvmsgs (sk, &cb);
+	if (result < 0)
+		goto out;
+
+	/* If search was successful, request may be ACKed after data */
+	result = nl_wait_for_ack (sk, NULL);
+	if (result < 0)
+		goto out;
+
+	if (response_data > 0)
+		result = response_data;
+	else
+		result = -ENOENT;
+
+out:
+	return result;
+}
+
+/*****************************************************************************/
+
+struct nl_sock *
+nl_socket_alloc (void)
+{
+	struct nl_sock *sk;
+
+	sk = g_slice_new0 (struct nl_sock);
+
+	sk->s_fd = -1;
+	sk->s_local.nl_family = AF_NETLINK;
+	sk->s_peer.nl_family = AF_NETLINK;
+	sk->s_seq_expect = sk->s_seq_next = time(NULL);
+
+	return sk;
+}
+
+void
+nl_socket_free (struct nl_sock *sk)
+{
+	if (!sk)
+		return;
+
+	if (sk->s_fd >= 0)
+		nm_close (sk->s_fd);
+	g_slice_free (struct nl_sock, sk);
+}
+
+int
+nl_socket_get_fd (const struct nl_sock *sk)
+{
+	return sk->s_fd;
+}
+
+uint32_t
+nl_socket_get_local_port (const struct nl_sock *sk)
+{
+	return sk->s_local.nl_pid;
+}
+
+size_t
+nl_socket_get_msg_buf_size (struct nl_sock *sk)
+{
+	return sk->s_bufsize;
+}
+
+int
+nl_socket_set_passcred (struct nl_sock *sk, int state)
+{
+	int err;
+
+	if (sk->s_fd == -1)
+		return -NLE_BAD_SOCK;
+
+	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_PASSCRED,
+	                  &state, sizeof (state));
+	if (err < 0)
+		return -nl_syserr2nlerr (errno);
+
+	if (state)
+		sk->s_flags |= NL_SOCK_PASSCRED;
+	else
+		sk->s_flags &= ~NL_SOCK_PASSCRED;
+
+	return 0;
+}
+
+int
+nl_socket_set_msg_buf_size (struct nl_sock *sk, size_t bufsize)
+{
+	sk->s_bufsize = bufsize;
+
+	return 0;
+}
+
+struct sockaddr_nl *
+nlmsg_get_dst (struct nl_msg *msg)
+{
+	return &msg->nm_dst;
+}
+
+int
+nl_socket_set_nonblocking (const struct nl_sock *sk)
+{
+	if (sk->s_fd == -1)
+		return -NLE_BAD_SOCK;
+
+	if (fcntl(sk->s_fd, F_SETFL, O_NONBLOCK) < 0)
+		return -nl_syserr2nlerr (errno);
+
+	return 0;
+}
+
+int
+nl_socket_set_buffer_size (struct nl_sock *sk, int rxbuf, int txbuf)
+{
+	int err;
+
+	if (rxbuf <= 0)
+		rxbuf = 32768;
+
+	if (txbuf <= 0)
+		txbuf = 32768;
+
+	if (sk->s_fd == -1)
+		return -NLE_BAD_SOCK;
+
+	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_SNDBUF,
+	                  &txbuf, sizeof (txbuf));
+	if (err < 0) {
+		return -nl_syserr2nlerr (errno);
+	}
+
+	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_RCVBUF,
+	                  &rxbuf, sizeof (rxbuf));
+	if (err < 0) {
+		return -nl_syserr2nlerr (errno);
+	}
+
+	return 0;
+}
+
+int
+nl_socket_add_memberships (struct nl_sock *sk, int group, ...)
+{
+	int err;
+	va_list ap;
+
+	if (sk->s_fd == -1)
+		return -NLE_BAD_SOCK;
+
+	va_start(ap, group);
+
+	while (group != 0) {
+		if (group < 0) {
+			va_end(ap);
+			g_return_val_if_reached (-NLE_BUG);
+		}
+
+		err = setsockopt (sk->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
+		                  &group, sizeof (group));
+		if (err < 0) {
+			va_end(ap);
+			return -nl_syserr2nlerr (errno);
+		}
+
+		group = va_arg(ap, int);
+	}
+
+	va_end(ap);
+
+	return 0;
+}
+
+int
+nl_socket_set_ext_ack (struct nl_sock *sk, gboolean enable)
+{
+	int err, val;
+
+	if (sk->s_fd == -1)
+		return -NLE_BAD_SOCK;
+
+	val = !!enable;
+	err = setsockopt (sk->s_fd, SOL_NETLINK, NETLINK_EXT_ACK, &val, sizeof (val));
+	if (err < 0)
+		return -nl_syserr2nlerr (errno);
+
+	return 0;
+}
+
+void nl_socket_disable_msg_peek (struct nl_sock *sk)
+{
+	sk->s_flags |= NL_MSG_PEEK_EXPLICIT;
+	sk->s_flags &= ~NL_MSG_PEEK;
+}
+
+int
+nl_connect (struct nl_sock *sk, int protocol)
+{
+	int err;
+	socklen_t addrlen;
+	struct sockaddr_nl local = { 0 };
+
+	if (sk->s_fd != -1)
+		return -NLE_BAD_SOCK;
+
+	sk->s_fd = socket (AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
+	if (sk->s_fd < 0) {
+		err = -nl_syserr2nlerr (errno);
+		goto errout;
+	}
+
+	err = nl_socket_set_buffer_size(sk, 0, 0);
+	if (err < 0)
+		goto errout;
+
+	nm_assert (sk->s_local.nl_pid == 0);
+
+	err = bind (sk->s_fd, (struct sockaddr*) &sk->s_local,
+	            sizeof (sk->s_local));
+	if (err != 0) {
+		err = -nl_syserr2nlerr (errno);
+		goto errout;
+	}
+
+	addrlen = sizeof (local);
+	err = getsockname (sk->s_fd, (struct sockaddr *) &local,
+	                   &addrlen);
+	if (err < 0) {
+		err = -nl_syserr2nlerr (errno);
+		goto errout;
+	}
+
+	if (addrlen != sizeof (local)) {
+		err = -NLE_UNSPEC;
+		goto errout;
+	}
+
+	if (local.nl_family != AF_NETLINK) {
+		err = -NLE_UNSPEC;
+		goto errout;
+	}
+
+	sk->s_local = local;
+	sk->s_proto = protocol;
+
+	return 0;
+
+errout:
+	if (sk->s_fd != -1) {
+		close(sk->s_fd);
+		sk->s_fd = -1;
+	}
+	return err;
+}
+
+/*****************************************************************************/
+
+static void
+_cb_init (struct nl_cb *dst, const struct nl_cb *src)
+{
+	nm_assert (dst);
+
+	if (src)
+		*dst = *src;
+	else
+		memset (dst, 0, sizeof (*dst));
+}
+
+static int ack_wait_handler(struct nl_msg *msg, void *arg)
+{
+	return NL_STOP;
+}
+
+int
+nl_wait_for_ack (struct nl_sock *sk,
+                 const struct nl_cb *cb)
+{
+	struct nl_cb cb2;
+
+	_cb_init (&cb2, cb);
+	cb2.ack_cb = ack_wait_handler;
+	return nl_recvmsgs (sk, &cb2);
+}
+
+#define NL_CB_CALL(cb, type, msg) \
+do { \
+	const struct nl_cb *_cb = (cb); \
+	\
+	if (_cb->type##_cb) { \
+		err = _cb->type##_cb ((msg), _cb->type##_arg); \
+		switch (err) { \
+		case NL_OK: \
+			err = 0; \
+			break; \
+		case NL_SKIP: \
+			goto skip; \
+		case NL_STOP: \
+			goto stop; \
+		default: \
+			goto out; \
+		} \
+	} \
+} while (0)
+
+int
+nl_recvmsgs (struct nl_sock *sk, const struct nl_cb *cb)
+{
+	int n, err = 0, multipart = 0, interrupted = 0, nrecv = 0;
+	gs_free unsigned char *buf = NULL;
+	struct nlmsghdr *hdr;
+	struct sockaddr_nl nla = { 0 };
+	gs_free struct ucred *creds = NULL;
+
+continue_reading:
+	n = nl_recv (sk, &nla, &buf, &creds);
+	if (n <= 0)
+		return n;
+
+	hdr = (struct nlmsghdr *) buf;
+	while (nlmsg_ok (hdr, n)) {
+		nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+		msg = nlmsg_alloc_convert (hdr);
+
+		nlmsg_set_proto (msg, sk->s_proto);
+		nlmsg_set_src (msg, &nla);
+		if (creds)
+			nlmsg_set_creds (msg, creds);
+
+		nrecv++;
+
+		/* Only do sequence checking if auto-ack mode is enabled */
+		if (!(sk->s_flags & NL_NO_AUTO_ACK)) {
+			if (hdr->nlmsg_seq != sk->s_seq_expect) {
+				err = -NLE_SEQ_MISMATCH;
+				goto out;
+			}
+		}
+
+		if (hdr->nlmsg_type == NLMSG_DONE ||
+		    hdr->nlmsg_type == NLMSG_ERROR ||
+		    hdr->nlmsg_type == NLMSG_NOOP ||
+		    hdr->nlmsg_type == NLMSG_OVERRUN) {
+			/* We can't check for !NLM_F_MULTI since some netlink
+			 * users in the kernel are broken. */
+			sk->s_seq_expect++;
+		}
+
+		if (hdr->nlmsg_flags & NLM_F_MULTI)
+			multipart = 1;
+
+		if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
+			/*
+			 * We have to continue reading to clear
+			 * all messages until a NLMSG_DONE is
+			 * received and report the inconsistency.
+			 */
+			interrupted = 1;
+		}
+
+		/* messages terminates a multipart message, this is
+		 * usually the end of a message and therefore we slip
+		 * out of the loop by default. the user may overrule
+		 * this action by skipping this packet. */
+		if (hdr->nlmsg_type == NLMSG_DONE) {
+			multipart = 0;
+			NL_CB_CALL(cb, finish, msg);
+		}
+
+		/* Message to be ignored, the default action is to
+		 * skip this message if no callback is specified. The
+		 * user may overrule this action by returning
+		 * NL_PROCEED. */
+		else if (hdr->nlmsg_type == NLMSG_NOOP)
+			goto skip;
+
+		/* Data got lost, report back to user. The default action is to
+		 * quit parsing. The user may overrule this action by retuning
+		 * NL_SKIP or NL_PROCEED (dangerous) */
+		else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
+			err = -NLE_MSG_OVERFLOW;
+			goto out;
+		}
+
+		/* Message carries a nlmsgerr */
+		else if (hdr->nlmsg_type == NLMSG_ERROR) {
+			struct nlmsgerr *e = nlmsg_data(hdr);
+
+			if (hdr->nlmsg_len < nlmsg_size(sizeof (*e))) {
+				/* Truncated error message, the default action
+				 * is to stop parsing. The user may overrule
+				 * this action by returning NL_SKIP or
+				 * NL_PROCEED (dangerous) */
+				err = -NLE_MSG_TRUNC;
+				goto out;
+			}
+			if (e->error) {
+				/* Error message reported back from kernel. */
+				if (cb->err_cb) {
+					err = cb->err_cb (&nla, e,
+					                  cb->err_arg);
+					if (err < 0)
+						goto out;
+					else if (err == NL_SKIP)
+						goto skip;
+					else if (err == NL_STOP) {
+						err = -e->error;
+						goto out;
+					}
+				} else {
+					err = -e->error;
+					goto out;
+				}
+			} else
+				NL_CB_CALL(cb, ack, msg);
+		} else {
+			/* Valid message (not checking for MULTIPART bit to
+			 * get along with broken kernels. NL_SKIP has no
+			 * effect on this.  */
+			NL_CB_CALL(cb, valid, msg);
+		}
+skip:
+		err = 0;
+		hdr = nlmsg_next(hdr, &n);
+	}
+
+	if (multipart) {
+		/* Multipart message not yet complete, continue reading */
+		nm_clear_g_free (&creds);
+		nm_clear_g_free (&buf);
+
+		goto continue_reading;
+	}
+
+stop:
+	err = 0;
+
+out:
+	if (interrupted)
+		err = -NLE_DUMP_INTR;
+
+	return err ?: nrecv;
+}
+
+int
+nl_sendmsg (struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr)
+{
+	int ret;
+
+	if (sk->s_fd < 0)
+		return -NLE_BAD_SOCK;
+
+	nlmsg_set_src (msg, &sk->s_local);
+
+	ret = sendmsg(sk->s_fd, hdr, 0);
+	if (ret < 0)
+		return -nl_syserr2nlerr (errno);
+
+	return ret;
+}
+
+int
+nl_send_iovec (struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen)
+{
+	struct sockaddr_nl *dst;
+	struct ucred *creds;
+	struct msghdr hdr = {
+		.msg_name = (void *) &sk->s_peer,
+		.msg_namelen = sizeof (struct sockaddr_nl),
+		.msg_iov = iov,
+		.msg_iovlen = iovlen,
+	};
+	char buf[CMSG_SPACE(sizeof (struct ucred))];
+
+	/* Overwrite destination if specified in the message itself, defaults
+	 * to the peer address of the socket.
+	 */
+	dst = nlmsg_get_dst(msg);
+	if (dst->nl_family == AF_NETLINK)
+		hdr.msg_name = dst;
+
+	/* Add credentials if present. */
+	creds = nlmsg_get_creds(msg);
+	if (creds != NULL) {
+		struct cmsghdr *cmsg;
+
+		hdr.msg_control = buf;
+		hdr.msg_controllen = sizeof (buf);
+
+		cmsg = CMSG_FIRSTHDR(&hdr);
+		cmsg->cmsg_level = SOL_SOCKET;
+		cmsg->cmsg_type = SCM_CREDENTIALS;
+		cmsg->cmsg_len = CMSG_LEN(sizeof (struct ucred));
+		memcpy(CMSG_DATA(cmsg), creds, sizeof (struct ucred));
+	}
+
+	return nl_sendmsg(sk, msg, &hdr);
+}
+
+void
+nl_complete_msg (struct nl_sock *sk, struct nl_msg *msg)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nlmsg_hdr(msg);
+	if (nlh->nlmsg_pid == NL_AUTO_PORT)
+		nlh->nlmsg_pid = nl_socket_get_local_port(sk);
+
+	if (nlh->nlmsg_seq == NL_AUTO_SEQ)
+		nlh->nlmsg_seq = sk->s_seq_next++;
+
+	if (msg->nm_protocol == -1)
+		msg->nm_protocol = sk->s_proto;
+
+	nlh->nlmsg_flags |= NLM_F_REQUEST;
+
+	if (!(sk->s_flags & NL_NO_AUTO_ACK))
+		nlh->nlmsg_flags |= NLM_F_ACK;
+}
+
+int
+nl_send (struct nl_sock *sk, struct nl_msg *msg)
+{
+	struct iovec iov = {
+		.iov_base = (void *) nlmsg_hdr(msg),
+		.iov_len = nlmsg_hdr(msg)->nlmsg_len,
+	};
+
+	return nl_send_iovec(sk, msg, &iov, 1);
+}
+
+int nl_send_auto(struct nl_sock *sk, struct nl_msg *msg)
+{
+	nl_complete_msg(sk, msg);
+
+	return nl_send(sk, msg);
+}
+
+int
+nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
+         unsigned char **buf, struct ucred **creds)
+{
+	ssize_t n;
+	int flags = 0;
+	static int page_size = 0;
+	struct iovec iov;
+	struct msghdr msg = {
+		.msg_name = (void *) nla,
+		.msg_namelen = sizeof (struct sockaddr_nl),
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	gs_free struct ucred* tmpcreds = NULL;
+	int retval;
+
+	nm_assert (nla);
+	nm_assert (buf && !*buf);
+	nm_assert (!creds || !*creds);
+
+	if (   (sk->s_flags & NL_MSG_PEEK)
+	    || (   !(sk->s_flags & NL_MSG_PEEK_EXPLICIT)
+	        && sk->s_bufsize == 0))
+		flags |= MSG_PEEK | MSG_TRUNC;
+
+	if (page_size == 0)
+		page_size = getpagesize() * 4;
+
+	iov.iov_len = sk->s_bufsize ? : page_size;
+	iov.iov_base = g_malloc (iov.iov_len);
+
+	if (   creds
+	    && (sk->s_flags & NL_SOCK_PASSCRED)) {
+		msg.msg_controllen = CMSG_SPACE (sizeof (struct ucred));
+		msg.msg_control = g_malloc (msg.msg_controllen);
+	}
+
+retry:
+	n = recvmsg(sk->s_fd, &msg, flags);
+	if (!n) {
+		retval = 0;
+		goto abort;
+	}
+
+	if (n < 0) {
+		if (errno == EINTR)
+			goto retry;
+
+		retval = -nl_syserr2nlerr (errno);
+		goto abort;
+	}
+
+	if (msg.msg_flags & MSG_CTRUNC) {
+		if (msg.msg_controllen == 0) {
+			retval = -NLE_MSG_TRUNC;
+			goto abort;
+		}
+
+		msg.msg_controllen *= 2;
+		msg.msg_control = g_realloc (msg.msg_control, msg.msg_controllen);
+		goto retry;
+	}
+
+	if (   iov.iov_len < n
+	    || (msg.msg_flags & MSG_TRUNC)) {
+		/* respond with error to an incomplete message */
+		if (flags == 0) {
+			retval = -NLE_MSG_TRUNC;
+			goto abort;
+		}
+
+		/* Provided buffer is not long enough, enlarge it
+		 * to size of n (which should be total length of the message)
+		 * and try again. */
+		iov.iov_base = g_realloc (iov.iov_base, n);
+		iov.iov_len = n;
+		flags = 0;
+		goto retry;
+	}
+
+	if (flags != 0) {
+		/* Buffer is big enough, do the actual reading */
+		flags = 0;
+		goto retry;
+	}
+
+	if (msg.msg_namelen != sizeof (struct sockaddr_nl)) {
+		retval =  -NLE_UNSPEC;
+		goto abort;
+	}
+
+	if (creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
+		struct cmsghdr *cmsg;
+
+		for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+			if (cmsg->cmsg_level != SOL_SOCKET)
+				continue;
+			if (cmsg->cmsg_type != SCM_CREDENTIALS)
+				continue;
+			tmpcreds = g_memdup (CMSG_DATA(cmsg), sizeof (*tmpcreds));
+			break;
+		}
+	}
+
+	retval = n;
+
+abort:
+	g_free (msg.msg_control);
+
+	if (retval <= 0) {
+		g_free (iov.iov_base);
+		return retval;
+	}
+
+	*buf = iov.iov_base;
+	NM_SET_OUT (creds, g_steal_pointer (&tmpcreds));
+	return retval;
+}
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
new file mode 100644
index 00000000..c0dc09c4
--- /dev/null
+++ b/src/platform/nm-netlink.h
@@ -0,0 +1,511 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-platform.c - Handle runtime kernel networking configuration
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_NETLINK_H__
+#define __NM_NETLINK_H__
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/genetlink.h>
+
+/*****************************************************************************/
+#define _NLE_BASE               100000
+#define NLE_UNSPEC              (_NLE_BASE +  0)
+#define NLE_BUG                 (_NLE_BASE +  1)
+#define NLE_NATIVE_ERRNO        (_NLE_BASE +  2)
+#define NLE_SEQ_MISMATCH        (_NLE_BASE +  3)
+#define NLE_MSG_TRUNC           (_NLE_BASE +  4)
+#define NLE_MSG_TOOSHORT        (_NLE_BASE +  5)
+#define NLE_DUMP_INTR           (_NLE_BASE +  6)
+#define NLE_ATTRSIZE            (_NLE_BASE +  7)
+#define NLE_BAD_SOCK            (_NLE_BASE +  8)
+#define NLE_NOADDR              (_NLE_BASE +  9)
+#define NLE_MSG_OVERFLOW        (_NLE_BASE + 10)
+
+#define _NLE_BASE_END           (_NLE_BASE + 11)
+
+#define NLMSGERR_ATTR_UNUSED            0
+#define NLMSGERR_ATTR_MSG               1
+#define NLMSGERR_ATTR_OFFS              2
+#define NLMSGERR_ATTR_COOKIE            3
+#define NLMSGERR_ATTR_MAX               3
+
+#ifndef NLM_F_ACK_TLVS
+#define NLM_F_ACK_TLVS                  0x200
+#endif
+
+static inline int
+nl_errno (int err)
+{
+	/* the error codes from our netlink implementation are plain errno
+	 * extended with our own error in a particular range starting from
+	 * _NLE_BASE.
+	 *
+	 * However, often we encode errors as negative values. This function
+	 * normalizes the error and returns its positive value. */
+	return err >= 0
+	       ? err
+	       : ((err == G_MININT) ? NLE_BUG : -errno);
+}
+
+static inline int
+nl_syserr2nlerr (int err)
+{
+	if (err == G_MININT)
+		return NLE_NATIVE_ERRNO;
+	if (err < 0)
+		err = -err;
+	return (err >= _NLE_BASE && err < _NLE_BASE_END)
+	       ? NLE_NATIVE_ERRNO
+	       : err;
+}
+
+const char *nl_geterror (int err);
+
+/*****************************************************************************/
+
+/* Basic attribute data types */
+enum {
+	NLA_UNSPEC,     /* Unspecified type, binary data chunk */
+	NLA_U8,         /* 8 bit integer */
+	NLA_U16,        /* 16 bit integer */
+	NLA_U32,        /* 32 bit integer */
+	NLA_U64,        /* 64 bit integer */
+	NLA_STRING,     /* NUL terminated character string */
+	NLA_FLAG,       /* Flag */
+	NLA_MSECS,      /* Micro seconds (64bit) */
+	NLA_NESTED,     /* Nested attributes */
+	NLA_NESTED_COMPAT,
+	NLA_NUL_STRING,
+	NLA_BINARY,
+	NLA_S8,
+	NLA_S16,
+	NLA_S32,
+	NLA_S64,
+	__NLA_TYPE_MAX,
+};
+
+#define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1)
+
+struct nl_msg;
+
+/*****************************************************************************/
+
+const char *nl_nlmsgtype2str (int type, char *buf, size_t size);
+
+const char *nl_nlmsg_flags2str (int flags, char *buf, size_t len);
+
+const char *nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len);
+
+/*****************************************************************************/
+
+struct nla_policy {
+	/* Type of attribute or NLA_UNSPEC */
+	uint16_t type;
+
+	/* Minimal length of payload required */
+	uint16_t minlen;
+
+	/* Maximal length of payload allowed */
+	uint16_t maxlen;
+};
+
+/*****************************************************************************/
+
+static inline int
+nla_attr_size(int payload)
+{
+	nm_assert (payload >= 0);
+
+	return NLA_HDRLEN + payload;
+}
+
+static inline int
+nla_total_size (int payload)
+{
+	return NLA_ALIGN (nla_attr_size (payload));
+}
+
+static inline int
+nla_padlen (int payload)
+{
+	return nla_total_size(payload) - nla_attr_size(payload);
+}
+
+struct nlattr *nla_reserve (struct nl_msg *msg, int attrtype, int attrlen);
+
+static inline int
+nla_len (const struct nlattr *nla)
+{
+	return nla->nla_len - NLA_HDRLEN;
+}
+
+static inline int
+nla_type (const struct nlattr *nla)
+{
+	return nla->nla_type & NLA_TYPE_MASK;
+}
+
+static inline void *
+nla_data (const struct nlattr *nla)
+{
+	nm_assert (nla);
+	return (char *) nla + NLA_HDRLEN;
+}
+
+static inline uint8_t
+nla_get_u8 (const struct nlattr *nla)
+{
+	return *(const uint8_t *) nla_data (nla);
+}
+
+static inline uint8_t
+nla_get_u8_cond (/*const*/ struct nlattr *const*tb, int attr, uint8_t default_val)
+{
+	nm_assert (tb);
+	nm_assert (attr >= 0);
+
+	return tb[attr] ? nla_get_u8 (tb[attr]) : default_val;
+}
+
+static inline uint16_t
+nla_get_u16 (const struct nlattr *nla)
+{
+	return *(const uint16_t *) nla_data (nla);
+}
+
+static inline uint32_t
+nla_get_u32(const struct nlattr *nla)
+{
+	return *(const uint32_t *) nla_data (nla);
+}
+
+uint64_t nla_get_u64 (const struct nlattr *nla);
+
+static inline char *
+nla_get_string (const struct nlattr *nla)
+{
+	return (char *) nla_data (nla);
+}
+
+size_t nla_strlcpy (char *dst, const struct nlattr *nla, size_t dstsize);
+
+int nla_memcpy (void *dest, const struct nlattr *src, int count);
+
+int nla_put (struct nl_msg *msg, int attrtype, int datalen, const void *data);
+
+static inline int
+nla_put_string (struct nl_msg *msg, int attrtype, const char *str)
+{
+	return nla_put(msg, attrtype, strlen(str) + 1, str);
+}
+
+#define NLA_PUT(msg, attrtype, attrlen, data) \
+	do { \
+		if (nla_put(msg, attrtype, attrlen, data) < 0) \
+			goto nla_put_failure; \
+	} while(0)
+
+#define NLA_PUT_TYPE(msg, type, attrtype, value) \
+	do { \
+		type __tmp = value; \
+		NLA_PUT(msg, attrtype, sizeof(type), &__tmp); \
+	} while(0)
+
+#define NLA_PUT_U8(msg, attrtype, value) \
+	NLA_PUT_TYPE(msg, uint8_t, attrtype, value)
+
+#define NLA_PUT_U16(msg, attrtype, value) \
+	NLA_PUT_TYPE(msg, uint16_t, attrtype, value)
+
+#define NLA_PUT_U32(msg, attrtype, value) \
+	NLA_PUT_TYPE(msg, uint32_t, attrtype, value)
+
+#define NLA_PUT_U64(msg, attrtype, value) \
+	NLA_PUT_TYPE(msg, uint64_t, attrtype, value)
+
+#define NLA_PUT_STRING(msg, attrtype, value) \
+	NLA_PUT(msg, attrtype, (int) strlen(value) + 1, value)
+
+struct nlattr *nla_find (const struct nlattr *head, int len, int attrtype);
+
+static inline int
+nla_ok (const struct nlattr *nla, int remaining)
+{
+	return remaining >= sizeof(*nla) &&
+	       nla->nla_len >= sizeof(*nla) &&
+	       nla->nla_len <= remaining;
+}
+
+static inline struct nlattr *
+nla_next(const struct nlattr *nla, int *remaining)
+{
+	int totlen = NLA_ALIGN(nla->nla_len);
+
+	*remaining -= totlen;
+	return (struct nlattr *) ((char *) nla + totlen);
+}
+
+#define nla_for_each_attr(pos, head, len, rem) \
+	for (pos = head, rem = len; \
+	     nla_ok(pos, rem); \
+	     pos = nla_next(pos, &(rem)))
+
+#define nla_for_each_nested(pos, nla, rem) \
+	for (pos = (struct nlattr *) nla_data(nla), rem = nla_len(nla); \
+	     nla_ok(pos, rem); \
+	     pos = nla_next(pos, &(rem)))
+
+void nla_nest_cancel (struct nl_msg *msg, const struct nlattr *attr);
+struct nlattr *nla_nest_start (struct nl_msg *msg, int attrtype);
+int nla_nest_end (struct nl_msg *msg, struct nlattr *start);
+
+int nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
+               const struct nla_policy *policy);
+
+static inline int
+nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla,
+                  const struct nla_policy *policy)
+{
+	return nla_parse (tb, maxtype, nla_data(nla), nla_len(nla), policy);
+}
+
+/*****************************************************************************/
+
+struct nl_msg *nlmsg_alloc (void);
+
+struct nl_msg *nlmsg_alloc_size (size_t max);
+
+struct nl_msg *nlmsg_alloc_inherit (struct nlmsghdr *hdr);
+
+struct nl_msg *nlmsg_alloc_convert (struct nlmsghdr *hdr);
+
+struct nl_msg *nlmsg_alloc_simple (int nlmsgtype, int flags);
+
+void *nlmsg_reserve (struct nl_msg *n, size_t len, int pad);
+
+int nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad);
+
+void nlmsg_free (struct nl_msg *msg);
+
+static inline int
+nlmsg_size (int payload)
+{
+	nm_assert (payload >= 0 && payload < G_MAXINT - NLMSG_HDRLEN - 4);
+	return NLMSG_HDRLEN + payload;
+}
+
+static inline int
+nlmsg_total_size (int payload)
+{
+	return NLMSG_ALIGN (nlmsg_size (payload));
+}
+
+static inline int
+nlmsg_ok (const struct nlmsghdr *nlh, int remaining)
+{
+	return (remaining >= (int)sizeof(struct nlmsghdr) &&
+	       nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&
+	       nlh->nlmsg_len <= remaining);
+}
+
+static inline struct nlmsghdr *
+nlmsg_next (struct nlmsghdr *nlh, int *remaining)
+{
+	int totlen = NLMSG_ALIGN(nlh->nlmsg_len);
+
+	*remaining -= totlen;
+
+	return (struct nlmsghdr *) ((unsigned char *) nlh + totlen);
+}
+
+int nlmsg_get_proto (struct nl_msg *msg);
+void nlmsg_set_proto (struct nl_msg *msg, int protocol);
+
+void nlmsg_set_src (struct nl_msg *msg, struct sockaddr_nl *addr);
+
+struct ucred *nlmsg_get_creds (struct nl_msg *msg);
+void nlmsg_set_creds (struct nl_msg *msg, struct ucred *creds);
+
+static inline void
+_nm_auto_nl_msg_cleanup (struct nl_msg **ptr)
+{
+	nlmsg_free (*ptr);
+}
+#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup)
+
+static inline void *
+nlmsg_data (const struct nlmsghdr *nlh)
+{
+	return (unsigned char *) nlh + NLMSG_HDRLEN;
+}
+
+static inline void *
+nlmsg_tail (const struct nlmsghdr *nlh)
+{
+	return (unsigned char *) nlh + NLMSG_ALIGN(nlh->nlmsg_len);
+}
+
+struct nlmsghdr *nlmsg_hdr (struct nl_msg *n);
+
+static inline int
+nlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen)
+{
+	if (nlh->nlmsg_len < nlmsg_size (hdrlen))
+		return 0;
+
+	return 1;
+}
+
+static inline int
+nlmsg_datalen (const struct nlmsghdr *nlh)
+{
+	return nlh->nlmsg_len - NLMSG_HDRLEN;
+}
+
+static inline int
+nlmsg_attrlen (const struct nlmsghdr *nlh, int hdrlen)
+{
+	return NM_MAX ((int) (nlmsg_datalen (nlh) - NLMSG_ALIGN (hdrlen)), 0);
+}
+
+static inline struct nlattr *
+nlmsg_attrdata (const struct nlmsghdr *nlh, int hdrlen)
+{
+	unsigned char *data = nlmsg_data(nlh);
+	return (struct nlattr *) (data + NLMSG_ALIGN(hdrlen));
+}
+
+static inline struct nlattr *
+nlmsg_find_attr (struct nlmsghdr *nlh, int hdrlen, int attrtype)
+{
+	return nla_find (nlmsg_attrdata (nlh, hdrlen),
+	                 nlmsg_attrlen (nlh, hdrlen),
+	                 attrtype);
+}
+
+int nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
+                 int maxtype, const struct nla_policy *policy);
+
+struct nlmsghdr *nlmsg_put (struct nl_msg *n, uint32_t pid, uint32_t seq,
+                            int type, int payload, int flags);
+
+/*****************************************************************************/
+
+#define NL_AUTO_PORT 0
+#define NL_AUTO_SEQ  0
+
+struct nl_sock;
+
+struct nl_sock *nl_socket_alloc (void);
+
+void nl_socket_free (struct nl_sock *sk);
+
+int nl_socket_get_fd (const struct nl_sock *sk);
+
+struct sockaddr_nl *nlmsg_get_dst (struct nl_msg *msg);
+
+size_t nl_socket_get_msg_buf_size (struct nl_sock *sk);
+int nl_socket_set_msg_buf_size (struct nl_sock *sk, size_t bufsize);
+
+int nl_socket_set_buffer_size (struct nl_sock *sk, int rxbuf, int txbuf);
+
+int nl_socket_set_passcred (struct nl_sock *sk, int state);
+
+int nl_socket_set_nonblocking (const struct nl_sock *sk);
+
+void nl_socket_disable_msg_peek (struct nl_sock *sk);
+
+uint32_t nl_socket_get_local_port (const struct nl_sock *sk);
+
+int nl_socket_add_memberships (struct nl_sock *sk, int group, ...);
+
+int nl_connect (struct nl_sock *sk, int protocol);
+
+int nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
+             unsigned char **buf, struct ucred **creds);
+
+int nl_send (struct nl_sock *sk, struct nl_msg *msg);
+
+int nl_send_auto (struct nl_sock *sk, struct nl_msg *msg);
+
+/*****************************************************************************/
+
+enum nl_cb_action {
+	/* Proceed with wathever would come next */
+	NL_OK,
+	/* Skip this message */
+	NL_SKIP,
+	/* Stop parsing altogether and discard remaining messages */
+	NL_STOP,
+};
+
+typedef int (*nl_recvmsg_msg_cb_t) (struct nl_msg *msg, void *arg);
+
+typedef int (*nl_recvmsg_err_cb_t) (struct sockaddr_nl *nla,
+                                    struct nlmsgerr *nlerr, void *arg);
+
+struct nl_cb {
+	nl_recvmsg_msg_cb_t     valid_cb;
+	void *                  valid_arg;
+
+	nl_recvmsg_msg_cb_t     finish_cb;
+	void *                  finish_arg;
+
+	nl_recvmsg_msg_cb_t     ack_cb;
+	void *                  ack_arg;
+
+	nl_recvmsg_err_cb_t     err_cb;
+	void *                  err_arg;
+};
+
+int nl_sendmsg (struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr);
+
+int nl_send_iovec (struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen);
+
+void nl_complete_msg (struct nl_sock *sk, struct nl_msg *msg);
+
+int nl_recvmsgs (struct nl_sock *sk, const struct nl_cb *cb);
+
+int nl_wait_for_ack (struct nl_sock *sk,
+                     const struct nl_cb *cb);
+
+int nl_socket_set_ext_ack (struct nl_sock *sk, gboolean enable);
+
+/*****************************************************************************/
+
+void *genlmsg_put (struct nl_msg *msg, uint32_t port, uint32_t seq, int family,
+                   int hdrlen, int flags, uint8_t cmd, uint8_t version);
+void *genlmsg_data (const struct genlmsghdr *gnlh);
+void *genlmsg_user_hdr (const struct genlmsghdr *gnlh);
+struct genlmsghdr *genlmsg_hdr (struct nlmsghdr *nlh);
+void *genlmsg_user_data (const struct genlmsghdr *gnlh, const int hdrlen);
+struct nlattr *genlmsg_attrdata (const struct genlmsghdr *gnlh, int hdrlen);
+int genlmsg_len (const struct genlmsghdr *gnlh);
+int genlmsg_attrlen (const struct genlmsghdr *gnlh, int hdrlen);
+int genlmsg_valid_hdr (struct nlmsghdr *nlh, int hdrlen);
+int genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
+                   int maxtype, const struct nla_policy *policy);
+
+int genl_ctrl_resolve (struct nl_sock *sk, const char *name);
+
+/*****************************************************************************/
+
+#endif /* __NM_NETLINK_H__ */
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index b664e8a9..114bf4b3 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -589,6 +589,8 @@ nmp_utils_ip_config_source_coerce_to_rtprot (NMIPConfigSource source)
 	switch (source) {
 	case NM_IP_CONFIG_SOURCE_KERNEL:
 		return RTPROT_KERNEL;
+	case NM_IP_CONFIG_SOURCE_IP6LL:
+		return RTPROT_KERNEL;
 	case NM_IP_CONFIG_SOURCE_DHCP:
 		return RTPROT_DHCP;
 	case NM_IP_CONFIG_SOURCE_NDISC:
@@ -656,6 +658,7 @@ nmp_utils_ip_config_source_to_string (NMIPConfigSource source, char *buf, gsize
 	case NM_IP_CONFIG_SOURCE_KERNEL:          s = "kernel"; break;
 	case NM_IP_CONFIG_SOURCE_SHARED:          s = "shared"; break;
 	case NM_IP_CONFIG_SOURCE_IP4LL:           s = "ipv4ll"; break;
+	case NM_IP_CONFIG_SOURCE_IP6LL:           s = "ipv6ll"; break;
 	case NM_IP_CONFIG_SOURCE_PPP:             s = "ppp"; break;
 	case NM_IP_CONFIG_SOURCE_WWAN:            s = "wwan"; break;
 	case NM_IP_CONFIG_SOURCE_VPN:             s = "vpn"; break;
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index c7ed90e3..84d862d2 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -353,6 +353,45 @@ nm_platform_process_events (NMPlatform *self)
 		klass->process_events (self);
 }
 
+const NMPlatformLink *
+nm_platform_process_events_ensure_link (NMPlatform *self,
+                                        int ifindex,
+                                        const char *ifname)
+{
+	const NMPObject *obj;
+	gboolean refreshed = FALSE;
+
+	g_return_val_if_fail (NM_IS_PLATFORM (self), NULL);
+
+	if (ifindex <= 0 && !ifname)
+		return NULL;
+
+	/* we look into the cache, whether a link for given ifindex/ifname
+	 * exits. If not, we poll the netlink socket, maybe the event
+	 * with the link is waiting.
+	 *
+	 * Then we try again to find the object.
+	 *
+	 * If the link is already cached the first time, we avoid polling
+	 * the netlink socket. */
+again:
+	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (self),
+	                                  ifindex,
+	                                  ifname,
+	                                  FALSE, /* also invisible. We don't care here whether udev is ready */
+	                                  NM_LINK_TYPE_NONE,
+	                                  NULL, NULL);
+	if (obj)
+		return NMP_OBJECT_CAST_LINK (obj);
+	if (!refreshed) {
+		refreshed = TRUE;
+		nm_platform_process_events (self);
+		goto again;
+	}
+
+	return NULL;
+}
+
 /*****************************************************************************/
 
 /**
@@ -597,11 +636,11 @@ nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name)
 	 * further by moving children/slaves to the end. */
 	g_ptr_array_sort_with_data (links, _link_get_all_presort, GINT_TO_POINTER (sort_by_name));
 
-	unseen = g_hash_table_new (g_direct_hash, g_direct_equal);
+	unseen = g_hash_table_new (nm_direct_hash, NULL);
 	for (i = 0; i < links->len; i++) {
 		item = NMP_OBJECT_CAST_LINK (links->pdata[i]);
 		nm_assert (item->ifindex > 0);
-		if (!nm_g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL))
+		if (!g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL))
 			nm_assert_not_reached ();
 	}
 
@@ -1118,6 +1157,21 @@ nm_platform_link_supports_slaves (NMPlatform *self, int ifindex)
 }
 
 /**
+ * nm_platform_refresh_all:
+ * @self: platform instance
+ * @obj_type: The object type to request.
+ *
+ * Resync and re-request all objects from kernel of a certain @obj_type.
+ */
+void
+nm_platform_refresh_all (NMPlatform *self, NMPObjectType obj_type)
+{
+	_CHECK_SELF_VOID (self, klass);
+
+	klass->refresh_all (self, obj_type);
+}
+
+/**
  * nm_platform_link_refresh:
  * @self: platform instance
  * @ifindex: Interface index
@@ -1325,30 +1379,26 @@ gconstpointer
 nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length)
 {
 	const NMPlatformLink *pllink;
-	gconstpointer a = NULL;
-	guint8 l = 0;
 
 	_CHECK_SELF (self, klass, NULL);
 
-	if (length)
-		*length = 0;
-
 	g_return_val_if_fail (ifindex > 0, NULL);
 
 	pllink = nm_platform_link_get (self, ifindex);
-	if (pllink && pllink->addr.len > 0) {
-		if (pllink->addr.len > NM_UTILS_HWADDR_LEN_MAX) {
-			if (length)
-				*length = 0;
-			g_return_val_if_reached (NULL);
-		}
-		a = pllink->addr.data;
-		l = pllink->addr.len;
+
+	if (   !pllink
+	    || pllink->addr.len <= 0) {
+		NM_SET_OUT (length, 0);
+		return NULL;
 	}
 
-	if (length)
-		*length = l;
-	return a;
+	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;
 }
 
 /**
@@ -1844,6 +1894,12 @@ nm_platform_link_get_lnk_sit (NMPlatform *self, int ifindex, const NMPlatformLin
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_SIT, out_link);
 }
 
+const NMPlatformLnkTun *
+nm_platform_link_get_lnk_tun (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_TUN, out_link);
+}
+
 const NMPlatformLnkVlan *
 nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
@@ -1990,33 +2046,43 @@ nm_platform_link_vxlan_add (NMPlatform *self,
  * @vnet_hdr: whether to set the IFF_VNET_HDR flag
  * @multi_queue: whether to set the IFF_MULTI_QUEUE flag
  * @out_link: on success, the link object
+ * @out_fd: (allow-none): if give, return the file descriptor for the
+ *   created device. Note that when creating a non-persistent device,
+ *   this argument is mandatory, otherwise it makes no sense
+ *   to create such an interface.
+ *   The caller is responsible for closing this file descriptor.
  *
  * Create a TUN or TAP interface.
  */
 NMPlatformError
 nm_platform_link_tun_add (NMPlatform *self,
                           const char *name,
-                          gboolean tap,
-                          gint64 owner,
-                          gint64 group,
-                          gboolean pi,
-                          gboolean vnet_hdr,
-                          gboolean multi_queue,
-                          const NMPlatformLink **out_link)
+                          const NMPlatformLnkTun *props,
+                          const NMPlatformLink **out_link,
+                          int *out_fd)
 {
+	char b[255];
 	NMPlatformError plerr;
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
 	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (NM_IN_SET (props->type, IFF_TUN, IFF_TAP), NM_PLATFORM_ERROR_BUG);
+
+	/* creating a non-persistant device requires that the caller handles
+	 * the file descriptor. */
+	g_return_val_if_fail (props->persist || out_fd, NM_PLATFORM_ERROR_BUG);
 
-	plerr = _link_add_check_existing (self, name, tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN, out_link);
+	NM_SET_OUT (out_fd, -1);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_TUN, out_link);
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("link: adding %s '%s' owner %" G_GINT64_FORMAT " group %" G_GINT64_FORMAT,
-	       tap ? "tap" : "tun", name, owner, group);
-	if (!klass->tun_add (self, name, tap, owner, group, pi, vnet_hdr, multi_queue, out_link))
+	_LOGD ("link: adding tun '%s' %s",
+	       name, nm_platform_lnk_tun_to_string (props, b, sizeof (b)));
+	if (!klass->link_tun_add (self, name, props, out_link, out_fd))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
@@ -2620,44 +2686,100 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe
 	return TRUE;
 }
 
+/**
+ * nm_platform_link_tun_get_properties:
+ * @self: the #NMPlatform instance
+ * @ifindex: the ifindex to look up
+ * @out_properties: (out): (allow-none): return the read properties
+ *
+ * Only recent versions of kernel export tun properties via netlink.
+ * So, if that's the case, then we have the NMPlatformLnkTun instance
+ * in the platform cache ready to return. Otherwise, this function
+ * falls back reading sysctl to obtain the tun properties. That
+ * is racy, because querying sysctl means that the object might
+ * be already removed from cache (while NM didn't yet process the
+ * netlink message).
+ *
+ * Hence, to lookup the tun properties, you always need to use this
+ * function, and use it with care knowing that it might obtain its
+ * data by reading sysctl. Note that we don't want to add this workaround
+ * to the platform cache itself, because the cache should (mainly)
+ * contain data from netlink. To access the sysctl side channel, the
+ * user needs to do explicitly.
+ *
+ * Returns: #TRUE, if the properties could be read. */
 gboolean
-nm_platform_link_tun_get_properties (NMPlatform *self, int ifindex, NMPlatformTunProperties *props)
+nm_platform_link_tun_get_properties (NMPlatform *self,
+                                     int ifindex,
+                                     NMPlatformLnkTun *out_properties)
 {
-	nm_auto_close int dirfd = -1;
+	const NMPObject *plobj;
+	const NMPObject *pllnk;
 	char ifname[IFNAMSIZ];
+	gint64 owner;
+	gint64 group;
 	gint64 flags;
-	gboolean success = TRUE;
+
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (props, FALSE);
-
-	memset (props, 0, sizeof (*props));
-	props->owner = -1;
-	props->group = -1;
 
-	dirfd = nm_platform_sysctl_open_netdir (self, ifindex, ifname);
-	if (dirfd < 0)
+	/* we consider also invisible links (those that are not yet in udev). */
+	plobj = nm_platform_link_get_obj (self, ifindex, FALSE);
+	if (!plobj)
+		return FALSE;
+	if (NMP_OBJECT_CAST_LINK (plobj)->type != NM_LINK_TYPE_TUN)
 		return FALSE;
 
-	props->owner = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "owner"), 10, -1, G_MAXINT64, -1);
-	if (errno)
-		success = FALSE;
+	pllnk = plobj->_link.netlink.lnk;
+	if (pllnk) {
+		nm_assert (NMP_OBJECT_GET_TYPE (pllnk) == NMP_OBJECT_TYPE_LNK_TUN);
+		nm_assert (NMP_OBJECT_GET_CLASS (pllnk)->lnk_link_type == NM_LINK_TYPE_TUN);
 
-	props->group = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "group"), 10, -1, G_MAXINT64, -1);
-	if (errno)
-		success = FALSE;
+		/* recent kernels expose tun properties via netlink and thus we have them
+		 * in the platform cache. */
+		NM_SET_OUT (out_properties, pllnk->lnk_tun);
+		return TRUE;
+	}
 
-	flags = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "tun_flags"), 16, 0, G_MAXINT64, -1);
-	if (flags >= 0) {
-		props->mode = ((flags & (IFF_TUN | IFF_TAP)) == IFF_TUN) ? "tun" : "tap";
-		props->no_pi = !!(flags & IFF_NO_PI);
-		props->vnet_hdr = !!(flags & IFF_VNET_HDR);
-		props->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE);
-	} else
-		success = FALSE;
+	/* fallback to reading sysctl. */
+	{
+		nm_auto_close int dirfd = -1;
 
-	return success;
+		dirfd = nm_platform_sysctl_open_netdir (self, ifindex, ifname);
+		if (dirfd < 0)
+			return FALSE;
+
+		owner = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "owner"), 10, -1, G_MAXUINT32, -2);
+		if (owner == -2)
+			return FALSE;
+
+		group = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "group"), 10, -1, G_MAXUINT32, -2);
+		if (group == -2)
+			return FALSE;
+
+		flags = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "tun_flags"), 16, 0, G_MAXINT64, -1);
+		if (flags == -1)
+			return FALSE;
+	}
+
+	if (out_properties) {
+		memset (out_properties, 0, sizeof (*out_properties));
+		if (owner != -1) {
+			out_properties->owner_valid = TRUE;
+			out_properties->owner = owner;
+		}
+		if (group != -1) {
+			out_properties->group_valid = TRUE;
+			out_properties->group = group;
+		}
+		out_properties->type = (flags & TUN_TYPE_MASK);
+		out_properties->pi = !(flags & IFF_NO_PI);
+		out_properties->vnet_hdr = !!(flags & IFF_VNET_HDR);
+		out_properties->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE);
+		out_properties->persist = !!(flags & IFF_PERSIST);
+	}
+	return TRUE;
 }
 
 gboolean
@@ -2932,7 +3054,7 @@ nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel (const NMPObject *ob
  * @user_data: user data for @predicate
  *
  * Returns the result of lookup in a GPtrArray. The result array contains
- * references objects from the cache, it's destroy function will unref them.
+ * references objects from the cache, its destroy function will unref them.
  *
  * The user must unref the GPtrArray, which will also unref the NMPObject
  * elements.
@@ -3111,24 +3233,68 @@ nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr addr
 }
 
 static gboolean
-array_contains_ip6_address (const GPtrArray *addresses, const NMPlatformIP6Address *address, gint32 now)
+_addr_array_clean_expired (int addr_family, int ifindex, GPtrArray *array, guint32 now, GHashTable **idx)
 {
-	guint len = addresses ? addresses->len : 0;
 	guint i;
+	gboolean any_addrs = FALSE;
 
-	for (i = 0; i < len; i++) {
-		NMPlatformIP6Address *candidate = NMP_OBJECT_CAST_IP6_ADDRESS (addresses->pdata[i]);
+	nm_assert_addr_family (addr_family);
+	nm_assert (ifindex > 0);
+	nm_assert (now > 0);
 
-		if (IN6_ARE_ADDR_EQUAL (&candidate->address, &address->address) && candidate->plen == address->plen) {
-			guint32 lifetime, preferred;
+	if (!array)
+		return FALSE;
+
+	/* remove all addresses that are already expired. */
+	for (i = 0; i < array->len; i++) {
+		const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS (array->pdata[i]);
+
+#if NM_MORE_ASSERTS > 10
+		nm_assert (a);
+		nm_assert (a->ifindex == ifindex);
+		{
+			const NMPObject *o = NMP_OBJECT_UP_CAST (a);
+			guint j;
 
-			if (nm_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
-			                           now, &lifetime, &preferred))
-				return TRUE;
+			nm_assert (NMP_OBJECT_GET_CLASS (o)->addr_family == addr_family);
+			for (j = i + 1; j < array->len; j++) {
+				const NMPObject *o2 = array->pdata[j];
+
+				nm_assert (NMP_OBJECT_GET_TYPE (o) == NMP_OBJECT_GET_TYPE (o2));
+				nm_assert (!nmp_object_id_equal (o, o2));
+			}
 		}
+#endif
+
+		if (   addr_family == AF_INET6
+		    && NM_FLAGS_HAS (a->n_ifa_flags, IFA_F_TEMPORARY)) {
+			/* temporary addresses are never added explicitly by NetworkManager but
+			 * kernel adds them via mngtempaddr flag.
+			 *
+			 * We drop them from this list. */
+			goto clear_and_next;
+		}
+
+		if (!nm_utils_lifetime_get (a->timestamp, a->lifetime, a->preferred,
+		                            now, NULL))
+			goto clear_and_next;
+
+		if (idx) {
+			if (G_UNLIKELY (!*idx)) {
+				*idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash,
+				                         (GEqualFunc) nmp_object_id_equal);
+			}
+			if (!g_hash_table_add (*idx, (gpointer) NMP_OBJECT_UP_CAST (a)))
+				nm_assert_not_reached ();
+		}
+		any_addrs = TRUE;
+		continue;
+
+clear_and_next:
+		nmp_object_unref (g_steal_pointer (&array->pdata[i]));
 	}
 
-	return FALSE;
+	return any_addrs;
 }
 
 static gboolean
@@ -3179,7 +3345,7 @@ ip4_addr_subnets_build_index (const GPtrArray *addresses,
 
 	nm_assert (addresses && addresses->len);
 
-	subnets = g_hash_table_new (NULL, NULL);
+	subnets = g_hash_table_new (nm_direct_hash, NULL);
 
 	/* Build a hash table of all addresses per subnet */
 	for (i = 0; i < addresses->len; i++) {
@@ -3312,39 +3478,8 @@ nm_platform_ip4_address_sync (NMPlatform *self,
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	if (known_addresses) {
-		/* remove all addresses that are already expired. */
-		for (i = 0; i < known_addresses->len; i++) {
-			const NMPObject *o;
-
-			o = known_addresses->pdata[i];
-			nm_assert (o);
-
-			known_address = NMP_OBJECT_CAST_IP4_ADDRESS (known_addresses->pdata[i]);
-
-			if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
-			                            now, &lifetime, &preferred))
-				goto delete_and_next;
-
-			if (G_UNLIKELY (!known_addresses_idx)) {
-				known_addresses_idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash,
-				                                        (GEqualFunc) nmp_object_id_equal);
-			}
-			if (!nm_g_hash_table_insert (known_addresses_idx, (gpointer) o, (gpointer) o)) {
-				/* duplicate? Keep only the first instance. */
-				goto delete_and_next;
-			}
-
-			continue;
-delete_and_next:
-			nmp_object_unref (o);
-			known_addresses->pdata[i] = NULL;
-		}
-
-		if (   !known_addresses_idx
-		    || g_hash_table_size (known_addresses_idx) == 0)
-			known_addresses = NULL;
-	}
+	if (!_addr_array_clean_expired (AF_INET, ifindex, known_addresses, now, &known_addresses_idx))
+		known_addresses = NULL;
 
 	plat_addresses = nm_platform_lookup_clone (self,
 	                                           nmp_lookup_init_object (&lookup,
@@ -3441,8 +3576,9 @@ delete_and_next:
 
 		known_address = NMP_OBJECT_CAST_IP4_ADDRESS (o);
 
-		if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
-		                            now, &lifetime, &preferred))
+		lifetime = nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
+		                                  now, &preferred);
+		if (!lifetime)
 			goto delete_and_next2;
 
 		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen,
@@ -3464,9 +3600,15 @@ delete_and_next2:
  * nm_platform_ip6_address_sync:
  * @self: platform instance
  * @ifindex: Interface index
- * @known_addresses: List of IPv6 addresses, as NMPObject. The list
- *   is not modified.
- * @keep_link_local: Don't remove link-local address
+ * @known_addresses: List of addresses. The list will be modified and only
+ *   addresses that were successfully added will be kept in the list.
+ *   That means, expired addresses and addresses that could not be added
+ *   will be dropped.
+ *   Hence, the input argument @known_addresses is also an output argument
+ *   telling which addresses were succesfully added.
+ *   Addresses are removed by unrefing the instance via nmp_object_unref()
+ *   and leaving a NULL tombstone.
+ * @full_sync: Also remove link-local and temporary addresses.
  *
  * A convenience function to synchronize addresses for a specific interface
  * with the least possible disturbance. It simply removes addresses that are
@@ -3477,32 +3619,117 @@ delete_and_next2:
 gboolean
 nm_platform_ip6_address_sync (NMPlatform *self,
                               int ifindex,
-                              const GPtrArray *known_addresses,
-                              gboolean keep_link_local)
+                              GPtrArray *known_addresses,
+                              gboolean full_sync)
 {
 	gs_unref_ptrarray GPtrArray *plat_addresses = NULL;
-	NMPlatformIP6Address *address;
 	gint32 now = nm_utils_get_monotonic_timestamp_s ();
-	guint i;
+	guint i_plat, i_know;
+	gs_unref_hashtable GHashTable *known_addresses_idx = NULL;
 	NMPLookup lookup;
 	guint32 ifa_flags;
 
-	/* Delete unknown addresses */
+	if (!_addr_array_clean_expired (AF_INET6, ifindex, known_addresses, now, &known_addresses_idx))
+		known_addresses = NULL;
+
+	/* @plat_addresses is in decreasing priority order (highest priority addresses first), contrary to
+	 * @known_addresses which is in increasing priority order (lowest priority addresses first). */
 	plat_addresses = nm_platform_lookup_clone (self,
 	                                           nmp_lookup_init_object (&lookup,
 	                                                                   NMP_OBJECT_TYPE_IP6_ADDRESS,
 	                                                                   ifindex),
 	                                           NULL, NULL);
+
 	if (plat_addresses) {
-		for (i = 0; i < plat_addresses->len; i++) {
-			address = NMP_OBJECT_CAST_IP6_ADDRESS (plat_addresses->pdata[i]);
+		guint known_addresses_len;
+
+		known_addresses_len = known_addresses ? known_addresses->len : 0;
+
+		/* First, compare every address whether it is still a "known address", that is, whether
+		 * to keep it or to delete it.
+		 *
+		 * If we don't find a matching valid address in @known_addresses, we will delete
+		 * plat_addr.
+		 *
+		 * Certain addresses, like temporary addresses, are ignored by this function
+		 * if not run with full_sync. These addresses are usually not managed by NetworkManager
+		 * directly, or at least, they are not managed via nm_platform_ip6_address_sync().
+		 * Only in full_sync mode, we really want to get rid of them (usually, when we take
+		 * the interface down).
+		 *
+		 * Note that we mark handled addresses by setting it to %NULL in @plat_addresses array. */
+		for (i_plat = 0; i_plat < plat_addresses->len; i_plat++) {
+			const NMPObject *plat_obj = plat_addresses->pdata[i_plat];
+			const NMPObject *know_obj;
+			const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_obj);
+
+			if (NM_FLAGS_HAS (plat_addr->n_ifa_flags, IFA_F_TEMPORARY)) {
+				if (!full_sync) {
+					/* just mark as handled, without actually deleting the address. */
+					goto clear_and_next;
+				}
+			} else if (known_addresses_idx) {
+				know_obj = g_hash_table_lookup (known_addresses_idx, plat_obj);
+				if (   know_obj
+				    && plat_addr->plen == NMP_OBJECT_CAST_IP6_ADDRESS (know_obj)->plen) {
+					/* technically, plen is not part of the ID for IPv6 addresses and thus
+					 * @plat_addr is essentially the same address as @know_addr (regrading
+					 * its identity, not its other attributes).
+					 * However, we cannot modify an existing addresses' plen without
+					 * removing and readding it. Thus, only keep plat_addr, if the plen
+					 * matches.
+					 *
+					 * keep this one, and continue */
+					continue;
+				}
+			}
+
+			nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen);
+clear_and_next:
+			nmp_object_unref (g_steal_pointer (&plat_addresses->pdata[i_plat]));
+		}
 
-			/* Leave link local address management to the kernel */
-			if (keep_link_local && IN6_IS_ADDR_LINKLOCAL (&address->address))
+		/* Next, we must preserve the priority of the routes. That is, source address
+		 * selection will choose addresses in the order as they are reported by kernel.
+		 * Note that the order in @plat_addresses of the remaining matches is highest
+		 * priority first.
+		 * We need to compare this to the order in @known_addresses (which has lowest
+		 * priority first).
+		 *
+		 * If we find a first discrepancy, we need to delete all remaining addresses
+		 * from that point on, because below we must re-add all the addresses in the
+		 * right order to get their priority right. */
+		i_plat = plat_addresses->len;
+		i_know = 0;
+		while (i_plat > 0) {
+			const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_addresses->pdata[--i_plat]);
+
+			if (!plat_addr)
 				continue;
 
-			if (!array_contains_ip6_address (known_addresses, address, now))
-				nm_platform_ip6_address_delete (self, ifindex, address->address, address->plen);
+			for (; i_know < known_addresses_len; i_know++) {
+				const NMPlatformIP6Address *know_addr = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]);
+
+				if (!know_addr)
+					continue;
+
+				if (IN6_ARE_ADDR_EQUAL (&plat_addr->address, &know_addr->address)) {
+					/* we have a match. Mark address as handled. */
+					i_know++;
+					goto next_plat;
+				}
+
+				/* all remainging addresses need to be removed as well, so that we can
+				 * re-add them in the correct order. Signal that, by setting @i_know
+				 * so that the next @i_plat iteration, we won't enter the loop and
+				 * delete the address right away */
+				i_know = known_addresses_len;
+				break;
+			}
+
+			nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen);
+next_plat:
+			;
 		}
 	}
 
@@ -3513,19 +3740,18 @@ nm_platform_ip6_address_sync (NMPlatform *self,
 	            ? IFA_F_NOPREFIXROUTE
 	            : 0;
 
-	/* Add missing addresses */
-	for (i = 0; i < known_addresses->len; i++) {
-		const NMPlatformIP6Address *known_address = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i]);
+	/* Add missing addresses. New addresses are added by kernel with top
+	 * priority.
+	 */
+	for (i_know = 0; i_know < known_addresses->len; i_know++) {
+		const NMPlatformIP6Address *known_address = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]);
 		guint32 lifetime, preferred;
 
-		if (NM_FLAGS_HAS (known_address->n_ifa_flags, IFA_F_TEMPORARY)) {
-			/* Kernel manages these */
+		if (!known_address)
 			continue;
-		}
 
-		if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
-		                            now, &lifetime, &preferred))
-			continue;
+		lifetime = nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
+		                                  now, &preferred);
 
 		if (!nm_platform_ip6_address_add (self, ifindex, known_address->address,
 		                                  known_address->plen, known_address->peer_address,
@@ -3553,7 +3779,7 @@ nm_platform_ip_address_flush (NMPlatform *self,
 	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET))
 		success &= nm_platform_ip4_address_sync (self, ifindex, NULL);
 	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6))
-		success &= nm_platform_ip6_address_sync (self, ifindex, NULL, FALSE);
+		success &= nm_platform_ip6_address_sync (self, ifindex, NULL, TRUE);
 	return success;
 }
 
@@ -3692,7 +3918,8 @@ nm_platform_ip_route_sync (NMPlatform *self,
 
 	for (i_type = 0; routes && i_type < 2; i_type++) {
 		for (i = 0; i < routes->len; i++) {
-			NMPlatformError plerr;
+			NMPlatformError plerr, plerr2;
+			gboolean gateway_route_added = FALSE;
 
 			conf_o = routes->pdata[i];
 
@@ -3712,7 +3939,7 @@ nm_platform_ip_route_sync (NMPlatform *self,
 				routes_idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash,
 				                               (GEqualFunc) nmp_object_id_equal);
 			}
-			if (!nm_g_hash_table_insert (routes_idx, (gpointer) conf_o, (gpointer) conf_o)) {
+			if (!g_hash_table_insert (routes_idx, (gpointer) conf_o, (gpointer) conf_o)) {
 				_LOGD ("route-sync: skip adding duplicate route %s",
 				       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)));
 				continue;
@@ -3738,6 +3965,7 @@ nm_platform_ip_route_sync (NMPlatform *self,
 				}
 			}
 
+sync_route_add:
 			plerr = nm_platform_ip_route_add (self,
 			                                    NMP_NLM_FLAG_APPEND
 			                                  | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
@@ -3762,6 +3990,11 @@ nm_platform_ip_route_sync (NMPlatform *self,
 							       nmp_object_to_string (plat_entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
 						}
 					}
+				} else if (NMP_OBJECT_CAST_IP_ROUTE (conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) {
+					_LOGD ("route-sync: ignore failure to add IPv%c route: %s: %s",
+					       vt->is_ip4 ? '4' : '6',
+					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
 				} else if (   -((int) plerr) == EINVAL
 				           && out_temporary_not_available
 				           && _err_inval_due_to_ipv6_tentative_pref_src (self, conf_o)) {
@@ -3771,25 +4004,66 @@ nm_platform_ip_route_sync (NMPlatform *self,
 					if (!*out_temporary_not_available)
 						*out_temporary_not_available = g_ptr_array_new_full (0, (GDestroyNotify) nmp_object_unref);
 					g_ptr_array_add (*out_temporary_not_available, (gpointer) nmp_object_ref (conf_o));
-				} else if (NMP_OBJECT_CAST_IP_ROUTE (conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) {
-					_LOGD ("route-sync: ignore failure to add IPv%c route: %s: %s",
+				} else if (   !gateway_route_added
+				           && (   (   -((int) plerr) == ENETUNREACH
+				                   && vt->is_ip4
+				                   && !!NMP_OBJECT_CAST_IP4_ROUTE (conf_o)->gateway)
+				               || (   -((int) plerr) == EHOSTUNREACH
+				                   && !vt->is_ip4
+				                   && !IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (conf_o)->gateway)))) {
+					NMPObject oo;
+
+					if (vt->is_ip4) {
+						const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (conf_o);
+
+						nmp_object_stackinit (&oo,
+						                      NMP_OBJECT_TYPE_IP4_ROUTE,
+						                      &((NMPlatformIP4Route) {
+						                          .network = r->gateway,
+						                          .plen = 32,
+						                          .metric = r->metric,
+						                          .rt_source = r->rt_source,
+						                          .table_coerced = r->table_coerced,
+						                      }));
+					} else {
+						const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (conf_o);
+
+						nmp_object_stackinit (&oo,
+						                      NMP_OBJECT_TYPE_IP6_ROUTE,
+						                      &((NMPlatformIP6Route) {
+						                          .network = r->gateway,
+						                          .plen = 128,
+						                          .metric = r->metric,
+						                          .rt_source = r->rt_source,
+						                          .table_coerced = r->table_coerced,
+						                      }));
+					}
+
+					_LOGD ("route-sync: failure to add IPv%c route: %s: %s; try adding direct route to gateway %s",
 					       vt->is_ip4 ? '4' : '6',
 					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
-				} else {
-					const char *reason = "";
-
-					if (   -((int) plerr) == ENETUNREACH
-					    && (  vt->is_ip4
-					        ? !!NMP_OBJECT_CAST_IP4_ROUTE (conf_o)->gateway
-					        : !IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (conf_o)->gateway)))
-						reason = "; is the gateway directly reachable?";
+					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)),
+					       nmp_object_to_string (&oo, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
+
+					plerr2 = nm_platform_ip_route_add (self,
+					                                     NMP_NLM_FLAG_APPEND
+					                                   | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
+					                                   &oo);
+
+					if (plerr2 != NM_PLATFORM_ERROR_SUCCESS) {
+						_LOGD ("route-sync: failure to add gateway IPv%c route: %s: %s",
+						       vt->is_ip4 ? '4' : '6',
+						       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+						       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
+					}
 
-					_LOGW ("route-sync: failure to add IPv%c route: %s: %s%s",
+					gateway_route_added = TRUE;
+					goto sync_route_add;
+				} else {
+					_LOGW ("route-sync: failure to add IPv%c route: %s: %s",
 					       vt->is_ip4 ? '4' : '6',
 					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)),
-					       reason);
+					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
 					success = FALSE;
 				}
 			}
@@ -4713,6 +4987,7 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi
 	            "%s" /* encap limit */
 	            "%s" /* flow label */
 	            "%s" /* proto */
+	            " flags 0x%x"
 	            "",
 	            nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet6_ntop (&lnk->remote, str_remote1)),
 	            nm_sprintf_buf (str_local, " local %s", nm_utils_inet6_ntop (&lnk->local, str_local1)),
@@ -4721,7 +4996,8 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi
 	            lnk->tclass == 1 ? " tclass inherit" : nm_sprintf_buf (str_tclass, " tclass 0x%x", lnk->tclass),
 	            nm_sprintf_buf (str_encap, " encap-limit %u", lnk->encap_limit),
 	            nm_sprintf_buf (str_flow, " flow-label 0x05%x", lnk->flow_label),
-	            nm_sprintf_buf (str_proto, " proto %u", lnk->proto));
+	            nm_sprintf_buf (str_proto, " proto %u", lnk->proto),
+	            (guint) lnk->flags);
 	return buf;
 }
 
@@ -4842,6 +5118,43 @@ nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len
 }
 
 const char *
+nm_platform_lnk_tun_to_string (const NMPlatformLnkTun *lnk, char *buf, gsize len)
+{
+	char str_owner[50];
+	char str_group[50];
+	char str_type[50];
+	const char *type;
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	if (lnk->type == IFF_TUN)
+		type = "tun";
+	else if (lnk->type == IFF_TAP)
+		type = "tap";
+	else
+		type = nm_sprintf_buf (str_type, "tun type %u", (guint) lnk->type);
+
+	g_snprintf (buf, len,
+	            "%s" /* type */
+	            "%s" /* pi */
+	            "%s" /* vnet_hdr */
+	            "%s" /* multi_queue */
+	            "%s" /* persist */
+	            "%s" /* owner */
+	            "%s" /* group */
+	            "",
+	            type,
+	            lnk->pi ? " pi" : "",
+	            lnk->vnet_hdr ? " vnet_hdr" : "",
+	            lnk->multi_queue ? " multi_queue" : "",
+	            lnk->persist ? " persist" : "",
+	            lnk->owner_valid ? nm_sprintf_buf (str_owner, " owner %u", (guint) lnk->owner) : "",
+	            lnk->group_valid ? nm_sprintf_buf (str_group, " group %u", (guint) lnk->group) : "");
+	return buf;
+}
+
+const char *
 nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len)
 {
 	char *b;
@@ -5043,10 +5356,10 @@ NM_UTILS_FLAGS2STR_DEFINE (nm_platform_addr_flags2str, unsigned,
 	NM_UTILS_FLAGS2STR (IFA_F_OPTIMISTIC, "optimistic"),
 	NM_UTILS_FLAGS2STR (IFA_F_HOMEADDRESS, "homeaddress"),
 	NM_UTILS_FLAGS2STR (IFA_F_DEPRECATED, "deprecated"),
-	NM_UTILS_FLAGS2STR (IFA_F_TENTATIVE, "tentative"),
 	NM_UTILS_FLAGS2STR (IFA_F_PERMANENT, "permanent"),
 	NM_UTILS_FLAGS2STR (IFA_F_MANAGETEMPADDR, "mngtmpaddr"),
 	NM_UTILS_FLAGS2STR (IFA_F_NOPREFIXROUTE, "noprefixroute"),
+	NM_UTILS_FLAGS2STR (IFA_F_TENTATIVE, "tentative"),
 );
 
 NM_UTILS_ENUM2STR_DEFINE (nm_platform_route_scope2str, int,
@@ -5537,7 +5850,8 @@ nm_platform_lnk_ip6tnl_hash_update (const NMPlatformLnkIp6Tnl *obj, NMHashState
 	                     obj->tclass,
 	                     obj->encap_limit,
 	                     obj->proto,
-	                     obj->flow_label);
+	                     obj->flow_label,
+	                     obj->flags);
 }
 
 int
@@ -5552,6 +5866,7 @@ nm_platform_lnk_ip6tnl_cmp (const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6
 	NM_CMP_FIELD (a, b, encap_limit);
 	NM_CMP_FIELD (a, b, flow_label);
 	NM_CMP_FIELD (a, b, proto);
+	NM_CMP_FIELD (a, b, flags);
 	return 0;
 }
 
@@ -5670,6 +5985,38 @@ nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b)
 }
 
 void
+nm_platform_lnk_tun_hash_update (const NMPlatformLnkTun *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->type,
+	                     obj->owner,
+	                     obj->group,
+	                     NM_HASH_COMBINE_BOOLS (guint8,
+	                                            obj->owner_valid,
+	                                            obj->group_valid,
+	                                            obj->pi,
+	                                            obj->vnet_hdr,
+	                                            obj->multi_queue,
+	                                            obj->persist));
+}
+
+int
+nm_platform_lnk_tun_cmp (const NMPlatformLnkTun *a, const NMPlatformLnkTun *b)
+{
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, type);
+	NM_CMP_FIELD (a, b, owner);
+	NM_CMP_FIELD (a, b, group);
+	NM_CMP_FIELD_BOOL (a, b, owner_valid);
+	NM_CMP_FIELD_BOOL (a, b, group_valid);
+	NM_CMP_FIELD_BOOL (a, b, pi);
+	NM_CMP_FIELD_BOOL (a, b, vnet_hdr);
+	NM_CMP_FIELD_BOOL (a, b, multi_queue);
+	NM_CMP_FIELD_BOOL (a, b, persist);
+	return 0;
+}
+
+void
 nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h)
 {
 	nm_hash_update_vals (h,
@@ -6260,14 +6607,13 @@ nm_platform_cache_update_emit_signal (NMPlatform *self,
 	const NMPObject *o;
 	const NMPClass *klass;
 
-	nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, (NMPlatformSignalChangeType) NMP_CACHE_OPS_UNCHANGED, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_SIGNAL_REMOVED));
+	nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, NM_PLATFORM_SIGNAL_NONE,
+	                                                             NM_PLATFORM_SIGNAL_ADDED,
+	                                                             NM_PLATFORM_SIGNAL_CHANGED,
+	                                                             NM_PLATFORM_SIGNAL_REMOVED));
 
 	ASSERT_nmp_cache_ops (nm_platform_get_cache (self), cache_op, obj_old, obj_new);
 
-	nm_assert (NM_IN_SET (nm_platform_netns_get (self),
-	                      NULL,
-	                      nmp_netns_get_current ()));
-
 	NMTST_ASSERT_PLATFORM_NETNS_CURRENT (self);
 
 	switch (cache_op) {
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index f6bf02bf..e6cef63b 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -25,6 +25,7 @@
 #include <linux/if.h>
 #include <linux/if_addr.h>
 #include <linux/if_link.h>
+#include <linux/ip6_tunnel.h>
 
 #include "nm-dbus-interface.h"
 #include "nm-core-types-internal.h"
@@ -32,6 +33,7 @@
 #include "nm-core-utils.h"
 #include "nm-setting-vlan.h"
 #include "nm-setting-wired.h"
+#include "nm-setting-ip-tunnel.h"
 
 #define NM_TYPE_PLATFORM            (nm_platform_get_type ())
 #define NM_PLATFORM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_PLATFORM, NMPlatform))
@@ -169,6 +171,34 @@ typedef enum { /*< skip >*/
 	NM_PLATFORM_ERROR_CANT_SET_MTU,
 } NMPlatformError;
 
+typedef enum {
+
+	/* match-flags are strictly inclusive. That means,
+	 * by default nothing is matched, but if you enable a particular
+	 * flag, a candidate that matches passes the check.
+	 *
+	 * In other words: adding more flags can only extend the result
+	 * set of matching objects.
+	 *
+	 * Also, the flags form partitions. Like, an address can be either of
+	 * ADDRTYPE_NORMAL or ADDRTYPE_LINKLOCAL, but never both. Same for
+	 * the ADDRSTATE match types.
+	 */
+	NM_PLATFORM_MATCH_WITH_NONE                                 = 0,
+
+	NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL                      = (1LL <<  0),
+	NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL                   = (1LL <<  1),
+	NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY                        =   NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
+	                                                              | NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL,
+
+	NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL                     = (1LL <<  2),
+	NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE                  = (1LL <<  3),
+	NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED                  = (1LL <<  4),
+	NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY                       =   NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL
+	                                                              | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE
+	                                                              | NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED,
+} NMPlatformMatchFlags;
+
 #define NM_PLATFORM_LINK_OTHER_NETNS    (-1)
 
 #define __NMPlatformObject_COMMON \
@@ -254,6 +284,8 @@ struct _NMPlatformObject {
 	__NMPlatformObject_COMMON;
 };
 
+#define NM_PLATFORM_IP_ADDRESS_CAST(address) \
+	NM_CONSTCAST (NMPlatformIPAddress, (address), NMPlatformIPXAddress, NMPlatformIP4Address, NMPlatformIP6Address)
 
 #define __NMPlatformIPAddress_COMMON \
 	__NMPlatformObject_COMMON; \
@@ -402,7 +434,7 @@ typedef union {
 	 * do not exist from the point-of-view of platform users.
 	 * Such a route is not alive, according to nmp_object_is_alive().
 	 *
-	 * XXX: currently we ignore all flags except RTM_F_CLONED
+	 * NOTE: currently we ignore all flags except RTM_F_CLONED
 	 * and RTNH_F_ONLINK for IPv4.
 	 * We also may not properly consider the flags as part of the ID
 	 * in route-cmp. */ \
@@ -604,6 +636,7 @@ typedef struct {
 	guint8 encap_limit;
 	guint8 proto;
 	guint flow_label;
+	guint32 flags;
 } NMPlatformLnkIp6Tnl;
 
 typedef struct {
@@ -651,6 +684,21 @@ typedef struct {
 } NMPlatformLnkSit;
 
 typedef struct {
+	guint32 owner;
+	guint32 group;
+
+	guint8 type;
+
+	bool owner_valid:1;
+	bool group_valid:1;
+
+	bool pi:1;
+	bool vnet_hdr:1;
+	bool multi_queue:1;
+	bool persist:1;
+} NMPlatformLnkTun;
+
+typedef struct {
 	/* rtnl_link_vlan_get_id(), IFLA_VLAN_ID */
 	guint16 id;
 	NMVlanFlags flags;
@@ -677,15 +725,6 @@ typedef struct {
 	bool l3miss:1;
 } NMPlatformLnkVxlan;
 
-typedef struct {
-	gint64 owner;
-	gint64 group;
-	const char *mode;
-	bool no_pi:1;
-	bool vnet_hdr:1;
-	bool multi_queue:1;
-} NMPlatformTunProperties;
-
 typedef enum {
 	NM_PLATFORM_LINK_DUPLEX_UNKNOWN,
 	NM_PLATFORM_LINK_DUPLEX_HALF,
@@ -714,6 +753,8 @@ typedef struct {
 	gboolean (*sysctl_set) (NMPlatform *, const char *pathid, int dirfd, const char *path, const char *value);
 	char * (*sysctl_get) (NMPlatform *, const char *pathid, int dirfd, const char *path);
 
+	void (*refresh_all) (NMPlatform *self, NMPObjectType obj_type);
+
 	gboolean (*link_add) (NMPlatform *,
 	                      const char *name,
 	                      NMLinkType type,
@@ -809,12 +850,15 @@ typedef struct {
 	                          const NMPlatformLnkSit *props,
 	                          const NMPlatformLink **out_link);
 
+	gboolean (*link_tun_add) (NMPlatform *platform,
+	                          const char *name,
+	                          const NMPlatformLnkTun *props,
+	                          const NMPlatformLink **out_link,
+	                          int *out_fd);
+
 	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 (*tun_add) (NMPlatform *platform, const char *name, gboolean tap, gint64 owner, gint64 group, gboolean pi,
-	                     gboolean vnet_hdr, gboolean multi_queue, const NMPlatformLink **out_link);
-
 	gboolean    (*wifi_get_capabilities) (NMPlatform *, int ifindex, NMDeviceWifiCapabilities *caps);
 	gboolean    (*wifi_get_bssid)        (NMPlatform *, int ifindex, guint8 *bssid);
 	GByteArray *(*wifi_get_ssid)         (NMPlatform *, int ifindex);
@@ -1028,6 +1072,8 @@ gboolean nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char
 const char *nm_platform_if_indextoname (NMPlatform *self, int ifindex, char *out_ifname/* of size IFNAMSIZ */);
 int nm_platform_if_nametoindex (NMPlatform *self, const char *ifname);
 
+void nm_platform_refresh_all (NMPlatform *self, NMPObjectType obj_type);
+
 const NMPObject *nm_platform_link_get_obj (NMPlatform *self,
                                            int ifindex,
                                            gboolean visible_only);
@@ -1071,7 +1117,21 @@ gboolean nm_platform_link_is_connected (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_uses_arp (NMPlatform *self, int ifindex);
 guint32 nm_platform_link_get_mtu (NMPlatform *self, int ifindex);
 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);
@@ -1083,6 +1143,10 @@ const char *nm_platform_link_get_type_name (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_refresh (NMPlatform *self, int ifindex);
 void nm_platform_process_events (NMPlatform *self);
 
+const NMPlatformLink *nm_platform_process_events_ensure_link (NMPlatform *self,
+                                                              int ifindex,
+                                                              const char *ifname);
+
 gboolean nm_platform_link_set_up (NMPlatform *self, int ifindex, gboolean *out_no_firmware);
 gboolean nm_platform_link_set_down (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_set_arp (NMPlatform *self, int ifindex);
@@ -1132,6 +1196,7 @@ const NMPlatformLnkMacsec *nm_platform_link_get_lnk_macsec (NMPlatform *self, in
 const NMPlatformLnkMacvlan *nm_platform_link_get_lnk_macvlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkMacvtap *nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkSit *nm_platform_link_get_lnk_sit (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkTun *nm_platform_link_get_lnk_tun (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkVlan *nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkVxlan *nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 
@@ -1159,16 +1224,6 @@ NMPlatformError nm_platform_link_vxlan_add (NMPlatform *self,
                                             const NMPlatformLnkVxlan *props,
                                             const NMPlatformLink **out_link);
 
-NMPlatformError nm_platform_link_tun_add (NMPlatform *self,
-                                          const char *name,
-                                          gboolean tap,
-                                          gint64 owner,
-                                          gint64 group,
-                                          gboolean pi,
-                                          gboolean vnet_hdr,
-                                          gboolean multi_queue,
-                                          const NMPlatformLink **out_link);
-
 NMPlatformError nm_platform_link_infiniband_add (NMPlatform *self,
                                                  int parent,
                                                  int p_key,
@@ -1179,7 +1234,9 @@ NMPlatformError nm_platform_link_infiniband_delete (NMPlatform *self,
 gboolean nm_platform_link_infiniband_get_properties (NMPlatform *self, int ifindex, int *parent, int *p_key, const char **mode);
 
 gboolean nm_platform_link_veth_get_properties   (NMPlatform *self, int ifindex, int *out_peer_ifindex);
-gboolean nm_platform_link_tun_get_properties    (NMPlatform *self, int ifindex, NMPlatformTunProperties *properties);
+gboolean nm_platform_link_tun_get_properties    (NMPlatform *self,
+                                                 int ifindex,
+                                                 NMPlatformLnkTun *out_properties);
 
 gboolean    nm_platform_wifi_get_capabilities (NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps);
 gboolean    nm_platform_wifi_get_bssid        (NMPlatform *self, int ifindex, guint8 *bssid);
@@ -1227,6 +1284,11 @@ NMPlatformError nm_platform_link_sit_add (NMPlatform *self,
                                           const char *name,
                                           const NMPlatformLnkSit *props,
                                           const NMPlatformLink **out_link);
+NMPlatformError nm_platform_link_tun_add (NMPlatform *self,
+                                          const char *name,
+                                          const NMPlatformLnkTun *props,
+                                          const NMPlatformLink **out_link,
+                                          int *out_fd);
 
 const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address);
 
@@ -1251,8 +1313,8 @@ gboolean nm_platform_ip6_address_add (NMPlatform *self,
                                       guint32 flags);
 gboolean nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
 gboolean nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen);
-gboolean nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresse);
-gboolean nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GPtrArray *known_addresses, gboolean keep_link_local);
+gboolean nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresses);
+gboolean nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresses, gboolean full_sync);
 gboolean nm_platform_ip_address_flush (NMPlatform *self,
                                        int addr_family,
                                        int ifindex);
@@ -1310,6 +1372,7 @@ const char *nm_platform_lnk_ipip_to_string (const NMPlatformLnkIpIp *lnk, char *
 const char *nm_platform_lnk_macsec_to_string (const NMPlatformLnkMacsec *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_macvlan_to_string (const NMPlatformLnkMacvlan *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_tun_to_string (const NMPlatformLnkTun *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len);
 const char *nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *buf, gsize len);
@@ -1333,6 +1396,7 @@ int nm_platform_lnk_ipip_cmp (const NMPlatformLnkIpIp *a, const NMPlatformLnkIpI
 int nm_platform_lnk_macsec_cmp (const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b);
 int nm_platform_lnk_macvlan_cmp (const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b);
 int nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b);
+int nm_platform_lnk_tun_cmp (const NMPlatformLnkTun *a, const NMPlatformLnkTun *b);
 int nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b);
 int nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b);
 int nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b);
@@ -1368,6 +1432,7 @@ void nm_platform_lnk_ipip_hash_update (const NMPlatformLnkIpIp *obj, NMHashState
 void nm_platform_lnk_macsec_hash_update (const NMPlatformLnkMacsec *obj, NMHashState *h);
 void nm_platform_lnk_macvlan_hash_update (const NMPlatformLnkMacvlan *obj, NMHashState *h);
 void nm_platform_lnk_sit_hash_update (const NMPlatformLnkSit *obj, NMHashState *h);
+void nm_platform_lnk_tun_hash_update (const NMPlatformLnkTun *obj, NMHashState *h);
 void nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h);
 void nm_platform_lnk_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h);
 
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
index d8561aef..f1092fe9 100644
--- a/src/platform/nmp-netns.c
+++ b/src/platform/nmp-netns.c
@@ -200,8 +200,8 @@ _stack_current_ns_types (NMPNetns *netns, int ns_types)
 		}
 
 		for (i = 0; i < G_N_ELEMENTS (ns_types_check); i++) {
-			if (   NM_FLAGS_HAS (ns_types, ns_types_check[i])
-			    && NM_FLAGS_HAS (info->ns_types, ns_types_check[i])) {
+			if (   NM_FLAGS_ANY (ns_types, ns_types_check[i])
+			    && NM_FLAGS_ANY (info->ns_types, ns_types_check[i])) {
 				res = NM_FLAGS_SET (res, ns_types_check[i]);
 				ns_types = NM_FLAGS_UNSET (ns_types, ns_types_check[i]);
 			}
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 1a9b9325..29bb999b 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -325,7 +325,7 @@ _vlan_xgress_qos_mappings_cmp (guint n_map,
 
 static void
 _vlan_xgress_qos_mappings_cpy (guint *dst_n_map,
-                               const NMVlanQosMapping **dst_map,
+                               NMVlanQosMapping **dst_map,
                                guint src_n_map,
                                const NMVlanQosMapping *src_map)
 {
@@ -388,7 +388,7 @@ _nmp_object_fixup_link_udev_fields (NMPObject **obj_new, NMPObject *obj_orig, gb
 	/* The link contains internal fields that are combined by
 	 * properties from netlink and udev. Update those properties */
 
-	/* When a link is not in netlink, it's udev fields don't matter. */
+	/* When a link is not in netlink, its udev fields don't matter. */
 	if (obj->_link.netlink.is_in_netlink) {
 		driver = _link_get_driver (obj->_link.udev.device,
 		                           obj->link.kind,
@@ -532,7 +532,7 @@ _nmp_object_stackinit_from_type (NMPObject *obj, NMPObjectType obj_type)
 }
 
 const NMPObject *
-nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, const NMPlatformObject *plobj)
+nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj)
 {
 	const NMPClass *klass = nmp_class_from_type (obj_type);
 
@@ -865,12 +865,6 @@ _vt_cmd_obj_cmp_lnk_vlan (const NMPObject *obj1, const NMPObject *obj2)
 	return c;
 }
 
-gboolean
-nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2)
-{
-	return nmp_object_cmp (obj1, obj2) == 0;
-}
-
 /* @src is a const object, which is not entirely correct for link types, where
  * we increase the ref count for src->_link.udev.device.
  * Hence, nmp_object_copy() can violate the const promise of @src.
@@ -922,11 +916,11 @@ _vt_cmd_obj_copy_lnk_vlan (NMPObject *dst, const NMPObject *src)
 {
 	dst->lnk_vlan = src->lnk_vlan;
 	_vlan_xgress_qos_mappings_cpy (&dst->_lnk_vlan.n_ingress_qos_map,
-	                               &dst->_lnk_vlan.ingress_qos_map,
+	                               NM_UNCONST_PPTR (NMVlanQosMapping, &dst->_lnk_vlan.ingress_qos_map),
 	                               src->_lnk_vlan.n_ingress_qos_map,
 	                               src->_lnk_vlan.ingress_qos_map);
 	_vlan_xgress_qos_mappings_cpy (&dst->_lnk_vlan.n_egress_qos_map,
-	                               &dst->_lnk_vlan.egress_qos_map,
+	                               NM_UNCONST_PPTR (NMVlanQosMapping, &dst->_lnk_vlan.egress_qos_map),
 	                               src->_lnk_vlan.n_egress_qos_map,
 	                               src->_lnk_vlan.egress_qos_map);
 }
@@ -1088,7 +1082,7 @@ nmp_object_id_hash (const NMPObject *obj)
 	NMHashState h;
 
 	if (!obj)
-		return 0;
+		return nm_hash_static (914932607u);
 
 	nm_hash_init (&h, 914932607u);
 	nmp_object_id_hash_update (obj, &h);
@@ -1532,8 +1526,6 @@ const NMPLookup *
 nmp_lookup_init_obj_type (NMPLookup *lookup,
                           NMPObjectType obj_type)
 {
-	NMPObject *o;
-
 	nm_assert (lookup);
 
 	switch (obj_type) {
@@ -1544,7 +1536,7 @@ nmp_lookup_init_obj_type (NMPLookup *lookup,
 	case NMP_OBJECT_TYPE_IP6_ROUTE:
 	case NMP_OBJECT_TYPE_QDISC:
 	case NMP_OBJECT_TYPE_TFILTER:
-		o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
+		_nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
 		lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_TYPE;
 		return _L (lookup);
 	default:
@@ -1776,6 +1768,54 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 
 /*****************************************************************************/
 
+static NMDedupMultiIdxMode
+_obj_get_add_mode (const NMPObject *obj)
+{
+	/* new objects are usually appended to the list. Except for
+	 * addresses, which are prepended during `ip address add`.
+	 *
+	 * Actually, for routes it is more complicated, because depending on
+	 * `ip route append`, `ip route replace`, `ip route prepend`, the object
+	 * will be added at the tail, at the front, or even replace an element
+	 * in the list. However, that is handled separately by nmp_cache_update_netlink_route()
+	 * and of no concern here. */
+	if (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj),
+	               NMP_OBJECT_TYPE_IP4_ADDRESS,
+	               NMP_OBJECT_TYPE_IP6_ADDRESS))
+		return NM_DEDUP_MULTI_IDX_MODE_PREPEND;
+	return NM_DEDUP_MULTI_IDX_MODE_APPEND;
+}
+
+static void
+_idxcache_update_order_for_dump (NMPCache *cache,
+                                 const NMDedupMultiEntry *entry)
+{
+	const NMPClass *klass;
+	const guint8 *i_idx_type;
+	const NMDedupMultiEntry *entry2;
+
+	nm_dedup_multi_entry_reorder (entry, NULL, TRUE);
+
+	klass = NMP_OBJECT_GET_CLASS (entry->obj);
+	for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) {
+		NMPCacheIdType id_type = *i_idx_type;
+
+		if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE)
+			continue;
+
+		entry2 = nm_dedup_multi_index_lookup_obj (cache->multi_idx,
+		                                          _idx_type_get (cache, id_type),
+		                                          entry->obj);
+		if (!entry2)
+			continue;
+
+		nm_assert (entry2 != entry);
+		nm_assert (entry2->obj == entry->obj);
+
+		nm_dedup_multi_entry_reorder (entry2, NULL, TRUE);
+	}
+}
+
 static void
 _idxcache_update_other_cache_ids (NMPCache *cache,
                                   NMPCacheIdType cache_id_type,
@@ -1835,7 +1875,7 @@ _idxcache_update_other_cache_ids (NMPCache *cache,
 		                               obj_new,
 		                               is_dump
 		                                 ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE
-		                                 : NM_DEDUP_MULTI_IDX_MODE_APPEND,
+		                                 : _obj_get_add_mode (obj_new),
 		                               is_dump
 		                                 ? NULL
 		                                 : entry_order,
@@ -1913,7 +1953,7 @@ _idxcache_update (NMPCache *cache,
 		                               obj_new,
 		                               is_dump
 		                                 ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE
-		                                 : NM_DEDUP_MULTI_IDX_MODE_APPEND,
+		                                 : _obj_get_add_mode (obj_new),
 		                               NULL,
 		                               entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING,
 		                               NULL,
@@ -2170,6 +2210,8 @@ nmp_cache_update_netlink (NMPCache *cache,
 	}
 
 	if (nmp_object_equal (obj_old, obj_hand_over)) {
+		if (is_dump)
+			_idxcache_update_order_for_dump (cache, entry_old);
 		nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
 		NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old));
 		return NMP_CACHE_OPS_UNCHANGED;
@@ -2243,6 +2285,8 @@ nmp_cache_update_netlink_route (NMPCache *cache,
 	}
 
 	if (nmp_object_equal (entry_old->obj, obj_hand_over)) {
+		if (is_dump)
+			_idxcache_update_order_for_dump (cache, entry_old);
 		nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
 		goto update_done;
 	}
@@ -2266,9 +2310,8 @@ update_done:
 	 * properly find @obj_replaced. */
 	resync_required = FALSE;
 	entry_replace = NULL;
-	if (is_dump) {
+	if (is_dump)
 		goto out;
-	}
 
 	if (!entry_new) {
 		if (   NM_FLAGS_HAS (nlmsgflags, NLM_F_REPLACE)
@@ -2283,6 +2326,8 @@ update_done:
 		goto out;
 	}
 
+	/* FIXME: for routes, we only maintain the order correctly for the BY_WEAK_ID
+	 * index. For all other indexes their order becomes messed up. */
 	entry_cur = _lookup_entry_with_idx_type (cache,
 	                                         NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
 	                                         entry_new->obj);
@@ -2735,6 +2780,17 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_sit_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_sit_cmp,
 	},
+	[NMP_OBJECT_TYPE_LNK_TUN - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_TUN,
+		.sizeof_data                        = sizeof (NMPObjectLnkTun),
+		.sizeof_public                      = sizeof (NMPlatformLnkTun),
+		.obj_type_name                      = "tun",
+		.lnk_link_type                      = NM_LINK_TYPE_TUN,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_tun_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_tun_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_tun_cmp,
+	},
 	[NMP_OBJECT_TYPE_LNK_VLAN - 1] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_VLAN,
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index e3862f2d..f473f462 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -88,7 +88,7 @@ typedef enum { /*< skip >*/
 	/* Consider all the destination fields of a route, that is, the ID without the ifindex
 	 * and gateway (meaning: network/plen,metric).
 	 * The reason for this is that `ip route change` can replace an existing route
-	 * and modify it's ifindex/gateway. Effectively, that means it deletes an existing
+	 * and modify its ifindex/gateway. Effectively, that means it deletes an existing
 	 * route and adds a different one (as the ID of the route changes). However, it only
 	 * sends one RTM_NEWADDR notification without notifying about the deletion. We detect
 	 * that by having this index to contain overlapping routes which require special
@@ -197,6 +197,10 @@ typedef struct {
 } NMPObjectLnkSit;
 
 typedef struct {
+	NMPlatformLnkTun _public;
+} NMPObjectLnkTun;
+
+typedef struct {
 	NMPlatformLnkVlan _public;
 
 	guint n_ingress_qos_map;
@@ -265,6 +269,9 @@ struct _NMPObject {
 		NMPlatformLnkSit        lnk_sit;
 		NMPObjectLnkSit         _lnk_sit;
 
+		NMPlatformLnkTun        lnk_tun;
+		NMPObjectLnkTun         _lnk_tun;
+
 		NMPlatformLnkVlan       lnk_vlan;
 		NMPObjectLnkVlan        _lnk_vlan;
 
@@ -457,11 +464,12 @@ nmp_object_ref (const NMPObject *obj)
 	return (const NMPObject *) nm_dedup_multi_obj_ref ((const NMDedupMultiObj *) obj);
 }
 
-static inline const NMPObject *
+static inline void
 nmp_object_unref (const NMPObject *obj)
 {
+	nm_assert (!obj || NMP_OBJECT_IS_VALID (obj));
+
 	nm_dedup_multi_obj_unref ((const NMDedupMultiObj *) obj);
-	return NULL;
 }
 
 #define nm_clear_nmp_object(ptr) \
@@ -482,7 +490,7 @@ nmp_object_unref (const NMPObject *obj)
 NMPObject *nmp_object_new (NMPObjectType obj_type, const NMPlatformObject *plob);
 NMPObject *nmp_object_new_link (int ifindex);
 
-const NMPObject *nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, const NMPlatformObject *plobj);
+const NMPObject *nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj);
 
 static inline NMPObject *
 nmp_object_stackinit_obj (NMPObject *obj, const NMPObject *src)
@@ -500,7 +508,13 @@ const NMPObject *nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifinde
 const char *nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size);
 void nmp_object_hash_update (const NMPObject *obj, NMHashState *h);
 int nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2);
-gboolean nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2);
+
+static inline gboolean
+nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2)
+{
+	return nmp_object_cmp (obj1, obj2) == 0;
+}
+
 void nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only);
 NMPObject *nmp_object_clone (const NMPObject *obj, gboolean id_only);
 
@@ -725,6 +739,16 @@ const NMDedupMultiEntry *nm_platform_lookup_entry (NMPlatform *platform,
                                                    NMPCacheIdType cache_id_type,
                                                    const NMPObject *obj);
 
+static inline const NMPObject *
+nm_platform_lookup_obj (NMPlatform *platform,
+                        NMPCacheIdType cache_id_type,
+                        const NMPObject *obj)
+{
+	return nm_dedup_multi_entry_get_obj (nm_platform_lookup_entry (platform,
+	                                                               cache_id_type,
+	                                                               obj));
+}
+
 static inline const NMDedupMultiHeadEntry *
 nm_platform_lookup_obj_type (NMPlatform *platform,
                              NMPObjectType obj_type)
diff --git a/src/platform/tests/meson.build b/src/platform/tests/meson.build
new file mode 100644
index 00000000..0571efac
--- /dev/null
+++ b/src/platform/tests/meson.build
@@ -0,0 +1,37 @@
+test_units = [
+  ['test-link-fake', 'test-link.c', 60],
+  ['test-link-linux', 'test-link.c', 60],
+  ['test-address-fake', 'test-address.c'],
+  ['test-address-linux', 'test-address.c'],
+  ['test-general', 'test-general.c'],
+  ['test-nmp-object', 'test-nmp-object.c'],
+  ['test-route-fake', 'test-route.c'],
+  ['test-route-linux', 'test-route.c'],
+  ['test-cleanup-fake', 'test-cleanup.c'],
+  ['test-cleanup-linux', 'test-cleanup.c'],
+]
+
+foreach test_unit: test_units
+  exe = executable(
+    'platform-' + test_unit[0],
+    test_unit[1],
+    dependencies: test_nm_dep,
+    c_args: test_cflags_platform
+  )
+
+  test(
+    'platform/' + test_unit[0],
+    test_script,
+    timeout: test_unit.length() > 2 ? test_unit[2] : 30,
+    args: test_args + [exe.full_path()]
+  )
+endforeach
+
+test = 'monitor'
+
+executable(
+  test,
+  test + '.c',
+  dependencies: test_nm_dep,
+  c_args: test_cflags_platform
+)
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index e1220052..f0e3e6cf 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -1,6 +1,5 @@
 /* -*- 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
  * 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/platform/tests/test-address.c b/src/platform/tests/test-address.c
index 93851ff7..ddef8853 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -1,6 +1,5 @@
 /* -*- 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
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -149,7 +148,7 @@ test_ip6_address_general (void)
 
 	/* Add address again (aka update) */
 	nmtstp_ip6_address_add (NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
-	accept_signals (address_changed, 0, 1);
+	accept_signals (address_changed, 0, 2);
 
 	/* Test address listing */
 	addresses = nmtstp_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 937cd12c..a213b31f 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -1,6 +1,5 @@
 /* -*- 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
  * 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/platform/tests/test-common.c b/src/platform/tests/test-common.c
index d56e681e..7885c083 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -1,6 +1,5 @@
 /* -*- 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
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -24,6 +23,7 @@
 #include <sched.h>
 #include <sys/wait.h>
 #include <fcntl.h>
+#include <linux/if_tun.h>
 
 #include "test-common.h"
 
@@ -628,7 +628,10 @@ nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms)
 const NMPlatformLink *
 nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_ms)
 {
-	return nmtstp_wait_for_link_until (platform, ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
+	return nmtstp_wait_for_link_until (platform, ifname, expected_link_type,
+	                                   timeout_ms
+	                                     ? nm_utils_get_monotonic_timestamp_ms () + timeout_ms
+	                                     : 0);
 }
 
 const NMPlatformLink *
@@ -636,6 +639,7 @@ nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType
 {
 	const NMPlatformLink *plink;
 	gint64 now;
+	gboolean waited_once = FALSE;
 
 	_init_platform (&platform, FALSE);
 
@@ -647,29 +651,24 @@ nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType
 		    && (expected_link_type == NM_LINK_TYPE_NONE || plink->type == expected_link_type))
 			return plink;
 
-		if (until_ms < now)
+		if (until_ms == 0) {
+			/* don't wait, don't even poll the socket. */
+			return NULL;
+		}
+
+		if (   waited_once
+		    && until_ms < now) {
+			/* timeout reached (+ we already waited for a signal at least once). */
 			return NULL;
+		}
 
+		waited_once = TRUE;
+		/* regardless of whether timeout is already reached, we poll the netlink
+		 * socket a bit. */
 		nmtstp_wait_for_signal (platform, until_ms - now);
 	}
 }
 
-const NMPlatformLink *
-nmtstp_assert_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
-{
-	return nmtstp_assert_wait_for_link_until (platform, ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
-}
-
-const NMPlatformLink *
-nmtstp_assert_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms)
-{
-	const NMPlatformLink *plink;
-
-	plink = nmtstp_wait_for_link_until (platform, ifname, expected_link_type, until_ms);
-	g_assert (plink);
-	return plink;
-}
-
 /*****************************************************************************/
 
 int
@@ -1284,6 +1283,10 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
 	char buffer[INET6_ADDRSTRLEN];
+	char encap[20];
+	char tclass[20];
+	gboolean encap_ignore;
+	gboolean tclass_inherit;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 
@@ -1309,15 +1312,18 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 			g_assert_not_reached ();
 		}
 
-		success = !nmtstp_run_command ("ip -6 tunnel add %s mode %s %s local %s remote %s ttl %u tclass %02x encaplimit %u flowlabel %x",
+		encap_ignore = NM_FLAGS_HAS (lnk->flags, IP6_TNL_F_IGN_ENCAP_LIMIT);
+		tclass_inherit = NM_FLAGS_HAS (lnk->flags, IP6_TNL_F_USE_ORIG_TCLASS);
+
+		success = !nmtstp_run_command ("ip -6 tunnel add %s mode %s %s local %s remote %s ttl %u tclass %s encaplimit %s flowlabel %x",
 		                                name,
 		                                mode,
 		                                dev,
 		                                nm_utils_inet6_ntop (&lnk->local, NULL),
 		                                nm_utils_inet6_ntop (&lnk->remote, buffer),
 		                                lnk->ttl,
-		                                lnk->tclass,
-		                                lnk->encap_limit,
+		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
+		                                encap_ignore ? "none" : nm_sprintf_buf (encap, "%u", lnk->encap_limit),
 		                                lnk->flow_label);
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_IP6TNL, 100);
@@ -1463,6 +1469,70 @@ nmtstp_link_sit_add (NMPlatform *platform,
 }
 
 const NMPlatformLink *
+nmtstp_link_tun_add (NMPlatform *platform,
+                     gboolean external_command,
+                     const char *name,
+                     const NMPlatformLnkTun *lnk,
+                     int *out_fd)
+{
+	const NMPlatformLink *pllink = NULL;
+	NMPlatformError plerr;
+	int err;
+
+	g_assert (nm_utils_is_valid_iface_name (name, NULL));
+	g_assert (lnk);
+	g_assert (NM_IN_SET (lnk->type, IFF_TUN, IFF_TAP));
+	g_assert (!out_fd || *out_fd == -1);
+
+	if (!lnk->persist) {
+		/* ip tuntap does not support non-persistent devices.
+		 *
+		 * Add this device only via NMPlatform. */
+		if (external_command == -1)
+			external_command = FALSE;
+	}
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	_init_platform (&platform, external_command);
+
+	if (external_command) {
+		g_assert (lnk->persist);
+
+		err = nmtstp_run_command ("ip tuntap add"
+		                          " mode %s"
+		                          "%s" /* user */
+		                          "%s" /* group */
+		                          "%s" /* pi */
+		                          "%s" /* vnet_hdr */
+		                          "%s" /* multi_queue */
+		                          " name %s",
+		                          lnk->type == IFF_TUN ? "tun" : "tap",
+		                          lnk->owner_valid ? nm_sprintf_bufa (100, " user %u", (guint) lnk->owner) : "",
+		                          lnk->group_valid ? nm_sprintf_bufa (100, " group %u", (guint) lnk->group) : "",
+		                          lnk->pi ? " pi" : "",
+		                          lnk->vnet_hdr ? " vnet_hdr" : "",
+		                          lnk->multi_queue ? " multi_queue" : "",
+		                          name);
+		/* Older versions of iproute2 don't support adding  devices.
+		 * On failure, fallback to using platform code. */
+		if (err == 0)
+			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_TUN, 100);
+		else
+			g_error ("failure to add tun/tap device via ip-route");
+	} else {
+		g_assert (lnk->persist || out_fd);
+		plerr = nm_platform_link_tun_add (platform, name, lnk, &pllink, out_fd);
+		g_assert_cmpint (plerr, ==, NM_PLATFORM_ERROR_SUCCESS);
+	}
+
+	g_assert (pllink);
+	g_assert_cmpint (pllink->type, ==, NM_LINK_TYPE_TUN);
+	g_assert_cmpstr (pllink->name, ==, name);
+	return pllink;
+}
+
+const NMPlatformLink *
 nmtstp_link_vxlan_add (NMPlatform *platform,
                        gboolean external_command,
                        const char *name,
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index fb406a3f..bd02b0d7 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -1,3 +1,21 @@
+/*
+ * 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 2016 - 2017 Red Hat, Inc.
+ */
+
 #include <stdlib.h>
 #include <unistd.h>
 #include <syslog.h>
@@ -111,8 +129,11 @@ const NMPlatformLink *nmtstp_wait_for_link_until (NMPlatform *platform, const ch
 			g_assert_not_reached (); \
 	} G_STMT_END
 
-const NMPlatformLink *nmtstp_assert_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
-const NMPlatformLink *nmtstp_assert_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
+#define nmtstp_assert_wait_for_link(platform, ifname, expected_link_type, timeout_ms) \
+	nmtst_assert_nonnull (nmtstp_wait_for_link (platform, ifname, expected_link_type, timeout_ms))
+
+#define nmtstp_assert_wait_for_link_until(platform, ifname, expected_link_type, until_ms) \
+	nmtst_assert_nonnull (nmtstp_wait_for_link_until (platform, ifname, expected_link_type, until_ms))
 
 /*****************************************************************************/
 
@@ -289,6 +310,11 @@ const NMPlatformLink *nmtstp_link_sit_add (NMPlatform *platform,
                                            gboolean external_command,
                                            const char *name,
                                            const NMPlatformLnkSit *lnk);
+const NMPlatformLink *nmtstp_link_tun_add (NMPlatform *platform,
+                                           gboolean external_command,
+                                           const char *name,
+                                           const NMPlatformLnkTun *lnk,
+                                           int *out_fd);
 const NMPlatformLink *nmtstp_link_vxlan_add (NMPlatform *platform,
                                              gboolean external_command,
                                              const char *name,
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index ef78cc24..dcd600ee 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -1,6 +1,5 @@
 /* -*- 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
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -24,6 +23,7 @@
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <linux/if_tun.h>
 
 #include "platform/nmp-object.h"
 #include "platform/nmp-netns.h"
@@ -698,6 +698,8 @@ test_software_detect (gconstpointer user_data)
 	const NMPObject *lnk;
 	guint i_step;
 	const gboolean ext = test_data->external_command;
+	NMPlatformLnkTun lnk_tun;
+	nm_auto_close int tun_fd = -1;
 
 	nmtstp_run_command_check ("ip link add %s type dummy", PARENT_NAME);
 	ifindex_parent = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex;
@@ -761,13 +763,27 @@ test_software_detect (gconstpointer user_data)
 			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip6_tunnel", NULL) != 0;
 		}
 
-		lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::15");
-		lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::16");
-		lnk_ip6tnl.parent_ifindex = ifindex_parent;
-		lnk_ip6tnl.tclass = 20;
-		lnk_ip6tnl.encap_limit = 6;
-		lnk_ip6tnl.flow_label = 1337;
-		lnk_ip6tnl.proto = IPPROTO_IPV6;
+		switch (test_data->test_mode) {
+		case 0:
+			lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::15");
+			lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::16");
+			lnk_ip6tnl.parent_ifindex = ifindex_parent;
+			lnk_ip6tnl.tclass = 20;
+			lnk_ip6tnl.encap_limit = 6;
+			lnk_ip6tnl.flow_label = 1337;
+			lnk_ip6tnl.proto = IPPROTO_IPV6;
+			break;
+		case 1:
+			lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::17");
+			lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::18");
+			lnk_ip6tnl.parent_ifindex = ifindex_parent;
+			lnk_ip6tnl.tclass = 0;
+			lnk_ip6tnl.encap_limit = 0;
+			lnk_ip6tnl.flow_label = 1338;
+			lnk_ip6tnl.proto = IPPROTO_IPV6;
+			lnk_ip6tnl.flags = IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS;
+			break;
+		}
 
 		if (!nmtstp_link_ip6tnl_add (NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) {
 			if (gracefully_skip) {
@@ -879,6 +895,38 @@ test_software_detect (gconstpointer user_data)
 		g_assert (nmtstp_link_vxlan_add (NULL, ext, DEVICE_NAME, &lnk_vxlan));
 		break;
 	}
+	case NM_LINK_TYPE_TUN: {
+		gboolean owner_valid = nmtst_get_rand_bool ();
+		gboolean group_valid = nmtst_get_rand_bool ();
+
+		switch (test_data->test_mode) {
+		case 0:
+			lnk_tun = (NMPlatformLnkTun) {
+				.type = nmtst_get_rand_bool () ? IFF_TUN : IFF_TAP,
+				.owner = owner_valid ? getuid () : 0,
+				.owner_valid = owner_valid,
+				.group = group_valid ? getgid () : 0,
+				.group_valid = group_valid,
+				.pi = nmtst_get_rand_bool (),
+				.vnet_hdr = nmtst_get_rand_bool (),
+				.multi_queue = nmtst_get_rand_bool (),
+
+				/* if we add the device via iproute2 (external), we can only
+				 * create persistent devices. */
+				.persist = (ext == 1) ? TRUE : nmtst_get_rand_bool (),
+			};
+			break;
+		default:
+			g_assert_not_reached ();
+			break;
+		}
+
+		g_assert (nmtstp_link_tun_add (NULL, ext, DEVICE_NAME, &lnk_tun,
+		                               (!lnk_tun.persist || nmtst_get_rand_bool ())
+		                                 ? &tun_fd
+		                                 : NULL));
+		break;
+	}
 	default:
 		g_assert_not_reached ();
 	}
@@ -909,7 +957,13 @@ test_software_detect (gconstpointer user_data)
 		lnk = nm_platform_link_get_lnk (NM_PLATFORM_GET, ifindex, test_data->link_type, &plink);
 		g_assert (plink);
 		g_assert_cmpint (plink->ifindex, ==, ifindex);
-		g_assert (lnk);
+
+		if (   !lnk
+		    && test_data->link_type == NM_LINK_TYPE_TUN) {
+			/* this is ok. Kernel apparently does not support tun properties via netlink. We
+			 * fetch them from sysfs below. */
+		} else
+			g_assert (lnk);
 
 		switch (test_data->link_type) {
 		case NM_LINK_TYPE_GRE: {
@@ -931,15 +985,31 @@ test_software_detect (gconstpointer user_data)
 		case NM_LINK_TYPE_IP6TNL: {
 			const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl;
 
-			g_assert (plnk == nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL));
-			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
-			nmtst_assert_ip6_address (&plnk->local, "fd01::15");
-			nmtst_assert_ip6_address (&plnk->remote, "fd01::16");
-			g_assert_cmpint (plnk->ttl, ==, 0);
-			g_assert_cmpint (plnk->tclass, ==, 20);
-			g_assert_cmpint (plnk->encap_limit, ==, 6);
-			g_assert_cmpint (plnk->flow_label, ==, 1337);
-			g_assert_cmpint (plnk->proto, ==, IPPROTO_IPV6);
+			switch (test_data->test_mode) {
+			case 0:
+				g_assert (plnk == nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL));
+				g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+				nmtst_assert_ip6_address (&plnk->local, "fd01::15");
+				nmtst_assert_ip6_address (&plnk->remote, "fd01::16");
+				g_assert_cmpint (plnk->ttl, ==, 0);
+				g_assert_cmpint (plnk->tclass, ==, 20);
+				g_assert_cmpint (plnk->encap_limit, ==, 6);
+				g_assert_cmpint (plnk->flow_label, ==, 1337);
+				g_assert_cmpint (plnk->proto, ==, IPPROTO_IPV6);
+				break;
+			case 1:
+				g_assert (plnk == nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, ifindex, NULL));
+				g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+				nmtst_assert_ip6_address (&plnk->local, "fd01::17");
+				nmtst_assert_ip6_address (&plnk->remote, "fd01::18");
+				g_assert_cmpint (plnk->ttl, ==, 0);
+				g_assert_cmpint (plnk->flow_label, ==, 1338);
+				g_assert_cmpint (plnk->proto, ==, IPPROTO_IPV6);
+				g_assert_cmpint (plnk->flags & 0xFFFF, /* ignore kernel internal flags */
+				                 ==,
+				                 IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS);
+				break;
+			}
 			break;
 		}
 		case NM_LINK_TYPE_IPIP: {
@@ -982,6 +1052,27 @@ test_software_detect (gconstpointer user_data)
 			g_assert_cmpint (plnk->path_mtu_discovery, ==, FALSE);
 			break;
 		}
+		case NM_LINK_TYPE_TUN: {
+			const NMPlatformLnkTun *plnk;
+			NMPlatformLnkTun lnk_tun2;
+
+			g_assert ((lnk ? &lnk->lnk_tun : NULL) == nm_platform_link_get_lnk_tun (NM_PLATFORM_GET, ifindex, NULL));
+
+			/* kernel might not expose tun options via netlink. Either way, try
+			 * to read them (either from platform cache, or fallback to sysfs).
+			 * See also: rh#1547213. */
+			if (!nm_platform_link_tun_get_properties (NM_PLATFORM_GET,
+			                                          ifindex,
+			                                          &lnk_tun2))
+				g_assert_not_reached ();
+
+			plnk = lnk ? &lnk->lnk_tun : &lnk_tun2;
+			if (lnk)
+				g_assert (memcmp (plnk, &lnk_tun2, sizeof (NMPlatformLnkTun)) == 0);
+
+			g_assert (nm_platform_lnk_tun_cmp (plnk, &lnk_tun) == 0);
+			break;
+		}
 		case NM_LINK_TYPE_VLAN: {
 			const NMPlatformLnkVlan *plnk = &lnk->lnk_vlan;
 
@@ -2248,7 +2339,7 @@ test_netns_push (gpointer fixture, gconstpointer test_data)
 		p = pl_base;
 		for (j = nstack; j >= 1; ) {
 			j--;
-			if (NM_FLAGS_HAS (stack[j].ns_types, ns_type)) {
+			if (NM_FLAGS_ANY (stack[j].ns_types, ns_type)) {
 				p = stack[j].pl;
 				break;
 			}
@@ -2546,11 +2637,13 @@ _nmtstp_setup_tests (void)
 		g_test_add_func ("/link/external", test_external);
 
 		test_software_detect_add ("/link/software/detect/gre", NM_LINK_TYPE_GRE, 0);
-		test_software_detect_add ("/link/software/detect/ip6tnl", NM_LINK_TYPE_IP6TNL, 0);
+		test_software_detect_add ("/link/software/detect/ip6tnl/0", NM_LINK_TYPE_IP6TNL, 0);
+		test_software_detect_add ("/link/software/detect/ip6tnl/1", NM_LINK_TYPE_IP6TNL, 1);
 		test_software_detect_add ("/link/software/detect/ipip", NM_LINK_TYPE_IPIP, 0);
 		test_software_detect_add ("/link/software/detect/macvlan", NM_LINK_TYPE_MACVLAN, 0);
 		test_software_detect_add ("/link/software/detect/macvtap", NM_LINK_TYPE_MACVTAP, 0);
 		test_software_detect_add ("/link/software/detect/sit", NM_LINK_TYPE_SIT, 0);
+		test_software_detect_add ("/link/software/detect/tun", NM_LINK_TYPE_TUN, 0);
 		test_software_detect_add ("/link/software/detect/vlan", NM_LINK_TYPE_VLAN, 0);
 		test_software_detect_add ("/link/software/detect/vxlan/0", NM_LINK_TYPE_VXLAN, 0);
 		test_software_detect_add ("/link/software/detect/vxlan/1", NM_LINK_TYPE_VXLAN, 1);
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index 3228de83..047d7a85 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -54,27 +54,20 @@ test_obj_base (void)
 	gs_unref_object GCancellable *obj_cancellable = g_cancellable_new ();
 	nm_auto_nmpobj NMPObject *obj_link = nmp_object_new_link (10);
 
-#define STATIC_ASSERT(cond) \
-	G_STMT_START { \
-		G_STATIC_ASSERT (cond); \
-		G_STATIC_ASSERT_EXPR (cond); \
-		g_assert (cond); \
-	} G_STMT_END
+	g_assert (&g->g_type_instance              == (void *) &o->_class);
+	g_assert (&g->g_type_instance.g_class      == (void *) &o->_class);
 
-	STATIC_ASSERT (&g->g_type_instance              == (void *) &o->_class);
-	STATIC_ASSERT (&g->g_type_instance.g_class      == (void *) &o->_class);
+	g_assert (sizeof (o->parent.parent)        == sizeof (GTypeInstance));
 
-	STATIC_ASSERT (sizeof (o->parent.parent)        == sizeof (GTypeInstance));
+	g_assert (&c->parent                       == (void *) c);
+	g_assert (&c->parent.parent.g_type_class   == (void *) c);
+	g_assert (&c->parent.parent.g_type         == (void *) c);
+	g_assert (&c->parent.parent.g_type         == &k->g_type);
 
-	STATIC_ASSERT (&c->parent                       == (void *) c);
-	STATIC_ASSERT (&c->parent.parent.g_type_class   == (void *) c);
-	STATIC_ASSERT (&c->parent.parent.g_type         == (void *) c);
-	STATIC_ASSERT (&c->parent.parent.g_type         == &k->g_type);
+	g_assert (sizeof (c->parent.parent)        == sizeof (GTypeClass));
 
-	STATIC_ASSERT (sizeof (c->parent.parent)        == sizeof (GTypeClass));
-
-	STATIC_ASSERT (&o->parent                       == (void *) o);
-	STATIC_ASSERT (&o->parent.klass                 == (void *) &o->_class);
+	g_assert (&o->parent                       == (void *) o);
+	g_assert (&o->parent.klass                 == (void *) &o->_class);
 
 	obj = (NMObjBaseInst *) obj_cancellable;
 	g_assert (!NMP_CLASS_IS_VALID ((NMPClass *) obj->klass));
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 13648f16..85b14b57 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -1,6 +1,5 @@
 /* -*- 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
  * 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/platform/wifi/wifi-utils-nl80211.c b/src/platform/wifi/wifi-utils-nl80211.c
index a5f25b02..db187a1f 100644
--- a/src/platform/wifi/wifi-utils-nl80211.c
+++ b/src/platform/wifi/wifi-utils-nl80211.c
@@ -22,17 +22,17 @@
 
 #include "nm-default.h"
 
+#include "wifi-utils-nl80211.h"
+
 #include <errno.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <net/ethernet.h>
 #include <unistd.h>
-#include <netlink/netlink.h>
-#include <netlink/msg.h>
 #include <linux/nl80211.h>
 
+#include "platform/nm-netlink.h"
 #include "wifi-utils-private.h"
-#include "wifi-utils-nl80211.h"
 #include "platform/nm-platform.h"
 #include "platform/nm-platform-utils.h"
 #include "nm-utils.h"
@@ -46,223 +46,14 @@
 		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
 	} G_STMT_END
 
-/*****************************************************************************/
-
-static int
-_nl_nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
-               const struct nla_policy *policy)
-{
-	return nla_parse (tb, maxtype, head, len, (struct nla_policy *) policy);
-}
-#define nla_parse(...) _nl_nla_parse(__VA_ARGS__)
-
-static int
-_nl_nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla,
-                      const struct nla_policy *policy)
-{
-	return nla_parse_nested (tb, maxtype, nla, (struct nla_policy *) policy);
-}
-#define nla_parse_nested(...) _nl_nla_parse_nested(__VA_ARGS__)
-
-/*****************************************************************************
- * Copied from libnl3/genl:
- *****************************************************************************/
-
-static void *
-genlmsg_put (struct nl_msg *msg, uint32_t port, uint32_t seq, int family,
-             int hdrlen, int flags, uint8_t cmd, uint8_t version)
-{
-	struct nlmsghdr *nlh;
-	struct genlmsghdr hdr = {
-		.cmd = cmd,
-		.version = version,
-	};
-
-	nlh = nlmsg_put (msg, port, seq, family, GENL_HDRLEN + hdrlen, flags);
-	if (nlh == NULL)
-		return NULL;
-
-	memcpy (nlmsg_data (nlh), &hdr, sizeof (hdr));
-
-	return (char *) nlmsg_data (nlh) + GENL_HDRLEN;
-}
-
-static void *
-genlmsg_data (const struct genlmsghdr *gnlh)
-{
-	return ((unsigned char *) gnlh + GENL_HDRLEN);
-}
-
-static void *
-genlmsg_user_hdr (const struct genlmsghdr *gnlh)
-{
-	return genlmsg_data (gnlh);
-}
-
-static struct genlmsghdr *
-genlmsg_hdr (struct nlmsghdr *nlh)
-{
-	return nlmsg_data (nlh);
-}
-
-static void *
-genlmsg_user_data (const struct genlmsghdr *gnlh, const int hdrlen)
-{
-	return (char *) genlmsg_user_hdr (gnlh) + NLMSG_ALIGN (hdrlen);
-}
-
-static struct nlattr *
-genlmsg_attrdata (const struct genlmsghdr *gnlh, int hdrlen)
-{
-	return genlmsg_user_data (gnlh, hdrlen);
-}
-
-static int
-genlmsg_len (const struct genlmsghdr *gnlh)
-{
-	const struct nlmsghdr *nlh;
-
-	nlh = (const struct nlmsghdr *) ((const unsigned char *) gnlh - NLMSG_HDRLEN);
-	return (nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN);
-}
-
-static int
-genlmsg_attrlen (const struct genlmsghdr *gnlh, int hdrlen)
-{
-	return genlmsg_len (gnlh) - NLMSG_ALIGN (hdrlen);
-}
-
-static int
-genlmsg_valid_hdr (struct nlmsghdr *nlh, int hdrlen)
-{
-	struct genlmsghdr *ghdr;
-
-	if (!nlmsg_valid_hdr (nlh, GENL_HDRLEN))
-		return 0;
-
-	ghdr = nlmsg_data (nlh);
-	if (genlmsg_len (ghdr) < NLMSG_ALIGN (hdrlen))
-		return 0;
-
-	return 1;
-}
-
-static int
-genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
-               int maxtype, const struct nla_policy *policy)
-{
-	struct genlmsghdr *ghdr;
-
-	if (!genlmsg_valid_hdr (nlh, hdrlen))
-		return -NLE_MSG_TOOSHORT;
-
-	ghdr = nlmsg_data (nlh);
-	return nla_parse (tb, maxtype, genlmsg_attrdata (ghdr, hdrlen),
-	                  genlmsg_attrlen (ghdr, hdrlen), policy);
-}
-
-/*****************************************************************************
- * Reimplementation of libnl3/genl functions:
- *****************************************************************************/
-
-static int
-probe_response (struct nl_msg *msg, void *arg)
-{
-	static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
-		[CTRL_ATTR_FAMILY_ID]    = { .type = NLA_U16 },
-		[CTRL_ATTR_FAMILY_NAME]  = { .type = NLA_STRING,
-		                            .maxlen = GENL_NAMSIZ },
-		[CTRL_ATTR_VERSION]      = { .type = NLA_U32 },
-		[CTRL_ATTR_HDRSIZE]      = { .type = NLA_U32 },
-		[CTRL_ATTR_MAXATTR]      = { .type = NLA_U32 },
-		[CTRL_ATTR_OPS]          = { .type = NLA_NESTED },
-		[CTRL_ATTR_MCAST_GROUPS] = { .type = NLA_NESTED },
-	};
-	struct nlattr *tb[CTRL_ATTR_MAX+1];
-	struct nlmsghdr *nlh = nlmsg_hdr (msg);
-	gint32 *response_data = arg;
-
-	if (genlmsg_parse (nlh, 0, tb, CTRL_ATTR_MAX, ctrl_policy))
-		return NL_SKIP;
-
-	if (tb[CTRL_ATTR_FAMILY_ID])
-		*response_data = nla_get_u16 (tb[CTRL_ATTR_FAMILY_ID]);
-
-	return NL_STOP;
-}
-
-static int
-genl_ctrl_resolve (struct nl_sock *sk, const char *name)
-{
-	struct nl_msg *msg;
-	struct nl_cb *cb, *orig;
-	int rc;
-	int result = -NLE_OBJ_NOTFOUND;
-	gint32 response_data = -1;
-
-	if (!(orig = nl_socket_get_cb (sk)))
-		goto out;
-
-	cb = nl_cb_clone (orig);
-	nl_cb_put (orig);
-	if (!cb)
-		goto out;
-
-	msg = nlmsg_alloc ();
-	if (!msg)
-		goto out_cb_free;
-
-	if (!genlmsg_put (msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL,
-	                  0, 0, CTRL_CMD_GETFAMILY, 1))
-		goto out_msg_free;
-
-	if (nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name) < 0)
-		goto out_msg_free;
-
-	rc = nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, probe_response, &response_data);
-	if (rc < 0)
-		goto out_msg_free;
-
-	rc = nl_send_auto_complete (sk, msg);
-	if (rc < 0)
-		goto out_msg_free;
-
-	rc = nl_recvmsgs (sk, cb);
-	if (rc < 0)
-		goto out_msg_free;
-
-	/* If search was successful, request may be ACKed after data */
-	rc = nl_wait_for_ack (sk);
-	if (rc < 0)
-		goto out_msg_free;
-
-	if (response_data > 0)
-		result = response_data;
-
-out_msg_free:
-	nlmsg_free (msg);
-out_cb_free:
-	nl_cb_put (cb);
-out:
-	if (result >= 0)
-		_LOGD (LOGD_WIFI, "genl_ctrl_resolve: resolved \"%s\" as 0x%x", name, result);
-	else
-		_LOGE (LOGD_WIFI, "genl_ctrl_resolve: failed resolve \"%s\"", name);
-	return result;
-}
-
-/*****************************************************************************
- * </libn-genl-3>
- *****************************************************************************/
-
 typedef struct {
 	WifiData parent;
 	struct nl_sock *nl_sock;
-	int id;
-	struct nl_cb *nl_cb;
 	guint32 *freqs;
+	int id;
 	int num_freqs;
 	int phy;
+	bool can_wowlan:1;
 } WifiDataNl80211;
 
 static int
@@ -292,19 +83,16 @@ error_handler (struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg)
 static struct nl_msg *
 _nl80211_alloc_msg (int id, int ifindex, int phy, guint32 cmd, guint32 flags)
 {
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
 	msg = nlmsg_alloc ();
-	if (msg) {
-		genlmsg_put (msg, 0, 0, id, 0, flags, cmd, 0);
-		NLA_PUT_U32 (msg, NL80211_ATTR_IFINDEX, ifindex);
-		if (phy != -1)
-			NLA_PUT_U32 (msg, NL80211_ATTR_WIPHY, phy);
-	}
-	return msg;
+	genlmsg_put (msg, 0, 0, id, 0, flags, cmd, 0);
+	NLA_PUT_U32 (msg, NL80211_ATTR_IFINDEX, ifindex);
+	if (phy != -1)
+		NLA_PUT_U32 (msg, NL80211_ATTR_WIPHY, phy);
+	return g_steal_pointer (&msg);
 
- nla_put_failure:
-	nlmsg_free (msg);
+nla_put_failure:
 	return NULL;
 }
 
@@ -314,42 +102,37 @@ nl80211_alloc_msg (WifiDataNl80211 *nl80211, guint32 cmd, guint32 flags)
 	return _nl80211_alloc_msg (nl80211->id, nl80211->parent.ifindex, nl80211->phy, cmd, flags);
 }
 
-/* NOTE: this function consumes 'msg' */
 static int
 _nl80211_send_and_recv (struct nl_sock *nl_sock,
-                        struct nl_cb *nl_cb,
                         struct nl_msg *msg,
                         int (*valid_handler) (struct nl_msg *, void *),
                         void *valid_data)
 {
-	struct nl_cb *cb;
-	int err, done;
+	int err;
+	int done = 0;
+	const struct nl_cb cb = {
+		.err_cb     = error_handler,
+		.err_arg    = &done,
+		.finish_cb  = finish_handler,
+		.finish_arg = &done,
+		.ack_cb     = ack_handler,
+		.ack_arg    = &done,
+		.valid_cb   = valid_handler,
+		.valid_arg  = valid_data,
+	};
 
 	g_return_val_if_fail (msg != NULL, -ENOMEM);
 
-	cb = nl_cb_clone (nl_cb);
-	if (!cb) {
-		err = -ENOMEM;
-		goto out;
-	}
-
-	err = nl_send_auto_complete (nl_sock, msg);
+	err = nl_send_auto (nl_sock, msg);
 	if (err < 0)
-		goto out;
-
-	done = 0;
-	nl_cb_err (cb, NL_CB_CUSTOM, error_handler, &done);
-	nl_cb_set (cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &done);
-	nl_cb_set (cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &done);
-	if (valid_handler)
-		nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, valid_data);
+		return err;
 
 	/* Loop until one of our NL callbacks says we're done; on success
 	 * done will be 1, on error it will be < 0.
 	 */
 	while (!done) {
-		err = nl_recvmsgs (nl_sock, cb);
-		if (err && err != -NLE_AGAIN) {
+		err = nl_recvmsgs (nl_sock, &cb);
+		if (err < 0 && err != -EAGAIN) {
 			/* Kernel scan list can change while we are dumping it, as new scan
 			 * results from H/W can arrive. BSS info is assured to be consistent
 			 * and we don't need consistent view of whole scan list. Hence do
@@ -364,12 +147,9 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock,
 			break;
 		}
 	}
-	if (err == 0 && done < 0)
-		err = done;
 
- out:
-	nl_cb_put (cb);
-	nlmsg_free (msg);
+	if (err >= 0 && done < 0)
+		err = done;
 	return err;
 }
 
@@ -379,7 +159,7 @@ nl80211_send_and_recv (WifiDataNl80211 *nl80211,
                        int (*valid_handler) (struct nl_msg *, void *),
                        void *valid_data)
 {
-	return _nl80211_send_and_recv (nl80211->nl_sock, nl80211->nl_cb, msg,
+	return _nl80211_send_and_recv (nl80211->nl_sock, msg,
 	                               valid_handler, valid_data);
 }
 
@@ -390,8 +170,6 @@ wifi_nl80211_deinit (WifiData *parent)
 
 	if (nl80211->nl_sock)
 		nl_socket_free (nl80211->nl_sock);
-	if (nl80211->nl_cb)
-		nl_cb_put (nl80211->nl_cb);
 	g_free (nl80211->freqs);
 }
 
@@ -435,12 +213,12 @@ wifi_nl80211_get_mode (WifiData *data)
 	struct nl80211_iface_info iface_info = {
 		.mode = NM_802_11_MODE_UNKNOWN,
 	};
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
 	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_INTERFACE, 0);
 
 	if (nl80211_send_and_recv (nl80211, msg, nl80211_iface_info_handler,
-				   &iface_info) < 0)
+	                           &iface_info) < 0)
 		return NM_802_11_MODE_UNKNOWN;
 
 	return iface_info.mode;
@@ -450,7 +228,7 @@ static gboolean
 wifi_nl80211_set_mode (WifiData *data, const NM80211Mode mode)
 {
 	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
 	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_INTERFACE, 0);
@@ -470,7 +248,7 @@ wifi_nl80211_set_mode (WifiData *data, const NM80211Mode mode)
 	}
 
 	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
-	return err ? FALSE : TRUE;
+	return err >= 0;
 
  nla_put_failure:
 	nlmsg_free (msg);
@@ -481,14 +259,14 @@ static gboolean
 wifi_nl80211_set_powersave (WifiData *data, guint32 powersave)
 {
 	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
 	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_POWER_SAVE, 0);
 	NLA_PUT_U32 (msg, NL80211_ATTR_PS_STATE,
 	             powersave == 1 ? NL80211_PS_ENABLED : NL80211_PS_DISABLED);
 	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
-	return err ? FALSE : TRUE;
+	return err >= 0;
 
 nla_put_failure:
 	nlmsg_free (msg);
@@ -518,7 +296,7 @@ struct nl80211_bss_info {
 	gboolean valid;
 };
 
-#define WLAN_EID_SSID	0
+#define WLAN_EID_SSID 0
 
 static void
 find_ssid (guint8 *ies, guint32 ies_len,
@@ -618,7 +396,7 @@ static void
 nl80211_get_bss_info (WifiDataNl80211 *nl80211,
                       struct nl80211_bss_info *bss_info)
 {
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
 	memset (bss_info, 0, sizeof (*bss_info));
 
@@ -742,7 +520,7 @@ static void
 nl80211_get_ap_info (WifiDataNl80211 *nl80211,
                      struct nl80211_station_info *sta_info)
 {
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct nl80211_bss_info bss_info;
 
 	memset (sta_info, 0, sizeof (*sta_info));
@@ -790,35 +568,38 @@ wifi_nl80211_get_qual (WifiData *data)
 	return sta_info.signal;
 }
 
-#if HAVE_NL80211_CRITICAL_PROTOCOL_CMDS
 static gboolean
 wifi_nl80211_indicate_addressing_running (WifiData *data, gboolean running)
 {
 	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
 	msg = nl80211_alloc_msg (nl80211,
-	                         running ? NL80211_CMD_CRIT_PROTOCOL_START :
-	                         NL80211_CMD_CRIT_PROTOCOL_STOP,
+	                         running
+	                           ? 98 /* NL80211_CMD_CRIT_PROTOCOL_START */
+	                           : 99 /* NL80211_CMD_CRIT_PROTOCOL_STOP */,
 	                         0);
 	/* Despite the DHCP name, we're using this for any type of IP addressing,
 	 * DHCPv4, DHCPv6, and IPv6 SLAAC.
 	 */
-	NLA_PUT_U16 (msg, NL80211_ATTR_CRIT_PROT_ID, NL80211_CRIT_PROTO_DHCP);
+	NLA_PUT_U16 (msg,
+	             179 /* NL80211_ATTR_CRIT_PROT_ID */,
+	             1 /* NL80211_CRIT_PROTO_DHCP */);
 	if (running) {
 		/* Give DHCP 5 seconds to complete */
-		NLA_PUT_U16 (msg, NL80211_ATTR_MAX_CRIT_PROT_DURATION, 5000);
+		NLA_PUT_U16 (msg,
+		             180 /* NL80211_ATTR_MAX_CRIT_PROT_DURATION */,
+		             5000);
 	}
 
 	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
-	return err ? FALSE : TRUE;
+	return err >= 0;
 
 nla_put_failure:
 	nlmsg_free (msg);
 	return FALSE;
 }
-#endif
 
 struct nl80211_wowlan_info {
 	gboolean enabled;
@@ -847,12 +628,14 @@ static gboolean
 wifi_nl80211_get_wowlan (WifiData *data)
 {
 	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct nl80211_wowlan_info info;
 
+	if (!nl80211->can_wowlan)
+		return FALSE;
+
 	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WOWLAN, 0);
 	nl80211_send_and_recv (nl80211, msg, nl80211_wowlan_handler, &info);
-
 	return info.enabled;
 }
 
@@ -1062,8 +845,22 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 WifiData *
 wifi_nl80211_init (int ifindex)
 {
+	static const WifiDataClass klass = {
+		.struct_size = sizeof (WifiDataNl80211),
+		.get_mode = wifi_nl80211_get_mode,
+		.set_mode = wifi_nl80211_set_mode,
+		.set_powersave = wifi_nl80211_set_powersave,
+		.get_freq = wifi_nl80211_get_freq,
+		.find_freq = wifi_nl80211_find_freq,
+		.get_bssid = wifi_nl80211_get_bssid,
+		.get_rate = wifi_nl80211_get_rate,
+		.get_qual = wifi_nl80211_get_qual,
+		.get_wowlan = wifi_nl80211_get_wowlan,
+		.indicate_addressing_running = wifi_nl80211_indicate_addressing_running,
+		.deinit = wifi_nl80211_deinit,
+	};
 	WifiDataNl80211 *nl80211;
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct nl80211_device_info device_info = {};
 	char ifname[IFNAMSIZ];
 
@@ -1073,19 +870,7 @@ wifi_nl80211_init (int ifindex)
 		nm_sprintf_buf (ifname, "if %d", ifindex);
 	}
 
-	nl80211 = wifi_data_new (ifindex, sizeof (*nl80211));
-	nl80211->parent.get_mode = wifi_nl80211_get_mode;
-	nl80211->parent.set_mode = wifi_nl80211_set_mode;
-	nl80211->parent.set_powersave = wifi_nl80211_set_powersave;
-	nl80211->parent.get_freq = wifi_nl80211_get_freq;
-	nl80211->parent.find_freq = wifi_nl80211_find_freq;
-	nl80211->parent.get_bssid = wifi_nl80211_get_bssid;
-	nl80211->parent.get_rate = wifi_nl80211_get_rate;
-	nl80211->parent.get_qual = wifi_nl80211_get_qual;
-#if HAVE_NL80211_CRITICAL_PROTOCOL_CMDS
-	nl80211->parent.indicate_addressing_running = wifi_nl80211_indicate_addressing_running;
-#endif
-	nl80211->parent.deinit = wifi_nl80211_deinit;
+	nl80211 = wifi_data_new (&klass, ifindex);
 
 	nl80211->nl_sock = nl_socket_alloc ();
 	if (nl80211->nl_sock == NULL)
@@ -1095,12 +880,10 @@ wifi_nl80211_init (int ifindex)
 		goto error;
 
 	nl80211->id = genl_ctrl_resolve (nl80211->nl_sock, "nl80211");
-	if (nl80211->id < 0)
-		goto error;
-
-	nl80211->nl_cb = nl_cb_alloc (NL_CB_DEFAULT);
-	if (nl80211->nl_cb == NULL)
+	if (nl80211->id < 0) {
+		_LOGD (LOGD_WIFI, "genl_ctrl_resolve: failed to resolve \"nl80211\"");
 		goto error;
+	}
 
 	nl80211->phy = -1;
 
@@ -1153,18 +936,15 @@ wifi_nl80211_init (int ifindex)
 	nl80211->freqs = device_info.freqs;
 	nl80211->num_freqs = device_info.num_freqs;
 	nl80211->parent.caps = device_info.caps;
-
-	if (device_info.can_wowlan)
-		nl80211->parent.get_wowlan = wifi_nl80211_get_wowlan;
+	nl80211->can_wowlan = device_info.can_wowlan;
 
 	_LOGI (LOGD_PLATFORM | LOGD_WIFI,
 	       "(%s): using nl80211 for WiFi device control",
 	       ifname);
-
 	return (WifiData *) nl80211;
 
 error:
-	wifi_utils_deinit ((WifiData *) nl80211);
+	wifi_utils_unref ((WifiData *) nl80211);
 	return NULL;
 }
 
diff --git a/src/platform/wifi/wifi-utils-private.h b/src/platform/wifi/wifi-utils-private.h
index 11a0f060..59386514 100644
--- a/src/platform/wifi/wifi-utils-private.h
+++ b/src/platform/wifi/wifi-utils-private.h
@@ -24,9 +24,8 @@
 #include "nm-dbus-interface.h"
 #include "wifi-utils.h"
 
-struct WifiData {
-	int ifindex;
-	NMDeviceWifiCapabilities caps;
+typedef struct {
+	gsize struct_size;
 
 	NM80211Mode (*get_mode) (WifiData *data);
 
@@ -66,9 +65,14 @@ struct WifiData {
 	gboolean (*set_mesh_ssid) (WifiData *data, const guint8 *ssid, gsize len);
 
 	gboolean (*indicate_addressing_running) (WifiData *data, gboolean running);
+} WifiDataClass;
+
+struct WifiData {
+	const WifiDataClass *klass;
+	int ifindex;
+	NMDeviceWifiCapabilities caps;
 };
 
-gpointer wifi_data_new (int ifindex, gsize len);
-void wifi_data_free (WifiData *data);
+gpointer wifi_data_new (const WifiDataClass *klass, int ifindex);
 
 #endif  /* __WIFI_UTILS_PRIVATE_H__ */
diff --git a/src/platform/wifi/wifi-utils-wext.c b/src/platform/wifi/wifi-utils-wext.c
index c4d3c999..c8744f79 100644
--- a/src/platform/wifi/wifi-utils-wext.c
+++ b/src/platform/wifi/wifi-utils-wext.c
@@ -21,17 +21,14 @@
 
 #include "nm-default.h"
 
+#include "wifi-utils-wext.h"
+
 #include <errno.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <net/ethernet.h>
 #include <unistd.h>
 
-#include "wifi-utils-private.h"
-#include "wifi-utils-wext.h"
-#include "nm-utils.h"
-#include "platform/nm-platform-utils.h"
-
 /* Hacks necessary to #include wireless.h; yay for WEXT */
 #ifndef __user
 #define __user
@@ -41,6 +38,10 @@
 #include <sys/socket.h>
 #include <linux/wireless.h>
 
+#include "wifi-utils-private.h"
+#include "nm-utils.h"
+#include "platform/nm-platform-utils.h"
+
 typedef struct {
 	WifiData parent;
 	int fd;
@@ -628,6 +629,21 @@ wext_get_caps (WifiDataWext *wext, const char *ifname, struct iw_range *range)
 WifiData *
 wifi_wext_init (int ifindex, gboolean check_scan)
 {
+	static const WifiDataClass klass = {
+		.struct_size = sizeof (WifiDataWext),
+		.get_mode = wifi_wext_get_mode,
+		.set_mode = wifi_wext_set_mode,
+		.set_powersave = wifi_wext_set_powersave,
+		.get_freq = wifi_wext_get_freq,
+		.find_freq = wifi_wext_find_freq,
+		.get_bssid = wifi_wext_get_bssid,
+		.get_rate = wifi_wext_get_rate,
+		.get_qual = wifi_wext_get_qual,
+		.deinit = wifi_wext_deinit,
+		.get_mesh_channel = wifi_wext_get_mesh_channel,
+		.set_mesh_channel = wifi_wext_set_mesh_channel,
+		.set_mesh_ssid = wifi_wext_set_mesh_ssid,
+	};
 	WifiDataWext *wext;
 	struct iw_range range;
 	guint32 response_len = 0;
@@ -642,19 +658,7 @@ wifi_wext_init (int ifindex, gboolean check_scan)
 		return NULL;
 	}
 
-	wext = wifi_data_new (ifindex, sizeof (*wext));
-	wext->parent.get_mode = wifi_wext_get_mode;
-	wext->parent.set_mode = wifi_wext_set_mode;
-	wext->parent.set_powersave = wifi_wext_set_powersave;
-	wext->parent.get_freq = wifi_wext_get_freq;
-	wext->parent.find_freq = wifi_wext_find_freq;
-	wext->parent.get_bssid = wifi_wext_get_bssid;
-	wext->parent.get_rate = wifi_wext_get_rate;
-	wext->parent.get_qual = wifi_wext_get_qual;
-	wext->parent.deinit = wifi_wext_deinit;
-	wext->parent.get_mesh_channel = wifi_wext_get_mesh_channel;
-	wext->parent.set_mesh_channel = wifi_wext_set_mesh_channel;
-	wext->parent.set_mesh_ssid = wifi_wext_set_mesh_ssid;
+	wext = wifi_data_new (&klass, ifindex);
 
 	wext->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
 	if (wext->fd < 0)
@@ -730,7 +734,7 @@ wifi_wext_init (int ifindex, gboolean check_scan)
 	return (WifiData *) wext;
 
 error:
-	wifi_utils_deinit ((WifiData *) wext);
+	wifi_utils_unref ((WifiData *) wext);
 	return NULL;
 }
 
diff --git a/src/platform/wifi/wifi-utils.c b/src/platform/wifi/wifi-utils.c
index d0052121..8818dc9d 100644
--- a/src/platform/wifi/wifi-utils.c
+++ b/src/platform/wifi/wifi-utils.c
@@ -38,22 +38,19 @@
 #include "platform/nm-platform-utils.h"
 
 gpointer
-wifi_data_new (int ifindex, gsize len)
+wifi_data_new (const WifiDataClass *klass, int ifindex)
 {
 	WifiData *data;
 
-	data = g_malloc0 (len);
+	nm_assert (klass);
+	nm_assert (klass->struct_size > sizeof (WifiData));
+
+	data = g_malloc0 (klass->struct_size);
+	data->klass = klass;
 	data->ifindex = ifindex;
 	return data;
 }
 
-void
-wifi_data_free (WifiData *data)
-{
-	memset (data, 0, sizeof (*data));
-	g_free (data);
-}
-
 /*****************************************************************************/
 
 WifiData *
@@ -85,14 +82,14 @@ wifi_utils_get_caps (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, NM_WIFI_DEVICE_CAP_NONE);
 
-	return data->caps;	
+	return data->caps;
 }
 
 NM80211Mode
 wifi_utils_get_mode (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, NM_802_11_MODE_UNKNOWN);
-	return data->get_mode (data);
+	return data->klass->get_mode (data);
 }
 
 gboolean
@@ -104,7 +101,7 @@ wifi_utils_set_mode (WifiData *data, const NM80211Mode mode)
 	                      || (mode == NM_802_11_MODE_ADHOC), FALSE);
 
 	/* nl80211 probably doesn't need this */
-	return data->set_mode ? data->set_mode (data, mode) : TRUE;
+	return data->klass->set_mode ? data->klass->set_mode (data, mode) : TRUE;
 }
 
 gboolean
@@ -112,14 +109,14 @@ wifi_utils_set_powersave (WifiData *data, guint32 powersave)
 {
 	g_return_val_if_fail (data != NULL, FALSE);
 
-	return data->set_powersave ? data->set_powersave (data, powersave) : TRUE;
+	return data->klass->set_powersave ? data->klass->set_powersave (data, powersave) : TRUE;
 }
 
 guint32
 wifi_utils_get_freq (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, 0);
-	return data->get_freq (data);
+	return data->klass->get_freq (data);
 }
 
 guint32
@@ -127,7 +124,7 @@ wifi_utils_find_freq (WifiData *data, const guint32 *freqs)
 {
 	g_return_val_if_fail (data != NULL, 0);
 	g_return_val_if_fail (freqs != NULL, 0);
-	return data->find_freq (data, freqs);
+	return data->klass->find_freq (data, freqs);
 }
 
 gboolean
@@ -137,38 +134,40 @@ wifi_utils_get_bssid (WifiData *data, guint8 *out_bssid)
 	g_return_val_if_fail (out_bssid != NULL, FALSE);
 
 	memset (out_bssid, 0, ETH_ALEN);
-	return data->get_bssid (data, out_bssid);
+	return data->klass->get_bssid (data, out_bssid);
 }
 
 guint32
 wifi_utils_get_rate (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, 0);
-	return data->get_rate (data);
+	return data->klass->get_rate (data);
 }
 
 int
 wifi_utils_get_qual (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, 0);
-	return data->get_qual (data);
+	return data->klass->get_qual (data);
 }
 
 gboolean
 wifi_utils_get_wowlan (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, 0);
-	if (!data->get_wowlan)
+
+	if (!data->klass->get_wowlan)
 		return FALSE;
-	return data->get_wowlan (data);
+	return data->klass->get_wowlan (data);
 }
 
 void
-wifi_utils_deinit (WifiData *data)
+wifi_utils_unref (WifiData *data)
 {
 	g_return_if_fail (data != NULL);
-	data->deinit (data);
-	wifi_data_free (data);
+
+	data->klass->deinit (data);
+	g_free (data);
 }
 
 gboolean
@@ -191,8 +190,8 @@ guint32
 wifi_utils_get_mesh_channel (WifiData *data)
 {
 	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (data->get_mesh_channel != NULL, FALSE);
-	return data->get_mesh_channel (data);
+	g_return_val_if_fail (data->klass->get_mesh_channel != NULL, FALSE);
+	return data->klass->get_mesh_channel (data);
 }
 
 gboolean
@@ -200,24 +199,24 @@ wifi_utils_set_mesh_channel (WifiData *data, guint32 channel)
 {
 	g_return_val_if_fail (data != NULL, FALSE);
 	g_return_val_if_fail (channel <= 13, FALSE);
-	g_return_val_if_fail (data->set_mesh_channel != NULL, FALSE);
-	return data->set_mesh_channel (data, channel);
+	g_return_val_if_fail (data->klass->set_mesh_channel != NULL, FALSE);
+	return data->klass->set_mesh_channel (data, channel);
 }
 
 gboolean
 wifi_utils_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len)
 {
 	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (data->set_mesh_ssid != NULL, FALSE);
-	return data->set_mesh_ssid (data, ssid, len);
+	g_return_val_if_fail (data->klass->set_mesh_ssid != NULL, FALSE);
+	return data->klass->set_mesh_ssid (data, ssid, len);
 }
 
 gboolean
 wifi_utils_indicate_addressing_running (WifiData *data, gboolean running)
 {
 	g_return_val_if_fail (data != NULL, FALSE);
-	if (data->indicate_addressing_running)
-		return data->indicate_addressing_running (data, running);
+	if (data->klass->indicate_addressing_running)
+		return data->klass->indicate_addressing_running (data, running);
 	return FALSE;
 }
 
diff --git a/src/platform/wifi/wifi-utils.h b/src/platform/wifi/wifi-utils.h
index 705717b0..2633e965 100644
--- a/src/platform/wifi/wifi-utils.h
+++ b/src/platform/wifi/wifi-utils.h
@@ -34,7 +34,7 @@ WifiData *wifi_utils_init (int ifindex, gboolean check_scan);
 
 int wifi_utils_get_ifindex (WifiData *data);
 
-void wifi_utils_deinit (WifiData *data);
+void wifi_utils_unref (WifiData *data);
 
 NMDeviceWifiCapabilities wifi_utils_get_caps (WifiData *data);
 
diff --git a/src/ppp/meson.build b/src/ppp/meson.build
new file mode 100644
index 00000000..20edb9d0
--- /dev/null
+++ b/src/ppp/meson.build
@@ -0,0 +1,41 @@
+name = 'nm-pppd-plugin'
+
+deps = [
+  dl_dep,
+  nm_core_dep
+]
+
+nm_pppd_plugin = shared_module(
+  name,
+  name_prefix: '',
+  sources: name + '.c',
+  include_directories: src_inc,
+  dependencies: deps,
+  c_args: [
+    '-DG_LOG_DOMAIN="@0@"'.format(name),
+    '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB',
+  ],
+  install: true,
+  install_dir: pppd_plugin_dir
+)
+
+name = 'nm-ppp-plugin'
+
+deps = [
+  nm_dep
+]
+
+linker_script = join_paths(meson.current_source_dir(), 'nm-ppp-plugin.ver')
+
+core_plugins += shared_module(
+  name,
+  sources: 'nm-ppp-manager.c',
+  dependencies: deps,
+  c_args: '-DPPPD_PLUGIN_DIR="@0@"'.format(pppd_plugin_dir),
+  link_args: [
+    '-Wl,--version-script,@0@'.format(linker_script),
+  ],
+  link_depends: linker_script,
+  install: true,
+  install_dir: nm_pkglibdir
+)
diff --git a/src/ppp/nm-ppp-manager-call.c b/src/ppp/nm-ppp-manager-call.c
index ad3307a9..ed70f687 100644
--- a/src/ppp/nm-ppp-manager-call.c
+++ b/src/ppp/nm-ppp-manager-call.c
@@ -44,11 +44,9 @@ nm_ppp_manager_create (const char *iface, GError **error)
 	GError *error_local = NULL;
 	NMPPPOps *ops;
 	struct stat st;
-	int errsv;
 
 	if (G_UNLIKELY (!ppp_ops)) {
 		if (stat (PPP_PLUGIN_PATH, &st) != 0) {
-			errsv = errno;
 			g_set_error_literal (error,
 			                     NM_MANAGER_ERROR, NM_MANAGER_ERROR_MISSING_PLUGIN,
 			                     "the PPP plugin " PPP_PLUGIN_PATH " is not installed");
diff --git a/src/ppp/nm-ppp-manager.c b/src/ppp/nm-ppp-manager.c
index 743f80a2..7d1eb408 100644
--- a/src/ppp/nm-ppp-manager.c
+++ b/src/ppp/nm-ppp-manager.c
@@ -50,13 +50,12 @@
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
+#include "nm-dbus-object.h"
 
 #include "nm-pppd-plugin.h"
 #include "nm-ppp-plugin-api.h"
 #include "nm-ppp-status.h"
 
-#include "introspection/org.freedesktop.NetworkManager.PPP.h"
-
 #define NM_PPPD_PLUGIN PPPD_PLUGIN_DIR "/nm-pppd-plugin.so"
 
 static NM_CACHED_QUARK_FCN ("ppp-manager-secret-tries", ppp_manager_secret_tries_quark)
@@ -76,6 +75,7 @@ GType nm_ppp_manager_get_type (void);
 
 enum {
 	STATE_CHANGED,
+	IFINDEX_SET,
 	IP4_CONFIG,
 	IP6_CONFIG,
 	STATS,
@@ -93,6 +93,8 @@ typedef struct {
 	GPid pid;
 
 	char *parent_iface;
+	char *ip_iface;
+	int ifindex;
 
 	NMActRequest *act_req;
 	GDBusMethodInvocation *pending_secrets_context;
@@ -103,7 +105,6 @@ typedef struct {
 	guint ppp_timeout_handler;
 
 	/* Monitoring */
-	char *ip_iface;
 	int monitor_fd;
 	guint monitor_id;
 
@@ -114,17 +115,17 @@ typedef struct {
 } NMPPPManagerPrivate;
 
 struct _NMPPPManager {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	NMPPPManagerPrivate _priv;
 };
 
 typedef struct {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 } NMPPPManagerClass;
 
-G_DEFINE_TYPE (NMPPPManager, nm_ppp_manager, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMPPPManager, nm_ppp_manager, NM_TYPE_DBUS_OBJECT)
 
-#define NM_PPP_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMPPPManager, NM_IS_PPP_MANAGER)
+#define NM_PPP_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMPPPManager, NM_IS_PPP_MANAGER, NMDBusObject)
 
 /*****************************************************************************/
 
@@ -174,24 +175,28 @@ monitor_cb (gpointer user_data)
 {
 	NMPPPManager *manager = NM_PPP_MANAGER (user_data);
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (manager);
-	struct ifreq req;
-	struct ppp_stats stats;
+	const char *ifname;
 
-	memset (&req, 0, sizeof (req));
-	memset (&stats, 0, sizeof (stats));
-	req.ifr_data = (caddr_t) &stats;
+	ifname = nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex);
 
-	strncpy (req.ifr_name, priv->ip_iface, sizeof (req.ifr_name));
-	if (ioctl (priv->monitor_fd, SIOCGPPPSTATS, &req) < 0) {
-		if (errno != ENODEV)
-			_LOGW ("could not read ppp stats: %s", strerror (errno));
-	} else {
-		g_signal_emit (manager, signals[STATS], 0,
-		               (guint) stats.p.ppp_ibytes,
-		               (guint) stats.p.ppp_obytes);
+	if (ifname) {
+		struct ppp_stats stats = { };
+		struct ifreq req = {
+			.ifr_data = (caddr_t) &stats,
+		};
+
+		nm_utils_ifname_cpy (req.ifr_name, ifname);
+		if (ioctl (priv->monitor_fd, SIOCGPPPSTATS, &req) < 0) {
+			if (errno != ENODEV)
+				_LOGW ("could not read ppp stats: %s", strerror (errno));
+		} else {
+			g_signal_emit (manager, signals[STATS], 0,
+			               (guint) stats.p.ppp_ibytes,
+			               (guint) stats.p.ppp_obytes);
+		}
 	}
 
-	return TRUE;
+	return G_SOURCE_CONTINUE;
 }
 
 static void
@@ -326,20 +331,27 @@ ppp_secrets_cb (NMActRequest *req,
 	 * against libnm just to parse this. So instead, let's just send what
 	 * it needs.
 	 */
-	g_dbus_method_invocation_return_value (
-		priv->pending_secrets_context,
-		g_variant_new ("(ss)", username ? username : "", password ? password : ""));
+	g_dbus_method_invocation_return_value (priv->pending_secrets_context,
+	                                       g_variant_new ("(ss)",
+	                                                      username ?: "",
+	                                                      password ?: ""));
 
- out:
+out:
 	priv->pending_secrets_context = NULL;
 	priv->secrets_id = NULL;
 	priv->secrets_setting_name = NULL;
 }
 
 static void
-impl_ppp_manager_need_secrets (NMPPPManager *manager,
-                               GDBusMethodInvocation *context)
+impl_ppp_manager_need_secrets (NMDBusObject *obj,
+                               const NMDBusInterfaceInfoExtended *interface_info,
+                               const NMDBusMethodInfoExtended *method_info,
+                               GDBusConnection *connection,
+                               const char *sender,
+                               GDBusMethodInvocation *invocation,
+                               GVariant *parameters)
 {
+	NMPPPManager *manager = NM_PPP_MANAGER (obj);
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (manager);
 	NMConnection *applied_connection;
 	const char *username = NULL;
@@ -358,7 +370,7 @@ impl_ppp_manager_need_secrets (NMPPPManager *manager,
 		/* Use existing secrets from the connection */
 		if (extract_details_from_connection (applied_connection, NULL, &username, &password, &error)) {
 			/* Send existing secrets to the PPP plugin */
-			priv->pending_secrets_context = context;
+			priv->pending_secrets_context = invocation;
 			ppp_secrets_cb (priv->act_req, priv->secrets_id, NULL, NULL, manager);
 		} else {
 			_LOGW ("%s", error->message);
@@ -383,39 +395,85 @@ impl_ppp_manager_need_secrets (NMPPPManager *manager,
 	                                               ppp_secrets_cb,
 	                                               manager);
 	g_object_set_qdata (G_OBJECT (applied_connection), ppp_manager_secret_tries_quark (), GUINT_TO_POINTER (++tries));
-	priv->pending_secrets_context = context;
+	priv->pending_secrets_context = invocation;
 
 	if (hints)
 		g_ptr_array_free (hints, TRUE);
 }
 
 static void
-impl_ppp_manager_set_state (NMPPPManager *manager,
-                            GDBusMethodInvocation *context,
-                            guint32 state)
+impl_ppp_manager_set_state (NMDBusObject *obj,
+                            const NMDBusInterfaceInfoExtended *interface_info,
+                            const NMDBusMethodInfoExtended *method_info,
+                            GDBusConnection *connection,
+                            const char *sender,
+                            GDBusMethodInvocation *invocation,
+                            GVariant *parameters)
 {
+	NMPPPManager *manager = NM_PPP_MANAGER (obj);
+	guint32 state;
+
+	g_variant_get (parameters, "(u)", &state);
 	g_signal_emit (manager, signals[STATE_CHANGED], 0, (guint) state);
+	g_dbus_method_invocation_return_value (invocation, NULL);
+}
 
-	g_dbus_method_invocation_return_value (context, NULL);
+static void
+impl_ppp_manager_set_ifindex (NMDBusObject *obj,
+                              const NMDBusInterfaceInfoExtended *interface_info,
+                              const NMDBusMethodInfoExtended *method_info,
+                              GDBusConnection *connection,
+                              const char *sender,
+                              GDBusMethodInvocation *invocation,
+                              GVariant *parameters)
+{
+	NMPPPManager *manager = NM_PPP_MANAGER (obj);
+	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (manager);
+	const NMPlatformLink *plink = NULL;
+	nm_auto_nmpobj const NMPObject *obj_keep_alive = NULL;
+	gint32 ifindex;
+
+	g_variant_get (parameters, "(i)", &ifindex);
+
+	_LOGD ("set-ifindex %d", (int) ifindex);
+
+	if (priv->ifindex >= 0) {
+		_LOGW ("can't change the ifindex from %d to %d", priv->ifindex, (int) ifindex);
+		return;
+	}
+
+	if (ifindex > 0) {
+		plink = nm_platform_link_get (NM_PLATFORM_GET, ifindex);
+		if (!plink) {
+			nm_platform_process_events (NM_PLATFORM_GET);
+			plink = nm_platform_link_get (NM_PLATFORM_GET, ifindex);
+		}
+	}
+
+	if (!plink) {
+		_LOGW ("unknown interface with ifindex %d", ifindex);
+		ifindex = 0;
+	}
+
+	priv->ifindex = ifindex;
+
+	obj_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink));
+
+	g_signal_emit (manager, signals[IFINDEX_SET], 0, ifindex, plink->name);
+	g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
 static gboolean
 set_ip_config_common (NMPPPManager *self,
                       GVariant *config_dict,
-                      const char *iface_prop,
                       guint32 *out_mtu)
 {
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (self);
 	NMConnection *applied_connection;
 	NMSettingPpp *s_ppp;
-	const char *iface;
 
-	if (!g_variant_lookup (config_dict, iface_prop, "&s", &iface)) {
-		_LOGE ("no interface received!");
+	if (priv->ifindex <= 0)
 		return FALSE;
-	}
-	if (priv->ip_iface == NULL)
-		priv->ip_iface = g_strdup (iface);
 
 	/* Got successful IP config; obviously the secrets worked */
 	applied_connection = nm_act_request_get_applied_connection (priv->act_req);
@@ -432,29 +490,32 @@ set_ip_config_common (NMPPPManager *self,
 }
 
 static void
-impl_ppp_manager_set_ip4_config (NMPPPManager *manager,
-                                 GDBusMethodInvocation *context,
-                                 GVariant *config_dict)
+impl_ppp_manager_set_ip4_config (NMDBusObject *obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *method_info,
+                                 GDBusConnection *connection,
+                                 const char *sender,
+                                 GDBusMethodInvocation *invocation,
+                                 GVariant *parameters)
 {
+	NMPPPManager *manager = NM_PPP_MANAGER (obj);
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (manager);
 	gs_unref_object NMIP4Config *config = NULL;
 	NMPlatformIP4Address address;
 	guint32 u32, mtu;
 	GVariantIter *iter;
-	int ifindex;
+	gs_unref_variant GVariant *config_dict = NULL;
 
 	_LOGI ("(IPv4 Config Get) reply received.");
 
-	nm_clear_g_source (&priv->ppp_timeout_handler);
+	g_variant_get (parameters, "(@a{sv})", &config_dict);
 
-	if (!set_ip_config_common (manager, config_dict, NM_PPP_IP4_CONFIG_INTERFACE, &mtu))
-		goto out;
+	nm_clear_g_source (&priv->ppp_timeout_handler);
 
-	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, priv->ip_iface);
-	if (ifindex <= 0)
+	if (!set_ip_config_common (manager, config_dict, &mtu))
 		goto out;
 
-	config = nm_ip4_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET), ifindex);
+	config = nm_ip4_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET), priv->ifindex);
 
 	if (mtu)
 		nm_ip4_config_set_mtu (config, mtu, NM_IP_CONFIG_SOURCE_PPP);
@@ -467,7 +528,7 @@ impl_ppp_manager_set_ip4_config (NMPPPManager *manager,
 
 	if (g_variant_lookup (config_dict, NM_PPP_IP4_CONFIG_GATEWAY, "u", &u32)) {
 		const NMPlatformIP4Route r = {
-			.ifindex   = ifindex,
+			.ifindex   = priv->ifindex,
 			.rt_source = NM_IP_CONFIG_SOURCE_PPP,
 			.gateway   = u32,
 			.table_coerced = nm_platform_route_table_coerce (priv->ip4_route_table),
@@ -503,10 +564,10 @@ impl_ppp_manager_set_ip4_config (NMPPPManager *manager,
 	}
 
 	/* Push the IP4 config up to the device */
-	g_signal_emit (manager, signals[IP4_CONFIG], 0, priv->ip_iface, config);
+	g_signal_emit (manager, signals[IP4_CONFIG], 0, config);
 
 out:
-	g_dbus_method_invocation_return_value (context, NULL);
+	g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
 /* Converts the named Interface Identifier item to an IPv6 LL address and
@@ -539,37 +600,40 @@ iid_value_to_ll6_addr (GVariant *dict,
 }
 
 static void
-impl_ppp_manager_set_ip6_config (NMPPPManager *manager,
-                                 GDBusMethodInvocation *context,
-                                 GVariant *config_dict)
+impl_ppp_manager_set_ip6_config (NMDBusObject *obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *method_info,
+                                 GDBusConnection *connection,
+                                 const char *sender,
+                                 GDBusMethodInvocation *invocation,
+                                 GVariant *parameters)
 {
+	NMPPPManager *manager = NM_PPP_MANAGER (obj);
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (manager);
 	gs_unref_object NMIP6Config *config = NULL;
 	NMPlatformIP6Address addr;
 	struct in6_addr a;
 	NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT;
 	gboolean has_peer = FALSE;
-	int ifindex;
+	gs_unref_variant GVariant *config_dict = NULL;
 
 	_LOGI ("(IPv6 Config Get) reply received.");
 
-	nm_clear_g_source (&priv->ppp_timeout_handler);
+	g_variant_get (parameters, "(@a{sv})", &config_dict);
 
-	if (!set_ip_config_common (manager, config_dict, NM_PPP_IP6_CONFIG_INTERFACE, NULL))
-		goto out;
+	nm_clear_g_source (&priv->ppp_timeout_handler);
 
-	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, priv->ip_iface);
-	if (ifindex <= 0)
+	if (!set_ip_config_common (manager, config_dict, NULL))
 		goto out;
 
-	config = nm_ip6_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET), ifindex);
+	config = nm_ip6_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET), priv->ifindex);
 
 	memset (&addr, 0, sizeof (addr));
 	addr.plen = 64;
 
 	if (iid_value_to_ll6_addr (config_dict, NM_PPP_IP6_CONFIG_PEER_IID, &a, NULL)) {
 		const NMPlatformIP6Route r = {
-			.ifindex   = ifindex,
+			.ifindex   = priv->ifindex,
 			.rt_source = NM_IP_CONFIG_SOURCE_PPP,
 			.gateway   = a,
 			.table_coerced = nm_platform_route_table_coerce (priv->ip6_route_table),
@@ -587,12 +651,12 @@ impl_ppp_manager_set_ip6_config (NMPPPManager *manager,
 		nm_ip6_config_add_address (config, &addr);
 
 		/* Push the IPv6 config and interface identifier up to the device */
-		g_signal_emit (manager, signals[IP6_CONFIG], 0, priv->ip_iface, &iid, config);
+		g_signal_emit (manager, signals[IP6_CONFIG], 0, &iid, config);
 	} else
 		_LOGE ("invalid IPv6 address received!");
 
 out:
-	g_dbus_method_invocation_return_value (context, NULL);
+	g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
 /*****************************************************************************/
@@ -899,7 +963,7 @@ create_pppd_cmd_line (NMPPPManager *self,
 	nm_cmd_line_add_int (cmd, 0);
 
 	nm_cmd_line_add_string (cmd, "ipparam");
-	nm_cmd_line_add_string (cmd, nm_exported_object_get_path (NM_EXPORTED_OBJECT (self)));
+	nm_cmd_line_add_string (cmd, nm_dbus_object_get_path (NM_DBUS_OBJECT (self)));
 
 	nm_cmd_line_add_string (cmd, "plugin");
 	nm_cmd_line_add_string (cmd, NM_PPPD_PLUGIN);
@@ -978,7 +1042,7 @@ _ppp_manager_start (NMPPPManager *manager,
 	return FALSE;
 #endif
 
-	nm_exported_object_export (NM_EXPORTED_OBJECT (manager));
+	nm_dbus_object_export (NM_DBUS_OBJECT (manager));
 
 	priv->pid = 0;
 
@@ -1052,7 +1116,7 @@ out:
 		nm_cmd_line_destroy (ppp_cmd);
 
 	if (priv->pid <= 0)
-		nm_exported_object_unexport (NM_EXPORTED_OBJECT (manager));
+		nm_dbus_object_unexport (NM_DBUS_OBJECT (manager));
 
 	return priv->pid > 0;
 }
@@ -1156,7 +1220,7 @@ _ppp_manager_stop_async (NMPPPManager *manager,
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (manager);
 	StopContext *ctx;
 
-	nm_exported_object_unexport (NM_EXPORTED_OBJECT (manager));
+	nm_dbus_object_unexport (NM_DBUS_OBJECT (manager));
 
 	ctx = g_slice_new0 (StopContext);
 	ctx->manager = g_object_ref (manager);
@@ -1193,10 +1257,10 @@ _ppp_manager_stop_async (NMPPPManager *manager,
 static void
 _ppp_manager_stop_sync (NMPPPManager *manager)
 {
-	NMExportedObject *exported = NM_EXPORTED_OBJECT (manager);
+	NMDBusObject *dbus = NM_DBUS_OBJECT (manager);
 
-	if (nm_exported_object_is_exported (exported))
-		nm_exported_object_unexport (exported);
+	if (nm_dbus_object_is_exported (dbus))
+		nm_dbus_object_unexport (dbus);
 
 	_ppp_cleanup (manager);
 	_ppp_kill (manager);
@@ -1244,6 +1308,7 @@ nm_ppp_manager_init (NMPPPManager *manager)
 {
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (manager);
 
+	priv->ifindex = -1;
 	priv->monitor_fd = -1;
 	priv->ip4_route_table = RT_TABLE_MAIN;
 	priv->ip4_route_metric = 460;
@@ -1265,11 +1330,11 @@ static void
 dispose (GObject *object)
 {
 	NMPPPManager *self = (NMPPPManager *) object;
-	NMExportedObject *exported = NM_EXPORTED_OBJECT (self);
+	NMDBusObject *dbus = NM_DBUS_OBJECT (self);
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (self);
 
-	if (nm_exported_object_is_exported (exported))
-		nm_exported_object_unexport (exported);
+	if (nm_dbus_object_is_exported (dbus))
+		nm_dbus_object_unexport (dbus);
 
 	_ppp_cleanup (self);
 	_ppp_kill (self);
@@ -1284,24 +1349,78 @@ finalize (GObject *object)
 {
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE ((NMPPPManager *) object);
 
-	g_free (priv->ip_iface);
 	g_free (priv->parent_iface);
 
 	G_OBJECT_CLASS (nm_ppp_manager_parent_class)->finalize (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_ppp = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_PPP,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"NeedSecrets",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("username", "s"),
+						NM_DEFINE_GDBUS_ARG_INFO ("password", "s"),
+					),
+				),
+				.handle = impl_ppp_manager_need_secrets,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"SetIp4Config",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("config", "a{sv}"),
+					),
+				),
+				.handle = impl_ppp_manager_set_ip4_config,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"SetIp6Config",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("config", "a{sv}"),
+					),
+				),
+				.handle = impl_ppp_manager_set_ip6_config,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"SetState",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("state", "u"),
+					),
+				),
+				.handle = impl_ppp_manager_set_state,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"SetIfindex",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("ifindex", "i"),
+					),
+				),
+				.handle = impl_ppp_manager_set_ifindex,
+			),
+		),
+	),
+};
+
 static void
 nm_ppp_manager_class_init (NMPPPManagerClass *manager_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (manager_class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (manager_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (manager_class);
 
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/PPP");
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/PPP");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_ppp);
 
 	obj_properties[PROP_PARENT_IFACE] =
 	     g_param_spec_string (NM_PPP_MANAGER_PARENT_IFACE, "", "",
@@ -1320,14 +1439,23 @@ nm_ppp_manager_class_init (NMPPPManagerClass *manager_class)
 	                  G_TYPE_NONE, 1,
 	                  G_TYPE_UINT);
 
+	signals[IFINDEX_SET] =
+	    g_signal_new (NM_PPP_MANAGER_SIGNAL_IFINDEX_SET,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 2,
+	                  G_TYPE_INT,
+	                  G_TYPE_STRING);
+
 	signals[IP4_CONFIG] =
 	    g_signal_new (NM_PPP_MANAGER_SIGNAL_IP4_CONFIG,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_FIRST,
 	                  0,
 	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 2,
-	                  G_TYPE_STRING,
+	                  G_TYPE_NONE, 1,
 	                  G_TYPE_OBJECT);
 
 	signals[IP6_CONFIG] =
@@ -1336,7 +1464,9 @@ nm_ppp_manager_class_init (NMPPPManagerClass *manager_class)
 	                  G_SIGNAL_RUN_FIRST,
 	                  0,
 	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_OBJECT);
+	                  G_TYPE_NONE, 2,
+	                  G_TYPE_POINTER,
+	                  G_TYPE_OBJECT);
 
 	signals[STATS] =
 	    g_signal_new (NM_PPP_MANAGER_SIGNAL_STATS,
@@ -1347,14 +1477,6 @@ nm_ppp_manager_class_init (NMPPPManagerClass *manager_class)
 	                  G_TYPE_NONE, 2,
 	                  G_TYPE_UINT /*guint32 in_bytes*/,
 	                  G_TYPE_UINT /*guint32 out_bytes*/);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (manager_class),
-	                                        NMDBUS_TYPE_PPP_MANAGER_SKELETON,
-	                                        "NeedSecrets", impl_ppp_manager_need_secrets,
-	                                        "SetIp4Config", impl_ppp_manager_set_ip4_config,
-	                                        "SetIp6Config", impl_ppp_manager_set_ip6_config,
-	                                        "SetState", impl_ppp_manager_set_state,
-	                                        NULL);
 }
 
 NMPPPOps ppp_ops = {
diff --git a/src/ppp/nm-ppp-manager.h b/src/ppp/nm-ppp-manager.h
index 35fb1b60..5457726e 100644
--- a/src/ppp/nm-ppp-manager.h
+++ b/src/ppp/nm-ppp-manager.h
@@ -25,6 +25,7 @@
 #define NM_PPP_MANAGER_PARENT_IFACE    "parent-iface"
 
 #define NM_PPP_MANAGER_SIGNAL_STATE_CHANGED "state-changed"
+#define NM_PPP_MANAGER_SIGNAL_IFINDEX_SET   "ifindex-set"
 #define NM_PPP_MANAGER_SIGNAL_IP4_CONFIG    "ip4-config"
 #define NM_PPP_MANAGER_SIGNAL_IP6_CONFIG    "ip6-config"
 #define NM_PPP_MANAGER_SIGNAL_STATS         "stats"
diff --git a/src/ppp/nm-pppd-plugin.c b/src/ppp/nm-pppd-plugin.c
index 0ac8f907..989f7433 100644
--- a/src/ppp/nm-pppd-plugin.c
+++ b/src/ppp/nm-pppd-plugin.c
@@ -28,6 +28,7 @@
 #include <pppd/ipcp.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <net/if.h>
 #include <arpa/inet.h>
 #include <dlfcn.h>
 
@@ -36,7 +37,9 @@
 #include <pppd/ipv6cp.h>
 
 #include "nm-default.h"
+
 #include "nm-dbus-interface.h"
+
 #include "nm-pppd-plugin.h"
 #include "nm-ppp-status.h"
 
@@ -50,7 +53,9 @@ static void
 nm_phasechange (void *data, 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));
 
@@ -126,6 +131,25 @@ nm_phasechange (void *data, int arg)
 		                   NULL,
 		                   NULL, NULL);
 	}
+
+	if (ppp_status == PHASE_RUNNING) {
+		index = 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);
+		/* Update the name in pppd if NM changed it */
+		if (   if_indextoname (index, new_name)
+		    && !nm_streq0 (ifname, new_name)) {
+			g_message ("nm-ppp-plugin: interface name changed from '%s' to '%s'", ifname, new_name);
+			strncpy (ifname, new_name, IF_NAMESIZE);
+		}
+	}
 }
 
 static void
@@ -148,6 +172,9 @@ nm_ip_up (void *data, int arg)
 
 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
 
+	/* Keep sending the interface name to be backwards compatible
+	 * with older versions of NM during a package upgrade, where
+	 * NM is not restarted and the pppd plugin was not loaded. */
 	g_variant_builder_add (&builder, "{sv}",
 	                       NM_PPP_IP4_CONFIG_INTERFACE,
 	                       g_variant_new_string (ifname));
@@ -242,6 +269,9 @@ nm_ip6_up (void *data, int arg)
 	g_message ("nm-ppp-plugin: (%s): ip6-up event", __func__);
 
 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+	/* Keep sending the interface name to be backwards compatible
+	 * with older versions of NM during a package upgrade, where
+	 * NM is not restarted and the pppd plugin was not loaded. */
 	g_variant_builder_add (&builder, "{sv}",
 	                       NM_PPP_IP6_CONFIG_INTERFACE,
 	                       g_variant_new_string (ifname));
@@ -361,8 +391,6 @@ plugin_init (void)
 	GDBusConnection *bus;
 	GError *err = NULL;
 
-	nm_g_type_init ();
-
 	g_message ("nm-ppp-plugin: (%s): initializing", __func__);
 
 	bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &err);
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index a68db47e..453136e4 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -31,14 +31,12 @@
 #include "nm-auth-utils.h"
 #include "nm-setting-vpn.h"
 #include "nm-auth-manager.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
 #include "nm-session-monitor.h"
 #include "nm-simple-connection.h"
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
-#include "nm-utils/c-list.h"
-
-#include "introspection/org.freedesktop.NetworkManager.AgentManager.h"
+#include "c-list/src/c-list.h"
 
 /*****************************************************************************/
 
@@ -67,15 +65,15 @@ typedef struct {
 } NMAgentManagerPrivate;
 
 struct _NMAgentManager {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	NMAgentManagerPrivate _priv;
 };
 
 struct _NMAgentManagerClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 };
 
-G_DEFINE_TYPE (NMAgentManager, nm_agent_manager, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMAgentManager, nm_agent_manager, NM_TYPE_DBUS_OBJECT)
 
 #define NM_AGENT_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMAgentManager, NM_IS_AGENT_MANAGER)
 
@@ -362,7 +360,7 @@ agent_register_permissions_done (NMAuthChain *chain,
 			request_add_agent (c_list_entry (iter, Request, lst_request), agent);
 	}
 
-	nm_auth_chain_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 
 static NMSecretAgent *
@@ -392,10 +390,10 @@ agent_disconnected_cb (NMSecretAgent *agent, gpointer user_data)
 }
 
 static void
-impl_agent_manager_register_with_capabilities (NMAgentManager *self,
-                                               GDBusMethodInvocation *context,
-                                               const char *identifier,
-                                               guint32 capabilities)
+agent_manager_register_with_capabilities (NMAgentManager *self,
+                                          GDBusMethodInvocation *context,
+                                          const char *identifier,
+                                          guint32 capabilities)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 	NMAuthSubject *subject;
@@ -460,45 +458,56 @@ done:
 }
 
 static void
-impl_agent_manager_register (NMAgentManager *self,
-                             GDBusMethodInvocation *context,
-                             const char *identifier)
+impl_agent_manager_register (NMDBusObject *obj,
+                             const NMDBusInterfaceInfoExtended *interface_info,
+                             const NMDBusMethodInfoExtended *method_info,
+                             GDBusConnection *connection,
+                             const char *sender,
+                             GDBusMethodInvocation *invocation,
+                             GVariant *parameters)
 {
-	impl_agent_manager_register_with_capabilities (self, context, identifier, 0);
+	const char *identifier;
+
+	g_variant_get (parameters, "(&s)", &identifier);
+	agent_manager_register_with_capabilities (NM_AGENT_MANAGER (obj), invocation, identifier, 0);
 }
 
 static void
-impl_agent_manager_unregister (NMAgentManager *self,
-                               GDBusMethodInvocation *context)
+impl_agent_manager_register_with_capabilities (NMDBusObject *obj,
+                                               const NMDBusInterfaceInfoExtended *interface_info,
+                                               const NMDBusMethodInfoExtended *method_info,
+                                               GDBusConnection *connection,
+                                               const char *sender,
+                                               GDBusMethodInvocation *invocation,
+                                               GVariant *parameters)
 {
-	GError *error = NULL;
-	char *sender = NULL;
+	const char *identifier;
+	guint32 capabilities;
 
-	if (!nm_bus_manager_get_caller_info (nm_bus_manager_get (),
-	                                     context,
-	                                     &sender,
-	                                     NULL,
-	                                     NULL)) {
-		error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
-		                             NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Unable to determine request sender.");
-		goto done;
-	}
+	g_variant_get (parameters, "(&su)", &identifier, &capabilities);
+	agent_manager_register_with_capabilities (NM_AGENT_MANAGER (obj), invocation, identifier, capabilities);
+}
+
+static void
+impl_agent_manager_unregister (NMDBusObject *obj,
+                               const NMDBusInterfaceInfoExtended *interface_info,
+                               const NMDBusMethodInfoExtended *method_info,
+                               GDBusConnection *connection,
+                               const char *sender,
+                               GDBusMethodInvocation *invocation,
+                               GVariant *parameters)
+{
+	NMAgentManager *self = NM_AGENT_MANAGER (obj);
 
-	/* Found the agent, unregister and remove it */
 	if (!remove_agent (self, sender)) {
-		error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
-		                             NM_AGENT_MANAGER_ERROR_NOT_REGISTERED,
-		                             "Caller is not registered as an Agent");
-		goto done;
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_AGENT_MANAGER_ERROR,
+		                                               NM_AGENT_MANAGER_ERROR_NOT_REGISTERED,
+		                                               "Caller is not registered as an Agent");
+		return;
 	}
 
-	g_dbus_method_invocation_return_value (context, NULL);
-
-done:
-	if (error)
-		g_dbus_method_invocation_take_error (context, error);
-	g_free (sender);
+	g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
 /*****************************************************************************/
@@ -530,7 +539,7 @@ request_free (Request *req)
 		g_object_unref (req->con.connection);
 		g_free (req->con.path);
 		if (req->con.chain)
-			nm_auth_chain_unref (req->con.chain);
+			nm_auth_chain_destroy (req->con.chain);
 		if (req->request_type == REQUEST_TYPE_CON_GET) {
 			g_free (req->con.get.setting_name);
 			g_strfreev (req->con.get.hints);
@@ -801,7 +810,7 @@ request_remove_agent (Request *req, NMSecretAgent *agent)
 		case REQUEST_TYPE_CON_DEL:
 			if (req->con.chain) {
 				/* This cancels the pending authorization requests. */
-				nm_auth_chain_unref (req->con.chain);
+				nm_auth_chain_destroy (req->con.chain);
 				req->con.chain = NULL;
 			}
 			break;
@@ -1038,7 +1047,7 @@ _con_get_request_start_validated (NMAuthChain *chain,
 		_con_get_request_start_proceed (req, req->con.current_has_modify);
 	}
 
-	nm_auth_chain_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 
 static void
@@ -1532,7 +1541,7 @@ agent_permissions_changed_done (NMAuthChain *chain,
 	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_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 
 static void
@@ -1586,7 +1595,7 @@ constructed (GObject *object)
 	priv->auth_mgr = g_object_ref (nm_auth_manager_get ());
 	priv->session_monitor = g_object_ref (nm_session_monitor_get ());
 
-	nm_exported_object_export (NM_EXPORTED_OBJECT (object));
+	nm_dbus_object_export (NM_DBUS_OBJECT (object));
 
 	g_signal_connect (priv->auth_mgr,
 	                  NM_AUTH_MANAGER_SIGNAL_CHANGED,
@@ -1607,7 +1616,7 @@ cancel_more:
 		goto cancel_more;
 	}
 
-	g_slist_free_full (priv->chains, (GDestroyNotify) nm_auth_chain_unref);
+	g_slist_free_full (priv->chains, (GDestroyNotify) nm_auth_chain_destroy);
 	priv->chains = NULL;
 
 	if (priv->agents) {
@@ -1622,20 +1631,64 @@ cancel_more:
 		g_clear_object (&priv->auth_mgr);
 	}
 
-	nm_exported_object_unexport (NM_EXPORTED_OBJECT (object));
+	nm_dbus_object_unexport (NM_DBUS_OBJECT (object));
 
 	g_clear_object (&priv->session_monitor);
 
 	G_OBJECT_CLASS (nm_agent_manager_parent_class)->dispose (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_agent_manager = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_AGENT_MANAGER,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Register",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("identifier", "s"),
+					),
+				),
+				.handle = impl_agent_manager_register,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"RegisterWithCapabilities",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("identifier",   "s"),
+						NM_DEFINE_GDBUS_ARG_INFO ("capabilities", "u"),
+					),
+				),
+				.handle = impl_agent_manager_register_with_capabilities,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"RegisterWithCapabilities",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("identifier",   "s"),
+						NM_DEFINE_GDBUS_ARG_INFO ("capabilities", "u"),
+					),
+				),
+				.handle = impl_agent_manager_register_with_capabilities,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Unregister",
+				),
+				.handle = impl_agent_manager_unregister,
+			),
+		),
+	),
+};
+
 static void
 nm_agent_manager_class_init (NMAgentManagerClass *agent_manager_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (agent_manager_class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (agent_manager_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (agent_manager_class);
 
-	exported_object_class->export_path = NM_DBUS_PATH_AGENT_MANAGER;
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC (NM_DBUS_PATH_AGENT_MANAGER);
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_agent_manager);
 
 	object_class->constructed = constructed;
 	object_class->dispose = dispose;
@@ -1649,11 +1702,4 @@ nm_agent_manager_class_init (NMAgentManagerClass *agent_manager_class)
 	                  g_cclosure_marshal_VOID__OBJECT,
 	                  G_TYPE_NONE, 1,
 	                  G_TYPE_OBJECT);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (agent_manager_class),
-	                                        NMDBUS_TYPE_AGENT_MANAGER_SKELETON,
-	                                        "Register", impl_agent_manager_register,
-	                                        "RegisterWithCapabilities", impl_agent_manager_register_with_capabilities,
-	                                        "Unregister", impl_agent_manager_unregister,
-	                                        NULL);
 }
diff --git a/src/settings/nm-agent-manager.h b/src/settings/nm-agent-manager.h
index f6845818..8c27f26b 100644
--- a/src/settings/nm-agent-manager.h
+++ b/src/settings/nm-agent-manager.h
@@ -23,7 +23,7 @@
 
 #include "nm-connection.h"
 
-#include "nm-exported-object.h"
+#include "nm-dbus-object.h"
 #include "nm-secret-agent.h"
 
 #define NM_TYPE_AGENT_MANAGER            (nm_agent_manager_get_type ())
diff --git a/src/settings/nm-secret-agent.c b/src/settings/nm-secret-agent.c
index 192e9877..af6d7017 100644
--- a/src/settings/nm-secret-agent.c
+++ b/src/settings/nm-secret-agent.c
@@ -26,13 +26,12 @@
 #include <pwd.h>
 
 #include "nm-dbus-interface.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
+#include "nm-core-internal.h"
 #include "nm-auth-subject.h"
 #include "nm-simple-connection.h"
 #include "NetworkManagerUtils.h"
-#include "nm-utils/c-list.h"
-
-#include "introspection/org.freedesktop.NetworkManager.SecretAgent.h"
+#include "c-list/src/c-list.h"
 
 /*****************************************************************************/
 
@@ -51,8 +50,8 @@ typedef struct {
 	char *dbus_owner;
 	NMSecretAgentCapabilities capabilities;
 	GSList *permissions;
-	NMDBusSecretAgent *proxy;
-	NMBusManager *bus_mgr;
+	GDBusProxy *proxy;
+	NMDBusManager *bus_mgr;
 	GDBusConnection *connection;
 	CList requests;
 	gulong on_disconnected_id;
@@ -300,7 +299,7 @@ nm_secret_agent_add_permission (NMSecretAgent *agent,
  * @permission: The name of the permission to check for
  *
  * Returns whether or not the agent has the given permission.
- * 
+ *
  * Returns: %TRUE if the agent has the given permission, %FALSE if it does not
  * or if the permission was not previous recorded with
  * nm_secret_agent_add_permission().
@@ -336,11 +335,17 @@ get_callback (GObject *proxy,
 	if (request_check_return (r)) {
 		NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (r->agent);
 		gs_free_error GError *error = NULL;
+		gs_unref_variant GVariant *ret = NULL;
 		gs_unref_variant GVariant *secrets = NULL;
 
-		nmdbus_secret_agent_call_get_secrets_finish (priv->proxy, &secrets, result, &error);
-		if (error)
+		ret = _nm_dbus_proxy_call_finish (priv->proxy, result, G_VARIANT_TYPE ("(a{sa{sv}})"), &error);
+		if (!ret)
 			g_dbus_error_strip_remote_error (error);
+		else {
+			g_variant_get (ret,
+			               "(@a{sa{sv}})",
+			               &secrets);
+		}
 		r->callback (r->agent, r, secrets, error, r->callback_data);
 	}
 
@@ -358,7 +363,6 @@ nm_secret_agent_get_secrets (NMSecretAgent *self,
                              gpointer callback_data)
 {
 	NMSecretAgentPrivate *priv;
-	static const char *no_hints[] = { NULL };
 	GVariant *dict;
 	NMSecretAgentCallId *r;
 
@@ -379,16 +383,20 @@ nm_secret_agent_get_secrets (NMSecretAgent *self,
 	r = request_new (self, "GetSecrets", path, setting_name, callback, callback_data);
 	r->is_get_secrets = TRUE;
 
-	/* Increase the timeout only for this call */
-	g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (priv->proxy), 120000);
-	nmdbus_secret_agent_call_get_secrets (priv->proxy,
-	                                      dict,
-	                                      path,
-	                                      setting_name,
-	                                      hints ? hints : no_hints,
-	                                      flags,
-	                                      r->cancellable,
-	                                      get_callback, r);
+	g_dbus_proxy_call (priv->proxy,
+	                   "GetSecrets",
+	                   g_variant_new ("(@a{sa{sv}}os^asu)",
+	                                  dict,
+	                                  path,
+	                                  setting_name,
+	                                  hints ?: NM_PTRARRAY_EMPTY (const char *),
+	                                  (guint32) flags),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   120000,
+	                   r->cancellable,
+	                   get_callback,
+	                   r);
+
 	g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (priv->proxy), -1);
 
 	return r;
@@ -399,17 +407,16 @@ nm_secret_agent_get_secrets (NMSecretAgent *self,
 static void
 cancel_done (GObject *proxy, GAsyncResult *result, gpointer user_data)
 {
-	char *description = user_data;
-	GError *error = NULL;
+	gs_free char *description = user_data;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *ret = NULL;
 
-	if (!nmdbus_secret_agent_call_cancel_get_secrets_finish (NMDBUS_SECRET_AGENT (proxy), result, &error)) {
+	ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result, G_VARIANT_TYPE ("()"), &error);
+	if (!ret) {
 		nm_log_dbg (LOGD_AGENTS, "%s%s%s: agent failed to cancel secrets: %s",
 		            NM_PRINT_FMT_QUOTED (description, "(", description, ")", "???"),
 		            error->message);
-		g_clear_error (&error);
 	}
-
-	g_free (description);
 }
 
 static void
@@ -426,11 +433,16 @@ do_cancel_secrets (NMSecretAgent *self, NMSecretAgentCallId *r, gboolean disposi
 	if (   r->is_get_secrets
 	    && priv->proxy) {
 		/* for GetSecrets call, we must cancel the request. */
-		nmdbus_secret_agent_call_cancel_get_secrets (priv->proxy,
-		                                             r->path, r->setting_name,
-		                                             NULL,
-		                                             cancel_done,
-		                                             g_strdup (nm_secret_agent_get_description (self)));
+		g_dbus_proxy_call (G_DBUS_PROXY (priv->proxy),
+		                   "CancelGetSecrets",
+		                   g_variant_new ("(os)",
+		                                  r->path,
+		                                  r->setting_name),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   NULL,
+		                   cancel_done,
+		                   g_strdup (nm_secret_agent_get_description (self)));
 	}
 
 	cancellable = r->cancellable;
@@ -494,11 +506,11 @@ agent_save_cb (GObject *proxy,
 	NMSecretAgentCallId *r = user_data;
 
 	if (request_check_return (r)) {
-		NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (r->agent);
 		gs_free_error GError *error = NULL;
+		gs_unref_variant GVariant *ret = NULL;
 
-		nmdbus_secret_agent_call_save_secrets_finish (priv->proxy, result, &error);
-		if (error)
+		ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result, G_VARIANT_TYPE ("()"), &error);
+		if (!ret)
 			g_dbus_error_strip_remote_error (error);
 		r->callback (r->agent, r, NULL, error, r->callback_data);
 	}
@@ -527,11 +539,16 @@ nm_secret_agent_save_secrets (NMSecretAgent *self,
 	dict = nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_ALL);
 
 	r = request_new (self, "SaveSecrets", path, NULL, callback, callback_data);
-	nmdbus_secret_agent_call_save_secrets (priv->proxy,
-	                                       dict,
-	                                       path,
-	                                       NULL, /* cancelling the request does *not* cancel the D-Bus call. */
-	                                       agent_save_cb, r);
+	g_dbus_proxy_call (priv->proxy,
+	                   "SaveSecrets",
+	                   g_variant_new ("(@a{sa{sv}}o)",
+	                                  dict,
+	                                  path),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   NULL, /* cancelling the request does *not* cancel the D-Bus call. */
+	                   agent_save_cb,
+	                   r);
 
 	return r;
 }
@@ -546,11 +563,11 @@ agent_delete_cb (GObject *proxy,
 	NMSecretAgentCallId *r = user_data;
 
 	if (request_check_return (r)) {
-		NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (r->agent);
 		gs_free_error GError *error = NULL;
+		gs_unref_variant GVariant *ret = NULL;
 
-		nmdbus_secret_agent_call_delete_secrets_finish (priv->proxy, result, &error);
-		if (error)
+		ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result, G_VARIANT_TYPE ("()"), &error);
+		if (!ret)
 			g_dbus_error_strip_remote_error (error);
 		r->callback (r->agent, r, NULL, error, r->callback_data);
 	}
@@ -579,12 +596,16 @@ nm_secret_agent_delete_secrets (NMSecretAgent *self,
 	dict = nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_NO_SECRETS);
 
 	r = request_new (self, "DeleteSecrets", path, NULL, callback, callback_data);
-	nmdbus_secret_agent_call_delete_secrets (priv->proxy,
-	                                         dict,
-	                                         path,
-	                                         NULL, /* cancelling the request does *not* cancel the D-Bus call. */
-	                                         agent_delete_cb, r);
-
+	g_dbus_proxy_call (priv->proxy,
+	                   "DeleteSecrets",
+	                   g_variant_new ("(@a{sa{sv}}o)",
+	                                  dict,
+	                                  path),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   NULL, /* cancelling the request does *not* cancel the D-Bus call. */
+	                   agent_delete_cb,
+	                   r);
 	return r;
 }
 
@@ -610,7 +631,7 @@ _on_disconnected_cleanup (NMSecretAgentPrivate *priv)
 }
 
 static void
-_on_disconnected_private_connection (NMBusManager *mgr,
+_on_disconnected_private_connection (NMDBusManager *mgr,
                                      GDBusConnection *connection,
                                      NMSecretAgent *self)
 {
@@ -666,7 +687,6 @@ nm_secret_agent_new (GDBusMethodInvocation *context,
 	NMSecretAgentPrivate *priv;
 	const char *dbus_owner;
 	struct passwd *pw;
-	GDBusProxy *proxy;
 	char *owner_username = NULL;
 	char *description = NULL;
 	char buf_subject[64];
@@ -695,9 +715,9 @@ nm_secret_agent_new (GDBusMethodInvocation *context,
 
 	priv = NM_SECRET_AGENT_GET_PRIVATE (self);
 
-	priv->bus_mgr = g_object_ref (nm_bus_manager_get ());
+	priv->bus_mgr = g_object_ref (nm_dbus_manager_get ());
 	priv->connection = g_object_ref (connection);
-	priv->connection_is_private = !!nm_bus_manager_connection_get_private_name (priv->bus_mgr, connection);
+	priv->connection_is_private = !!nm_dbus_manager_connection_get_private_name (priv->bus_mgr, connection);
 
 	_LOGt ("constructed: %s, owner=%s%s%s (%s), private-connection=%d, unique-name=%s%s%s, capabilities=%s",
 	       (description = _create_description (dbus_owner, identifier, uid)),
@@ -714,20 +734,18 @@ nm_secret_agent_new (GDBusMethodInvocation *context,
 	priv->capabilities = capabilities;
 	priv->subject = g_object_ref (subject);
 
-	proxy = nm_bus_manager_new_proxy (priv->bus_mgr,
-	                                  priv->connection,
-	                                  NMDBUS_TYPE_SECRET_AGENT_PROXY,
-	                                  priv->dbus_owner,
-	                                  NM_DBUS_PATH_SECRET_AGENT,
-	                                  NM_DBUS_INTERFACE_SECRET_AGENT);
-	g_assert (proxy);
-	priv->proxy = NMDBUS_SECRET_AGENT (proxy);
+	priv->proxy = nm_dbus_manager_new_proxy (priv->bus_mgr,
+	                                         priv->connection,
+	                                         G_TYPE_DBUS_PROXY,
+	                                         priv->dbus_owner,
+	                                         NM_DBUS_PATH_SECRET_AGENT,
+	                                         NM_DBUS_INTERFACE_SECRET_AGENT);
 
 	/* 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_BUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED,
+		                                             NM_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED,
 		                                             G_CALLBACK (_on_disconnected_private_connection),
 		                                             self);
 	} else {
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 58626372..0b998372 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -25,13 +25,14 @@
 
 #include <string.h>
 
-#include "nm-utils/c-list.h"
+#include "c-list/src/c-list.h"
 
 #include "nm-common-macros.h"
 #include "nm-config.h"
 #include "nm-config-data.h"
 #include "nm-dbus-interface.h"
 #include "nm-session-monitor.h"
+#include "nm-auth-manager.h"
 #include "nm-auth-utils.h"
 #include "nm-auth-subject.h"
 #include "nm-agent-manager.h"
@@ -39,8 +40,6 @@
 #include "nm-core-internal.h"
 #include "nm-audit-manager.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Settings.Connection.h"
-
 #define SETTINGS_TIMESTAMPS_FILE  NMSTATEDIR "/timestamps"
 #define SETTINGS_SEEN_BSSIDS_FILE NMSTATEDIR "/seen-bssids"
 
@@ -60,9 +59,9 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingsConnection,
 );
 
 enum {
-	UPDATED,
 	REMOVED,
 	UPDATED_INTERNAL,
+	FLAGS_CHANGED,
 	LAST_SIGNAL
 };
 
@@ -74,7 +73,7 @@ typedef struct _NMSettingsConnectionPrivate {
 	NMSessionMonitor *session_monitor;
 	gulong session_changed_id;
 
-	NMSettingsConnectionFlags flags:5;
+	NMSettingsConnectionIntFlags flags:5;
 
 	bool removed:1;
 	bool ready:1;
@@ -83,7 +82,8 @@ typedef struct _NMSettingsConnectionPrivate {
 
 	NMSettingsAutoconnectBlockedReason autoconnect_blocked_reason:4;
 
-	GSList *pending_auths; /* List of pending authentication requests */
+	/* List of pending authentication requests */
+	CList auth_lst_head;
 
 	CList call_ids_lst_head; /* in-progress secrets requests */
 
@@ -115,7 +115,7 @@ typedef struct _NMSettingsConnectionPrivate {
 
 } NMSettingsConnectionPrivate;
 
-G_DEFINE_TYPE_WITH_CODE (NMSettingsConnection, nm_settings_connection, NM_TYPE_EXPORTED_OBJECT,
+G_DEFINE_TYPE_WITH_CODE (NMSettingsConnection, nm_settings_connection, NM_TYPE_DBUS_OBJECT,
                          G_IMPLEMENT_INTERFACE (NM_TYPE_CONNECTION, nm_settings_connection_connection_interface_init)
                          )
 
@@ -146,12 +146,9 @@ G_DEFINE_TYPE_WITH_CODE (NMSettingsConnection, nm_settings_connection, NM_TYPE_E
 
 /*****************************************************************************/
 
-static void
-_emit_updated (NMSettingsConnection *self, gboolean by_user)
-{
-	g_signal_emit (self, signals[UPDATED], 0);
-	g_signal_emit (self, signals[UPDATED_INTERNAL], 0, by_user);
-}
+static const GDBusSignalInfo signal_info_updated;
+static const GDBusSignalInfo signal_info_removed;
+static const NMDBusInterfaceInfoExtended interface_info_settings_connection;
 
 /*****************************************************************************/
 
@@ -320,7 +317,7 @@ static void
 set_visible (NMSettingsConnection *self, gboolean new_visible)
 {
 	nm_settings_connection_set_flags (self,
-	                                  NM_SETTINGS_CONNECTION_FLAGS_VISIBLE,
+	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE,
 	                                  new_visible);
 }
 
@@ -390,7 +387,7 @@ nm_settings_connection_check_permission (NMSettingsConnection *self,
 	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 
 	if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (self),
-	                   NM_SETTINGS_CONNECTION_FLAGS_VISIBLE))
+	                   NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE))
 		return FALSE;
 
 	s_con = nm_connection_get_setting_connection (NM_CONNECTION (self));
@@ -490,30 +487,30 @@ secrets_cleared_cb (NMSettingsConnection *self)
 static void
 set_persist_mode (NMSettingsConnection *self, NMSettingsConnectionPersistMode persist_mode)
 {
-	NMSettingsConnectionFlags flags = NM_SETTINGS_CONNECTION_FLAGS_NONE;
-	const NMSettingsConnectionFlags ALL =   NM_SETTINGS_CONNECTION_FLAGS_UNSAVED
-	                                      | NM_SETTINGS_CONNECTION_FLAGS_NM_GENERATED
-	                                      | NM_SETTINGS_CONNECTION_FLAGS_VOLATILE;
+	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;
 
 	switch (persist_mode) {
 	case NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK:
-		flags = NM_SETTINGS_CONNECTION_FLAGS_NONE;
+		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_FLAGS_UNSAVED;
+		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_FLAGS_UNSAVED |
-		        NM_SETTINGS_CONNECTION_FLAGS_VOLATILE;
+		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_FLAGS_UNSAVED,
+		                                  NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED,
 		                                  TRUE);
 		return;
 	case NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP:
@@ -526,6 +523,16 @@ set_persist_mode (NMSettingsConnection *self, NMSettingsConnectionPersistMode pe
 }
 
 static void
+_emit_updated (NMSettingsConnection *self, gboolean by_user)
+{
+	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);
+}
+
+static void
 connection_changed_cb (NMSettingsConnection *self, gpointer unused)
 {
 	set_persist_mode (self, NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED);
@@ -571,17 +578,13 @@ _update_prepare (NMSettingsConnection *self,
                  NMConnection *new_connection,
                  GError **error)
 {
-	NMSettingsConnectionPrivate *priv;
-
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
 	g_return_val_if_fail (NM_IS_CONNECTION (new_connection), FALSE);
 
-	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-
 	if (!nm_connection_normalize (new_connection, NULL, NULL, error))
 		return FALSE;
 
-	if (   nm_connection_get_path (NM_CONNECTION (self))
+	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,
@@ -661,8 +664,10 @@ nm_settings_connection_update (NMSettingsConnection *self,
 	                               NM_SETTING_COMPARE_FLAG_EXACT)) {
 		gs_unref_object NMConnection *simple = NULL;
 
-		if (log_diff_name)
-			nm_utils_log_connection_diff (replace_connection, NM_CONNECTION (self), LOGL_DEBUG, LOGD_CORE, log_diff_name, "++ ");
+		if (log_diff_name) {
+			nm_utils_log_connection_diff (replace_connection, NM_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
@@ -679,7 +684,7 @@ nm_settings_connection_update (NMSettingsConnection *self,
 	}
 
 	nm_settings_connection_set_flags (self,
-	                                  NM_SETTINGS_CONNECTION_FLAGS_NM_GENERATED | NM_SETTINGS_CONNECTION_FLAGS_VOLATILE,
+	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
 	                                  FALSE);
 
 	if (replaced) {
@@ -798,7 +803,7 @@ nm_settings_connection_delete (NMSettingsConnection *self,
 	for_agents = nm_simple_connection_new_clone (NM_CONNECTION (self));
 	nm_connection_clear_secrets (for_agents);
 	nm_agent_manager_delete_secrets (priv->agent_mgr,
-	                                 nm_connection_get_path (NM_CONNECTION (self)),
+	                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
 	                                 for_agents);
 	g_object_unref (for_agents);
 
@@ -1295,7 +1300,7 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
 	priv->last_secret_agent_version_id = nm_agent_manager_get_agent_version_id (priv->agent_mgr);
 
 	call_id_a = nm_agent_manager_get_secrets (priv->agent_mgr,
-	                                          nm_connection_get_path (NM_CONNECTION (self)),
+	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
 	                                          NM_CONNECTION (self),
 	                                          subject,
 	                                          existing_secrets,
@@ -1359,7 +1364,7 @@ nm_settings_connection_cancel_secrets (NMSettingsConnection *self,
 	_get_secrets_cancel (self, call_id, FALSE);
 }
 
-/**** User authorization **************************************/
+/*****************************************************************************/
 
 typedef void (*AuthCallback) (NMSettingsConnection *self,
                               GDBusMethodInvocation *context,
@@ -1367,46 +1372,61 @@ typedef void (*AuthCallback) (NMSettingsConnection *self,
                               GError *error,
                               gpointer data);
 
-static void
-pk_auth_cb (NMAuthChain *chain,
-            GError *chain_error,
-            GDBusMethodInvocation *context,
-            gpointer user_data)
-{
-	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (user_data);
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	GError *error = NULL;
-	NMAuthCallResult result;
-	const char *perm;
+typedef struct {
+	CList auth_lst;
+	NMAuthManagerCallId *call_id;
+	NMSettingsConnection *self;
 	AuthCallback callback;
 	gpointer callback_data;
+	GDBusMethodInvocation *invocation;
 	NMAuthSubject *subject;
+} AuthData;
 
-	priv->pending_auths = g_slist_remove (priv->pending_auths, chain);
+static void
+pk_auth_cb (NMAuthManager *auth_manager,
+            NMAuthManagerCallId *auth_call_id,
+            gboolean is_authorized,
+            gboolean is_challenge,
+            GError *auth_error,
+            gpointer user_data)
+{
+	AuthData *auth_data = user_data;
+	NMSettingsConnection *self;
+	gs_free_error GError *error = NULL;
+
+	nm_assert (auth_data);
+	nm_assert (NM_IS_SETTINGS_CONNECTION (auth_data->self));
+
+	self = auth_data->self;
+
+	auth_data->call_id = NULL;
 
-	perm = nm_auth_chain_get_data (chain, "perm");
-	g_assert (perm);
-	result = nm_auth_chain_get_result (chain, perm);
+	c_list_unlink (&auth_data->auth_lst);
 
-	/* If our NMSettingsConnection is already gone, do nothing */
-	if (chain_error) {
+	if (g_error_matches (auth_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		error = g_error_new (NM_SETTINGS_ERROR,
+		                     NM_SETTINGS_ERROR_FAILED,
+		                     "Error checking authorization: connection was deleted");
+	} else if (auth_error) {
 		error = g_error_new (NM_SETTINGS_ERROR,
 		                     NM_SETTINGS_ERROR_FAILED,
 		                     "Error checking authorization: %s",
-		                     chain_error->message ? chain_error->message : "(unknown)");
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+		                     auth_error->message);
+	} else if (nm_auth_call_result_eval (is_authorized, is_challenge, auth_error) != NM_AUTH_CALL_RESULT_YES) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             "Insufficient privileges.");
+		                             "Insufficient privileges");
 	}
 
-	callback = nm_auth_chain_get_data (chain, "callback");
-	callback_data = nm_auth_chain_get_data (chain, "callback-data");
-	subject = nm_auth_chain_get_data (chain, "subject");
-	callback (self, context, subject, error, callback_data);
+	auth_data->callback (self,
+	                     auth_data->invocation,
+	                     auth_data->subject,
+	                     error,
+	                     auth_data->callback_data);
 
-	g_clear_error (&error);
-	nm_auth_chain_unref (chain);
+	g_object_unref (auth_data->invocation);
+	g_object_unref (auth_data->subject);
+	g_slice_free (AuthData, auth_data);
 }
 
 /**
@@ -1434,59 +1454,52 @@ _new_auth_subject (GDBusMethodInvocation *context, GError **error)
 	return subject;
 }
 
+/* may either invoke callback synchronously or asynchronously. */
 static void
 auth_start (NMSettingsConnection *self,
-            GDBusMethodInvocation *context,
+            GDBusMethodInvocation *invocation,
             NMAuthSubject *subject,
             const char *check_permission,
             AuthCallback callback,
             gpointer callback_data)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	NMAuthChain *chain;
+	AuthData *auth_data;
 	GError *error = NULL;
-	char *error_desc = NULL;
-
-	g_return_if_fail (context != NULL);
-	g_return_if_fail (NM_IS_AUTH_SUBJECT (subject));
 
-	/* Ensure the caller can view this connection */
-	if (!nm_auth_is_subject_in_acl (NM_CONNECTION (self),
-	                                subject,
-	                                &error_desc)) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             error_desc);
-		g_free (error_desc);
+	nm_assert (nm_dbus_object_is_exported (NM_DBUS_OBJECT (self)));
+	nm_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+	nm_assert (NM_IS_AUTH_SUBJECT (subject));
 
-		callback (self, context, subject, error, callback_data);
+	if (!nm_auth_is_subject_in_acl_set_error (NM_CONNECTION (self),
+	                                          subject,
+	                                          NM_SETTINGS_ERROR,
+	                                          NM_SETTINGS_ERROR_PERMISSION_DENIED,
+	                                          &error)) {
+		callback (self, invocation, subject, error, callback_data);
 		g_clear_error (&error);
 		return;
 	}
 
 	if (!check_permission) {
 		/* Don't need polkit auth, automatic success */
-		callback (self, context, subject, NULL, callback_data);
-		return;
-	}
-
-	chain = nm_auth_chain_new_subject (subject, context, pk_auth_cb, self);
-	if (!chain) {
-		g_set_error_literal (&error,
-		                     NM_SETTINGS_ERROR,
-		                     NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                     "Unable to authenticate the request.");
-		callback (self, context, subject, error, callback_data);
-		g_clear_error (&error);
+		callback (self, invocation, subject, NULL, callback_data);
 		return;
 	}
 
-	priv->pending_auths = g_slist_append (priv->pending_auths, chain);
-	nm_auth_chain_set_data (chain, "perm", (gpointer) check_permission, NULL);
-	nm_auth_chain_set_data (chain, "callback", callback, NULL);
-	nm_auth_chain_set_data (chain, "callback-data", callback_data, NULL);
-	nm_auth_chain_set_data (chain, "subject", g_object_ref (subject), g_object_unref);
-	nm_auth_chain_add_call (chain, check_permission, TRUE);
+	auth_data = g_slice_new (AuthData);
+	auth_data->self = self;
+	auth_data->callback = callback;
+	auth_data->callback_data = callback_data;
+	auth_data->invocation = g_object_ref (invocation);
+	auth_data->subject = g_object_ref (subject);
+	c_list_link_tail (&priv->auth_lst_head, &auth_data->auth_lst);
+	auth_data->call_id = nm_auth_manager_check_authorization (nm_auth_manager_get (),
+	                                                          subject,
+	                                                          check_permission,
+	                                                          TRUE,
+	                                                          pk_auth_cb,
+	                                                          auth_data);
 }
 
 /**** DBus method handlers ************************************/
@@ -1523,7 +1536,7 @@ check_writable (NMConnection *self, GError **error)
 }
 
 static void
-get_settings_auth_cb (NMSettingsConnection *self, 
+get_settings_auth_cb (NMSettingsConnection *self,
                       GDBusMethodInvocation *context,
                       NMAuthSubject *subject,
                       GError *error,
@@ -1577,18 +1590,25 @@ get_settings_auth_cb (NMSettingsConnection *self,
 }
 
 static void
-impl_settings_connection_get_settings (NMSettingsConnection *self,
-                                       GDBusMethodInvocation *context)
-{
-	NMAuthSubject *subject;
+impl_settings_connection_get_settings (NMDBusObject *obj,
+                                       const NMDBusInterfaceInfoExtended *interface_info,
+                                       const NMDBusMethodInfoExtended *method_info,
+                                       GDBusConnection *connection,
+                                       const char *sender,
+                                       GDBusMethodInvocation *invocation,
+                                       GVariant *parameters)
+{
+	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (obj);
+	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
 
-	subject = _new_auth_subject (context, &error);
-	if (subject) {
-		auth_start (self, context, subject, NULL, get_settings_auth_cb, NULL);
-		g_object_unref (subject);
-	} else
-		g_dbus_method_invocation_take_error (context, error);
+	subject = _new_auth_subject (invocation, &error);
+	if (!subject) {
+		g_dbus_method_invocation_take_error (invocation, error);
+		return;
+	}
+
+	auth_start (self, invocation, subject, NULL, get_settings_auth_cb, NULL);
 }
 
 typedef struct {
@@ -1782,7 +1802,7 @@ update_auth_cb (NMSettingsConnection *self,
 		                                        secrets_filter_cb,
 		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
 		nm_agent_manager_save_secrets (info->agent_mgr,
-		                               nm_connection_get_path (NM_CONNECTION (self)),
+		                               nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
 		                               for_agent,
 		                               info->subject);
 	}
@@ -1830,7 +1850,6 @@ settings_connection_update (NMSettingsConnection *self,
 	GError *error = NULL;
 	UpdateInfo *info;
 	const char *permission;
-	char *error_desc = NULL;
 
 	/* 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)
@@ -1867,15 +1886,12 @@ settings_connection_update (NMSettingsConnection *self,
 	 * that's sending the update request.  You can't make a connection
 	 * invisible to yourself.
 	 */
-	if (!nm_auth_is_subject_in_acl (tmp ? tmp : NM_CONNECTION (self),
-	                                subject,
-	                                &error_desc)) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             error_desc);
-		g_free (error_desc);
+	if (!nm_auth_is_subject_in_acl_set_error (tmp ? tmp : NM_CONNECTION (self),
+	                                          subject,
+	                                          NM_SETTINGS_ERROR,
+	                                          NM_SETTINGS_ERROR_PERMISSION_DENIED,
+	                                          &error))
 		goto error;
-	}
 
 	info = g_slice_new0 (UpdateInfo);
 	info->is_update2 = is_update2;
@@ -1901,54 +1917,87 @@ error:
 }
 
 static void
-impl_settings_connection_update (NMSettingsConnection *self,
-                                 GDBusMethodInvocation *context,
-                                 GVariant *new_settings)
+impl_settings_connection_update (NMDBusObject *obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *method_info,
+                                 GDBusConnection *connection,
+                                 const char *sender,
+                                 GDBusMethodInvocation *invocation,
+                                 GVariant *parameters)
 {
-	settings_connection_update (self, FALSE, context, new_settings, NM_SETTINGS_UPDATE2_FLAG_TO_DISK);
+	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (obj);
+	gs_unref_variant GVariant *settings = NULL;
+
+	g_variant_get (parameters, "(@a{sa{sv}})", &settings);
+	settings_connection_update (self, FALSE, invocation, settings, NM_SETTINGS_UPDATE2_FLAG_TO_DISK);
 }
 
 static void
-impl_settings_connection_update_unsaved (NMSettingsConnection *self,
-                                         GDBusMethodInvocation *context,
-                                         GVariant *new_settings)
+impl_settings_connection_update_unsaved (NMDBusObject *obj,
+                                         const NMDBusInterfaceInfoExtended *interface_info,
+                                         const NMDBusMethodInfoExtended *method_info,
+                                         GDBusConnection *connection,
+                                         const char *sender,
+                                         GDBusMethodInvocation *invocation,
+                                         GVariant *parameters)
 {
-	settings_connection_update (self, FALSE, context, new_settings, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY);
+	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (obj);
+	gs_unref_variant GVariant *settings = NULL;
+
+	g_variant_get (parameters, "(@a{sa{sv}})", &settings);
+	settings_connection_update (self, FALSE, invocation, settings, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY);
 }
 
 static void
-impl_settings_connection_save (NMSettingsConnection *self,
-                               GDBusMethodInvocation *context)
+impl_settings_connection_save (NMDBusObject *obj,
+                               const NMDBusInterfaceInfoExtended *interface_info,
+                               const NMDBusMethodInfoExtended *method_info,
+                               GDBusConnection *connection,
+                               const char *sender,
+                               GDBusMethodInvocation *invocation,
+                               GVariant *parameters)
 {
-	settings_connection_update (self, FALSE, context, NULL, NM_SETTINGS_UPDATE2_FLAG_TO_DISK);
+	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (obj);
+
+	settings_connection_update (self, FALSE, invocation, NULL, NM_SETTINGS_UPDATE2_FLAG_TO_DISK);
 }
 
 static void
-impl_settings_connection_update2 (NMSettingsConnection *self,
-                                  GDBusMethodInvocation *context,
-                                  GVariant *settings,
-                                  guint32 flags_u,
-                                  GVariant *args)
-{
+impl_settings_connection_update2 (NMDBusObject *obj,
+                                  const NMDBusInterfaceInfoExtended *interface_info,
+                                  const NMDBusMethodInfoExtended *method_info,
+                                  GDBusConnection *connection,
+                                  const char *sender,
+                                  GDBusMethodInvocation *invocation,
+                                  GVariant *parameters)
+{
+	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (obj);
+	gs_unref_variant GVariant *settings = NULL;
+	gs_unref_variant GVariant *args = NULL;
+	guint32 flags_u;
 	GError *error = NULL;
 	GVariantIter iter;
 	const char *args_name;
-	const NMSettingsUpdate2Flags flags = (NMSettingsUpdate2Flags) flags_u;
+	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)))) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
 		                             "Unknown flags");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_take_error (invocation, error);
 		return;
 	}
 
+	flags = (NMSettingsUpdate2Flags) flags_u;
+
 	if (   (   NM_FLAGS_ANY (flags, ALL_PERSIST_MODES)
 	        && !nm_utils_is_power_of_two (flags & ALL_PERSIST_MODES))
 	    || (   NM_FLAGS_HAS (flags, NM_SETTINGS_UPDATE2_FLAG_VOLATILE)
@@ -1957,7 +2006,7 @@ impl_settings_connection_update2 (NMSettingsConnection *self,
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
 		                             "Conflicting flags");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_take_error (invocation, error);
 		return;
 	}
 
@@ -1965,7 +2014,7 @@ impl_settings_connection_update2 (NMSettingsConnection *self,
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
 		                             "args is of invalid type");
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_take_error (invocation, error);
 		return;
 	}
 
@@ -1974,13 +2023,13 @@ impl_settings_connection_update2 (NMSettingsConnection *self,
 		error = g_error_new (NM_SETTINGS_ERROR,
 		                     NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
 		                     "Unsupported argument '%s'", args_name);
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_take_error (invocation, error);
 		return;
 	}
 
 	settings_connection_update (self,
 	                            TRUE,
-	                            context,
+	                            invocation,
 	                            settings,
 	                            flags);
 }
@@ -2025,7 +2074,7 @@ get_modify_permission_basic (NMSettingsConnection *self)
 	 * request affects more than just the caller, require 'modify.system'.
 	 */
 	s_con = nm_connection_get_setting_connection (NM_CONNECTION (self));
-	g_assert (s_con);
+	nm_assert (s_con);
 	if (nm_setting_connection_get_num_permissions (s_con) == 1)
 		return NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
 
@@ -2033,26 +2082,30 @@ get_modify_permission_basic (NMSettingsConnection *self)
 }
 
 static void
-impl_settings_connection_delete (NMSettingsConnection *self,
-                                 GDBusMethodInvocation *context)
-{
-	NMAuthSubject *subject = NULL;
+impl_settings_connection_delete (NMDBusObject *obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *method_info,
+                                 GDBusConnection *connection,
+                                 const char *sender,
+                                 GDBusMethodInvocation *invocation,
+                                 GVariant *parameters)
+{
+	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (obj);
+	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
 
 	if (!check_writable (NM_CONNECTION (self), &error))
-		goto out_err;
+		goto err;
 
-	subject = _new_auth_subject (context, &error);
-	if (subject) {
-		auth_start (self, context, subject, get_modify_permission_basic (self), delete_auth_cb, NULL);
-		g_object_unref (subject);
-	} else
-		goto out_err;
+	subject = _new_auth_subject (invocation, &error);
+	if (!subject)
+		goto err;
 
+	auth_start (self, invocation, subject, get_modify_permission_basic (self), delete_auth_cb, NULL);
 	return;
-out_err:
+err:
 	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_DELETE, self, FALSE, NULL, subject, error->message);
-	g_dbus_method_invocation_take_error (context, error);
+	g_dbus_method_invocation_take_error (invocation, error);
 }
 
 /*****************************************************************************/
@@ -2111,24 +2164,33 @@ dbus_get_secrets_auth_cb (NMSettingsConnection *self,
 }
 
 static void
-impl_settings_connection_get_secrets (NMSettingsConnection *self,
-                                      GDBusMethodInvocation *context,
-                                      const gchar *setting_name)
-{
-	NMAuthSubject *subject;
+impl_settings_connection_get_secrets (NMDBusObject *obj,
+                                      const NMDBusInterfaceInfoExtended *interface_info,
+                                      const NMDBusMethodInfoExtended *method_info,
+                                      GDBusConnection *connection,
+                                      const char *sender,
+                                      GDBusMethodInvocation *invocation,
+                                      GVariant *parameters)
+{
+	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (obj);
+	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
+	const char *setting_name;
 
-	subject = _new_auth_subject (context, &error);
-	if (subject) {
-		auth_start (self,
-		            context,
-		            subject,
-		            get_modify_permission_basic (self),
-		            dbus_get_secrets_auth_cb,
-		            g_strdup (setting_name));
-		g_object_unref (subject);
-	} else
-		g_dbus_method_invocation_take_error (context, error);
+	subject = _new_auth_subject (invocation, &error);
+	if (!subject) {
+		g_dbus_method_invocation_take_error (invocation, error);
+		return;
+	}
+
+	g_variant_get (parameters, "(&s)", &setting_name);
+
+	auth_start (self,
+	            invocation,
+	            subject,
+	            get_modify_permission_basic (self),
+	            dbus_get_secrets_auth_cb,
+	            g_strdup (setting_name));
 }
 
 static void
@@ -2157,7 +2219,7 @@ dbus_clear_secrets_auth_cb (NMSettingsConnection *self,
 
 	/* Tell agents to remove secrets for this connection */
 	nm_agent_manager_delete_secrets (priv->agent_mgr,
-	                                 nm_connection_get_path (NM_CONNECTION (self)),
+	                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
 	                                 NM_CONNECTION (self));
 
 	nm_settings_connection_update (self,
@@ -2177,26 +2239,31 @@ dbus_clear_secrets_auth_cb (NMSettingsConnection *self,
 }
 
 static void
-impl_settings_connection_clear_secrets (NMSettingsConnection *self,
-                                        GDBusMethodInvocation *context)
-{
-	NMAuthSubject *subject;
+impl_settings_connection_clear_secrets (NMDBusObject *obj,
+                                        const NMDBusInterfaceInfoExtended *interface_info,
+                                        const NMDBusMethodInfoExtended *method_info,
+                                        GDBusConnection *connection,
+                                        const char *sender,
+                                        GDBusMethodInvocation *invocation,
+                                        GVariant *parameters)
+{
+	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (obj);
+	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
 
-	subject = _new_auth_subject (context, &error);
-	if (subject) {
-		auth_start (self,
-		            context,
-		            subject,
-		            get_modify_permission_basic (self),
-		            dbus_clear_secrets_auth_cb,
-		            NULL);
-		g_object_unref (subject);
-	} else {
+	subject = _new_auth_subject (invocation, &error);
+	if (!subject) {
 		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_CLEAR_SECRETS, self,
 		                            FALSE, NULL, NULL, error->message);
-		g_dbus_method_invocation_take_error (context, error);
+		g_dbus_method_invocation_take_error (invocation, error);
+		return;
 	}
+	auth_start (self,
+	            invocation,
+	            subject,
+	            get_modify_permission_basic (self),
+	            dbus_clear_secrets_auth_cb,
+	            NULL);
 }
 
 /*****************************************************************************/
@@ -2216,55 +2283,64 @@ void
 nm_settings_connection_signal_remove (NMSettingsConnection *self)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	AuthData *auth_data;
 
 	if (priv->removed)
 		return;
 	priv->removed = TRUE;
-	g_signal_emit_by_name (self, NM_SETTINGS_CONNECTION_REMOVED);
+
+	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)
 {
-	return NM_FLAGS_HAS (nm_settings_connection_get_flags (self), NM_SETTINGS_CONNECTION_FLAGS_UNSAVED);
+	return NM_FLAGS_HAS (nm_settings_connection_get_flags (self), NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED);
 }
 
 /*****************************************************************************/
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_settings_connection_flags_to_string, NMSettingsConnectionFlags,
-	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_FLAGS_NONE,          "none"),
-	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_FLAGS_UNSAVED,       "unsaved"),
-	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_FLAGS_NM_GENERATED,  "nm-generated"),
-	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_FLAGS_VOLATILE,      "volatile"),
-	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_FLAGS_VISIBLE,       "visible"),
+NM_UTILS_FLAGS2STR_DEFINE_STATIC (_settings_connection_flags_to_string, NMSettingsConnectionIntFlags,
+	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,          "none"),
+	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED,       "unsaved"),
+	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,  "nm-generated"),
+	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,      "volatile"),
+	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE,       "visible"),
 );
 
-NMSettingsConnectionFlags
+NMSettingsConnectionIntFlags
 nm_settings_connection_get_flags (NMSettingsConnection *self)
 {
-	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), NM_SETTINGS_CONNECTION_FLAGS_NONE);
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), NM_SETTINGS_CONNECTION_INT_FLAGS_NONE);
 
 	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->flags;
 }
 
-NMSettingsConnectionFlags
-nm_settings_connection_set_flags (NMSettingsConnection *self, NMSettingsConnectionFlags flags, gboolean set)
+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_FLAGS_NONE);
+	                                              set ? flags : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE);
 }
 
-NMSettingsConnectionFlags
+NMSettingsConnectionIntFlags
 nm_settings_connection_set_flags_full (NMSettingsConnection *self,
-                                       NMSettingsConnectionFlags mask,
-                                       NMSettingsConnectionFlags value)
+                                       NMSettingsConnectionIntFlags mask,
+                                       NMSettingsConnectionIntFlags value)
 {
 	NMSettingsConnectionPrivate *priv;
-	NMSettingsConnectionFlags old_flags;
+	NMSettingsConnectionIntFlags old_flags;
 
-	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), NM_SETTINGS_CONNECTION_FLAGS_NONE);
-	nm_assert (mask && !NM_FLAGS_ANY (mask, ~NM_SETTINGS_CONNECTION_FLAGS_ALL));
+	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 (value, ~mask));
 
 	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
@@ -2273,6 +2349,7 @@ nm_settings_connection_set_flags_full (NMSettingsConnection *self,
 
 	old_flags = priv->flags;
 	if (old_flags != value) {
+		gboolean notify_unsaved = FALSE;
 		char buf1[255], buf2[255];
 
 		_LOGT ("update settings-connection flags to %s (was %s)",
@@ -2280,9 +2357,17 @@ nm_settings_connection_set_flags_full (NMSettingsConnection *self,
 		       _settings_connection_flags_to_string (priv->flags, buf2, sizeof (buf2)));
 		priv->flags = value;
 		nm_assert (priv->flags == value);
-		_notify (self, PROP_FLAGS);
-		if (NM_FLAGS_HAS (old_flags, NM_SETTINGS_CONNECTION_FLAGS_UNSAVED) != NM_FLAGS_HAS (value, NM_SETTINGS_CONNECTION_FLAGS_UNSAVED))
+
+		if (NM_FLAGS_HAS (old_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED) != NM_FLAGS_HAS (value, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED)) {
+			g_object_freeze_notify (G_OBJECT (self));
 			_notify (self, PROP_UNSAVED);
+			notify_unsaved = TRUE;
+		}
+		_notify (self, PROP_FLAGS);
+		if (notify_unsaved)
+			g_object_thaw_notify (G_OBJECT (self));
+
+		g_signal_emit (self, signals[FLAGS_CHANGED], 0);
 	}
 	return old_flags;
 }
@@ -2703,7 +2788,7 @@ _autoconnect_retries_set (NMSettingsConnection *self,
 	if (retries)
 		priv->autoconnect_retries_blocked_until = 0;
 	else {
-		/* XXX: the blocked time must be identical for all connections, otherwise
+		/* NOTE: the blocked time must be identical for all connections, otherwise
 		 * the tracking of resetting the retry count in NMPolicy needs adjustment
 		 * in _connection_autoconnect_retries_set() (as it would need to re-evaluate
 		 * the next-timeout everytime a connection gets blocked). */
@@ -2798,7 +2883,7 @@ gboolean
 nm_settings_connection_autoconnect_is_blocked (NMSettingsConnection *self)
 {
 	NMSettingsConnectionPrivate *priv;
-	NMSettingsConnectionFlags flags;
+	NMSettingsConnectionIntFlags flags;
 
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), TRUE);
 
@@ -2810,9 +2895,9 @@ nm_settings_connection_autoconnect_is_blocked (NMSettingsConnection *self)
 		return TRUE;
 
 	flags = priv->flags;
-	if (NM_FLAGS_HAS (flags, NM_SETTINGS_CONNECTION_FLAGS_VOLATILE))
+	if (NM_FLAGS_HAS (flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
 		return TRUE;
-	if (!NM_FLAGS_HAS (flags, NM_SETTINGS_CONNECTION_FLAGS_VISIBLE))
+	if (!NM_FLAGS_HAS (flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE))
 		return TRUE;
 
 	return FALSE;
@@ -2900,8 +2985,11 @@ nm_settings_connection_init (NMSettingsConnection *self)
 	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnectionPrivate);
 	self->_priv = priv;
 
+	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,
@@ -2937,6 +3025,9 @@ dispose (GObject *object)
 
 	_LOGD ("disposing");
 
+	nm_assert (c_list_is_empty (&self->_connections_lst));
+	nm_assert (c_list_is_empty (&priv->auth_lst_head));
+
 	/* Cancel in-progress secrets requests */
 	if (priv->agent_mgr) {
 		c_list_for_each_entry_safe (call_id, call_id_safe, &priv->call_ids_lst_head, call_ids_lst)
@@ -2954,11 +3045,7 @@ dispose (GObject *object)
 	g_clear_object (&priv->system_secrets);
 	g_clear_object (&priv->agent_secrets);
 
-	/* Cancel PolicyKit requests */
-	g_slist_free_full (priv->pending_auths, (GDestroyNotify) nm_auth_chain_unref);
-	priv->pending_auths = NULL;
-
-	g_clear_pointer (&priv->seen_bssids, (GDestroyNotify) g_hash_table_destroy);
+	g_clear_pointer (&priv->seen_bssids, g_hash_table_destroy);
 
 	set_visible (self, FALSE);
 
@@ -2986,7 +3073,8 @@ get_property (GObject *object, guint prop_id,
 		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));
+		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));
@@ -3014,22 +3102,120 @@ set_property (GObject *object, guint prop_id,
 	}
 }
 
+static const GDBusSignalInfo signal_info_updated = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"Updated",
+);
+
+static const GDBusSignalInfo signal_info_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"Removed",
+);
+
+static const NMDBusInterfaceInfoExtended interface_info_settings_connection = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_SETTINGS_CONNECTION,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Update",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("properties", "a{sa{sv}}"),
+					),
+				),
+				.handle = impl_settings_connection_update,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"UpdateUnsaved",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("properties", "a{sa{sv}}"),
+					),
+				),
+				.handle = impl_settings_connection_update_unsaved,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Delete",
+				),
+				.handle = impl_settings_connection_delete,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetSettings",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("settings", "a{sa{sv}}"),
+					),
+				),
+				.handle = impl_settings_connection_get_settings,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetSecrets",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("setting_name", "s"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("secrets", "a{sa{sv}}"),
+					),
+				),
+				.handle = impl_settings_connection_get_secrets,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"ClearSecrets",
+				),
+				.handle = impl_settings_connection_clear_secrets,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Save",
+				),
+				.handle = impl_settings_connection_save,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"Update2",
+					.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 ("result", "a{sv}"),
+					),
+				),
+				.handle = impl_settings_connection_update2,
+			),
+		),
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+			&signal_info_updated,
+			&signal_info_removed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Unsaved", "b",  NM_SETTINGS_CONNECTION_UNSAVED),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE   ("Flags",   "u",  NM_SETTINGS_CONNECTION_FLAGS),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
-nm_settings_connection_class_init (NMSettingsConnectionClass *class)
+nm_settings_connection_class_init (NMSettingsConnectionClass *klass)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS (class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 
-	g_type_class_add_private (class, sizeof (NMSettingsConnectionPrivate));
+	g_type_class_add_private (klass, sizeof (NMSettingsConnectionPrivate));
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH_SETTINGS);
+	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;
 
-	class->supports_secrets = supports_secrets;
+	klass->supports_secrets = supports_secrets;
 
 	obj_properties[PROP_UNSAVED] =
 	     g_param_spec_boolean (NM_SETTINGS_CONNECTION_UNSAVED, "", "",
@@ -3045,9 +3231,7 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *class)
 
 	obj_properties[PROP_FLAGS] =
 	     g_param_spec_uint (NM_SETTINGS_CONNECTION_FLAGS, "", "",
-	                        NM_SETTINGS_CONNECTION_FLAGS_NONE,
-	                        NM_SETTINGS_CONNECTION_FLAGS_ALL,
-	                        NM_SETTINGS_CONNECTION_FLAGS_NONE,
+	                        0, G_MAXUINT32, 0,
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
 
@@ -3060,20 +3244,10 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *class)
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-
-	signals[UPDATED] =
-	    g_signal_new (NM_SETTINGS_CONNECTION_UPDATED,
-	                  G_TYPE_FROM_CLASS (class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0,
-	                  NULL, NULL,
-	                  g_cclosure_marshal_VOID__VOID,
-	                  G_TYPE_NONE, 0);
-
 	/* internal signal, with an argument (gboolean by_user). */
 	signals[UPDATED_INTERNAL] =
 	    g_signal_new (NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
-	                  G_TYPE_FROM_CLASS (class),
+	                  G_TYPE_FROM_CLASS (klass),
 	                  G_SIGNAL_RUN_FIRST,
 	                  0, NULL, NULL,
 	                  g_cclosure_marshal_VOID__BOOLEAN,
@@ -3081,24 +3255,20 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *class)
 
 	signals[REMOVED] =
 	    g_signal_new (NM_SETTINGS_CONNECTION_REMOVED,
-	                  G_TYPE_FROM_CLASS (class),
+	                  G_TYPE_FROM_CLASS (klass),
 	                  G_SIGNAL_RUN_FIRST,
 	                  0,
 	                  NULL, NULL,
 	                  g_cclosure_marshal_VOID__VOID,
 	                  G_TYPE_NONE, 0);
 
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (class),
-	                                        NMDBUS_TYPE_SETTINGS_CONNECTION_SKELETON,
-	                                        "Update", impl_settings_connection_update,
-	                                        "UpdateUnsaved", impl_settings_connection_update_unsaved,
-	                                        "Delete", impl_settings_connection_delete,
-	                                        "GetSettings", impl_settings_connection_get_settings,
-	                                        "GetSecrets", impl_settings_connection_get_secrets,
-	                                        "ClearSecrets", impl_settings_connection_clear_secrets,
-	                                        "Save", impl_settings_connection_save,
-	                                        "Update2", impl_settings_connection_update2,
-	                                        NULL);
+	signals[FLAGS_CHANGED] =
+	    g_signal_new (NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
+	                  G_TYPE_FROM_CLASS (klass),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL,
+	                  g_cclosure_marshal_VOID__VOID,
+	                  G_TYPE_NONE, 0);
 }
 
 static void
diff --git a/src/settings/nm-settings-connection.h b/src/settings/nm-settings-connection.h
index 29ec05dd..58901699 100644
--- a/src/settings/nm-settings-connection.h
+++ b/src/settings/nm-settings-connection.h
@@ -24,7 +24,7 @@
 
 #include <net/ethernet.h>
 
-#include "nm-exported-object.h"
+#include "nm-dbus-object.h"
 #include "nm-connection.h"
 
 #define NM_TYPE_SETTINGS_CONNECTION            (nm_settings_connection_get_type ())
@@ -34,14 +34,11 @@
 #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))
 
-/* Signals */
-#define NM_SETTINGS_CONNECTION_UPDATED "updated"
 #define NM_SETTINGS_CONNECTION_REMOVED "removed"
 #define NM_SETTINGS_CONNECTION_GET_SECRETS "get-secrets"
 #define NM_SETTINGS_CONNECTION_CANCEL_SECRETS "cancel-secrets"
-
-/* Internal signals */
 #define NM_SETTINGS_CONNECTION_UPDATED_INTERNAL "updated-internal"
+#define NM_SETTINGS_CONNECTION_FLAGS_CHANGED    "flags-changed"
 
 /* Properties */
 #define NM_SETTINGS_CONNECTION_UNSAVED  "unsaved"
@@ -53,32 +50,45 @@
 
 
 /**
- * NMSettingsConnectionFlags:
- * @NM_SETTINGS_CONNECTION_FLAGS_NONE: no flag set
- * @NM_SETTINGS_CONNECTION_FLAGS_UNSAVED: the connection is not saved to disk
- * @NM_SETTINGS_CONNECTION_FLAGS_NM_GENERATED: A connection is "nm-generated" if
+ * NMSettingsConnectionIntFlags:
+ * @NM_SETTINGS_CONNECTION_INT_FLAGS_NONE: no flag set
+ * @NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED: the connection is not saved to disk.
+ *  See also #NM_SETTINGS_CONNECTION_FLAG_UNSAVED.
+ * @NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED: A connection is "nm-generated" if
  *  it was generated by NetworkManger. If the connection gets modified or saved
  *  by the user, the flag gets cleared. A nm-generated is implicitly unsaved.
- * @NM_SETTINGS_CONNECTION_FLAGS_VOLATILE: The connection will be deleted
+ *  See also #NM_SETTINGS_CONNECTION_FLAG_NM_GENERATED.
+ * @NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE: The connection will be deleted
  *  when it disconnects. That is for in-memory connections (unsaved), which are
  *  currently active but cleanup on disconnect.
- * @NM_SETTINGS_CONNECTION_FLAGS_VISIBLE: The connection is visible
- * @NM_SETTINGS_CONNECTION_FLAGS_ALL: special mask, for all known flags
+ *  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
+ *   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
  *
  * #NMSettingsConnection flags.
  **/
 typedef enum {
-	NM_SETTINGS_CONNECTION_FLAGS_NONE                               = 0,
+	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_FLAGS_UNSAVED                            = (1LL <<  0),
-	NM_SETTINGS_CONNECTION_FLAGS_NM_GENERATED                       = (1LL <<  1),
-	NM_SETTINGS_CONNECTION_FLAGS_VOLATILE                           = (1LL <<  2),
+	__NM_SETTINGS_CONNECTION_INT_FLAGS_LAST,
 
-	NM_SETTINGS_CONNECTION_FLAGS_VISIBLE                            = (1LL <<  3),
+	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_FLAGS_LAST,
-	NM_SETTINGS_CONNECTION_FLAGS_ALL = ((__NM_SETTINGS_CONNECTION_FLAGS_LAST - 1) << 1) - 1,
-} NMSettingsConnectionFlags;
+	NM_SETTINGS_CONNECTION_INT_FLAGS_ALL = ((__NM_SETTINGS_CONNECTION_INT_FLAGS_LAST - 1) << 1) - 1,
+} NMSettingsConnectionIntFlags;
 
 typedef enum {
 	NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE                       = 0,
@@ -106,12 +116,13 @@ typedef struct _NMSettingsConnectionClass NMSettingsConnectionClass;
 struct _NMSettingsConnectionPrivate;
 
 struct _NMSettingsConnection {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	struct _NMSettingsConnectionPrivate *_priv;
+	CList _connections_lst;
 };
 
 struct _NMSettingsConnectionClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 
 	gboolean (*commit_changes) (NMSettingsConnection *self,
 	                            NMConnection *new_connection,
@@ -199,9 +210,9 @@ void nm_settings_connection_signal_remove (NMSettingsConnection *self);
 
 gboolean nm_settings_connection_get_unsaved (NMSettingsConnection *self);
 
-NMSettingsConnectionFlags nm_settings_connection_get_flags (NMSettingsConnection *self);
-NMSettingsConnectionFlags nm_settings_connection_set_flags (NMSettingsConnection *self, NMSettingsConnectionFlags flags, gboolean set);
-NMSettingsConnectionFlags nm_settings_connection_set_flags_full (NMSettingsConnection *self, NMSettingsConnectionFlags mask, NMSettingsConnectionFlags value);
+NMSettingsConnectionIntFlags nm_settings_connection_get_flags (NMSettingsConnection *self);
+NMSettingsConnectionIntFlags nm_settings_connection_set_flags (NMSettingsConnection *self, NMSettingsConnectionIntFlags flags, gboolean set);
+NMSettingsConnectionIntFlags nm_settings_connection_set_flags_full (NMSettingsConnection *self, NMSettingsConnectionIntFlags mask, NMSettingsConnectionIntFlags value);
 
 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);
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 8e3fc582..2c6b7101 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -62,10 +62,12 @@
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 
+#include "nm-utils/nm-c-list.h"
+#include "nm-dbus-object.h"
 #include "devices/nm-device-ethernet.h"
 #include "nm-settings-connection.h"
 #include "nm-settings-plugin.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
 #include "nm-auth-utils.h"
 #include "nm-auth-subject.h"
 #include "nm-session-monitor.h"
@@ -77,8 +79,6 @@
 #include "nm-dispatcher.h"
 #include "nm-hostname-manager.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Settings.h"
-
 /*****************************************************************************/
 
 #define EXPORT(sym) void * __export_##sym = &sym;
@@ -107,7 +107,6 @@ enum {
 	CONNECTION_UPDATED,
 	CONNECTION_REMOVED,
 	CONNECTION_FLAGS_CHANGED,
-	NEW_CONNECTION, /* exported, not used internally */
 	LAST_SIGNAL
 };
 
@@ -121,29 +120,33 @@ typedef struct {
 	GSList *auths;
 
 	GSList *plugins;
-	gboolean connections_loaded;
-	GHashTable *connections;
+
+	CList connections_lst_head;
+
 	NMSettingsConnection **connections_cached_list;
 	GSList *unmanaged_specs;
 	GSList *unrecognized_specs;
 
-	gboolean started;
-	gboolean startup_complete;
-
 	NMHostnameManager *hostname_manager;
 
+	guint connections_len;
+
+	bool started:1;
+	bool startup_complete:1;
+	bool connections_loaded:1;
+
 } NMSettingsPrivate;
 
 struct _NMSettings {
-	NMExportedObject parent;
+	NMDBusObject parent;
 	NMSettingsPrivate _priv;
 };
 
 struct _NMSettingsClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 };
 
-G_DEFINE_TYPE (NMSettings, nm_settings, NM_TYPE_EXPORTED_OBJECT);
+G_DEFINE_TYPE (NMSettings, nm_settings, NM_TYPE_DBUS_OBJECT);
 
 #define NM_SETTINGS_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSettings, NM_IS_SETTINGS)
 
@@ -154,6 +157,10 @@ G_DEFINE_TYPE (NMSettings, nm_settings, NM_TYPE_EXPORTED_OBJECT);
 
 /*****************************************************************************/
 
+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);
 
@@ -164,27 +171,29 @@ static void connection_ready_changed (NMSettingsConnection *conn,
                                       GParamSpec *pspec,
                                       gpointer user_data);
 
+static void default_wired_clear_tag (NMSettings *self,
+                                     NMDevice *device,
+                                     NMSettingsConnection *connection,
+                                     gboolean add_to_no_auto_default);
+
 /*****************************************************************************/
 
 static void
 check_startup_complete (NMSettings *self)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GHashTableIter iter;
 	NMSettingsConnection *conn;
 
 	if (priv->startup_complete)
 		return;
 
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &conn)) {
+	c_list_for_each_entry (conn, &priv->connections_lst_head, _connections_lst) {
 		if (!nm_settings_connection_get_ready (conn))
 			return;
 	}
 
 	/* the connection_ready_changed signal handler is no longer needed. */
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &conn))
+	c_list_for_each_entry (conn, &priv->connections_lst_head, _connections_lst)
 		g_signal_handlers_disconnect_by_func (conn, G_CALLBACK (connection_ready_changed), self);
 
 	priv->startup_complete = TRUE;
@@ -247,43 +256,25 @@ load_connections (NMSettings *self)
 	unrecognized_specs_changed (NULL, self);
 }
 
-void
-nm_settings_for_each_connection (NMSettings *self,
-                                 NMSettingsForEachFunc for_each_func,
-                                 gpointer user_data)
-{
-	NMSettingsPrivate *priv;
-	GHashTableIter iter;
-	gpointer data;
-
-	g_return_if_fail (NM_IS_SETTINGS (self));
-	g_return_if_fail (for_each_func != NULL);
-
-	priv = NM_SETTINGS_GET_PRIVATE (self);
-
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, &data))
-		for_each_func (self, NM_SETTINGS_CONNECTION (data), user_data);
-}
-
 static void
-impl_settings_list_connections (NMSettings *self,
-                                GDBusMethodInvocation *context)
+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);
-	GPtrArray *connections;
-	GHashTableIter iter;
-	gpointer key;
-
-	connections = g_ptr_array_sized_new (g_hash_table_size (priv->connections) + 1);
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, &key, NULL))
-		g_ptr_array_add (connections, key);
-	g_ptr_array_add (connections, NULL);
-
-	g_dbus_method_invocation_return_value (context,
-	                                       g_variant_new ("(^ao)", connections->pdata));
-	g_ptr_array_unref (connections);
+	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 *
@@ -291,16 +282,14 @@ nm_settings_get_connection_by_uuid (NMSettings *self, const char *uuid)
 {
 	NMSettingsPrivate *priv;
 	NMSettingsConnection *candidate;
-	GHashTableIter iter;
 
 	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
 	g_return_val_if_fail (uuid != NULL, NULL);
 
 	priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &candidate)) {
-		if (g_strcmp0 (uuid, nm_settings_connection_get_uuid (candidate)) == 0)
+	c_list_for_each_entry (candidate, &priv->connections_lst_head, _connections_lst) {
+		if (nm_streq (uuid, nm_settings_connection_get_uuid (candidate)))
 			return candidate;
 	}
 
@@ -308,14 +297,21 @@ nm_settings_get_connection_by_uuid (NMSettings *self, const char *uuid)
 }
 
 static void
-impl_settings_get_connection_by_uuid (NMSettings *self,
-                                      GDBusMethodInvocation *context,
-                                      const char *uuid)
+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 *connection = NULL;
-	NMAuthSubject *subject = NULL;
+	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
-	char *error_desc = NULL;
+	const char *uuid;
+
+	g_variant_get (parameters, "(&s)", &uuid);
 
 	connection = nm_settings_get_connection_by_uuid (self, uuid);
 	if (!connection) {
@@ -325,7 +321,7 @@ impl_settings_get_connection_by_uuid (NMSettings *self,
 		goto error;
 	}
 
-	subject = nm_auth_subject_new_unix_process_from_context (context);
+	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,
@@ -333,26 +329,40 @@ impl_settings_get_connection_by_uuid (NMSettings *self,
 		goto error;
 	}
 
-	if (!nm_auth_is_subject_in_acl (NM_CONNECTION (connection),
-	                                subject,
-	                                &error_desc)) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             error_desc);
-		g_free (error_desc);
+	if (!nm_auth_is_subject_in_acl_set_error (NM_CONNECTION (connection),
+	                                          subject,
+	                                          NM_SETTINGS_ERROR,
+	                                          NM_SETTINGS_ERROR_PERMISSION_DENIED,
+	                                          &error))
 		goto error;
-	}
 
-	g_clear_object (&subject);
-	g_dbus_method_invocation_return_value (
-		context,
-		g_variant_new ("(o)", nm_connection_get_path (NM_CONNECTION (connection))));
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new ("(o)",
+	                                                      nm_dbus_object_get_path (NM_DBUS_OBJECT (connection))));
 	return;
 
 error:
-	g_assert (error);
-	g_dbus_method_invocation_take_error (context, error);
-	g_clear_object (&subject);
+	g_dbus_method_invocation_take_error (invocation, error);
+}
+
+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);
 }
 
 /**
@@ -370,37 +380,33 @@ error:
 NMSettingsConnection *const*
 nm_settings_get_connections (NMSettings *self, guint *out_len)
 {
-	GHashTableIter iter;
 	NMSettingsPrivate *priv;
-	guint l, i;
 	NMSettingsConnection **v;
 	NMSettingsConnection *con;
+	guint i;
 
 	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
 
 	priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	if (G_LIKELY (priv->connections_cached_list)) {
-		NM_SET_OUT (out_len, g_hash_table_size (priv->connections));
-		return priv->connections_cached_list;
-	}
+	nm_assert (priv->connections_len == c_list_length (&priv->connections_lst_head));
 
-	l = g_hash_table_size (priv->connections);
+	if (G_UNLIKELY (!priv->connections_cached_list)) {
+		v = g_new (NMSettingsConnection *, priv->connections_len + 1);
 
-	v = g_new (NMSettingsConnection *, (gsize) l + 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;
 
-	i = 0;
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &con)) {
-		nm_assert (i < l);
-		v[i++] = con;
+		priv->connections_cached_list = v;
 	}
-	nm_assert (i == l);
-	v[i] = NULL;
 
-	NM_SET_OUT (out_len, l);
-	priv->connections_cached_list = v;
-	return v;
+	NM_SET_OUT (out_len, priv->connections_len);
+	return priv->connections_cached_list;
 }
 
 /**
@@ -467,28 +473,41 @@ 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, NULL);
+	g_return_val_if_fail (path, NULL);
 
 	priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	return (NMSettingsConnection *) g_hash_table_lookup (priv->connections, path);
+	connection = (NMSettingsConnection *) 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)
 {
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GHashTableIter iter;
-	gpointer data;
+	NMSettingsConnection *candidate = NULL;
+	const char *path;
 
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, &data))
-		if (data == connection)
-			return TRUE;
+	g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (connection), FALSE);
 
-	return FALSE;
+	path = nm_dbus_object_get_path (NM_DBUS_OBJECT (connection));
+	if (path)
+		candidate = nm_settings_get_connection_by_path (self, path);
+
+	nm_assert (!candidate || candidate == connection);
+	nm_assert (!!candidate == nm_c_list_contains_entry (&NM_SETTINGS_GET_PRIVATE (self)->connections_lst_head,
+	                                                    connection,
+	                                                    _connections_lst));
+	return !!candidate;
 }
 
 const GSList *
@@ -500,7 +519,7 @@ nm_settings_get_unmanaged_specs (NMSettings *self)
 }
 
 static NMSettingsPlugin *
-get_plugin (NMSettings *self, guint32 capability)
+get_plugin (NMSettings *self, NMSettingsPluginCapabilities capability)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	GSList *iter;
@@ -677,8 +696,8 @@ load_plugins (NMSettings *self, const char **plugins, GError **error)
 			continue;
 		}
 
-		if (!strcmp (pname, "ifcfg-suse")) {
-			_LOGW ("skipping deprecated plugin ifcfg-suse");
+		if (NM_IN_STRSET (pname, "ifcfg-suse", "ifnet")) {
+			_LOGW ("skipping deprecated plugin %s", pname);
 			continue;
 		}
 
@@ -806,7 +825,6 @@ connection_updated (NMSettingsConnection *connection, gboolean by_user, gpointer
 
 static void
 connection_flags_changed (NMSettingsConnection *connection,
-                          GParamSpec *pspec,
                           gpointer user_data)
 {
 	g_signal_emit (NM_SETTINGS (user_data),
@@ -820,11 +838,20 @@ connection_removed (NMSettingsConnection *connection, gpointer user_data)
 {
 	NMSettings *self = NM_SETTINGS (user_data);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	const char *cpath = nm_connection_get_path (NM_CONNECTION (connection));
+	NMDevice *device;
 
-	if (!g_hash_table_lookup (priv->connections, cpath))
-		g_return_if_reached ();
-	g_object_ref (connection);
+	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));
+
+	/* 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
+	 * wired device to the config file and don't create a new default wired
+	 * connection for that device again.
+	 */
+	device = g_object_get_qdata (G_OBJECT (connection), _default_wired_device_quark ());
+	if (device)
+		default_wired_clear_tag (self, device, connection, TRUE);
 
 	/* Disconnect signal handlers, as plugins might still keep references
 	 * to the connection (and thus the signal handlers would still be live)
@@ -836,23 +863,30 @@ connection_removed (NMSettingsConnection *connection, gpointer user_data)
 	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_object_unref (self);
 
 	/* Forget about the connection internally */
-	g_hash_table_remove (priv->connections, (gpointer) cpath);
-	g_clear_pointer (&priv->connections_cached_list, g_free);
+	_clear_connections_cached_list (priv);
+	priv->connections_len--;
+	c_list_unlink (&connection->_connections_lst);
+
+	if (priv->connections_loaded) {
+		_notify (self, PROP_CONNECTIONS);
 
-	/* Notify D-Bus */
-	g_signal_emit (self, signals[CONNECTION_REMOVED], 0, connection);
+		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)));
+	}
 
-	/* Re-emit for listeners like NMPolicy */
-	_notify (self, PROP_CONNECTIONS);
-	if (nm_exported_object_is_exported (NM_EXPORTED_OBJECT (connection)))
-		nm_exported_object_unexport (NM_EXPORTED_OBJECT (connection));
+	nm_dbus_object_unexport (NM_DBUS_OBJECT (connection));
 
-	check_startup_complete (self);
+	if (priv->connections_loaded)
+		g_signal_emit (self, signals[CONNECTION_REMOVED], 0, connection);
 
 	g_object_unref (connection);
+
+	check_startup_complete (self);
 }
 
 #define NM_DBUS_SERVICE_OPENCONNECT    "org.freedesktop.NetworkManager.openconnect"
@@ -900,19 +934,16 @@ claim_connection (NMSettings *self, NMSettingsConnection *connection)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	GError *error = NULL;
-	GHashTableIter iter;
-	gpointer data;
 	const char *path;
 	NMSettingsConnection *existing;
 
 	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (connection));
-	g_return_if_fail (nm_connection_get_path (NM_CONNECTION (connection)) == NULL);
+	g_return_if_fail (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (connection)));
 
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, &data)) {
-		/* prevent duplicates */
-		if (data == connection)
-			return;
+	/* prevent duplicates */
+	if (!c_list_is_empty (&connection->_connections_lst)) {
+		nm_assert (c_list_contains (&priv->connections_lst_head, &connection->_connections_lst));
+		return;
 	}
 
 	if (!nm_connection_normalize (NM_CONNECTION (connection), NULL, NULL, &error)) {
@@ -944,20 +975,19 @@ claim_connection (NMSettings *self, NMSettingsConnection *connection)
 	/* Read seen-bssids from look-aside file and put it into the connection's data */
 	nm_settings_connection_read_and_fill_seen_bssids (connection);
 
-	/* Ensure it's initial visibility is up-to-date */
+	/* Ensure its initial visibility is up-to-date */
 	nm_settings_connection_recheck_visibility (connection);
 
 	/* Evil openconnect migration hack */
 	openconnect_migrate_hack (NM_CONNECTION (connection));
 
-	g_object_ref (self);
 	/* This one unexports the connection, it needs to run late to give the active
 	 * connection a chance to deal with its reference to this settings connection. */
 	g_signal_connect_after (connection, NM_SETTINGS_CONNECTION_REMOVED,
 	                        G_CALLBACK (connection_removed), self);
 	g_signal_connect (connection, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
 	                  G_CALLBACK (connection_updated), self);
-	g_signal_connect (connection, "notify::" NM_SETTINGS_CONNECTION_FLAGS,
+	g_signal_connect (connection, NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
 	                  G_CALLBACK (connection_flags_changed),
 	                  self);
 	if (!priv->startup_complete) {
@@ -966,28 +996,29 @@ claim_connection (NMSettings *self, NMSettingsConnection *connection)
 		                  self);
 	}
 
-	/* Export the connection over D-Bus */
-	g_warn_if_fail (nm_connection_get_path (NM_CONNECTION (connection)) == NULL);
-	path = nm_exported_object_export (NM_EXPORTED_OBJECT (connection));
-	nm_connection_set_path (NM_CONNECTION (connection), path);
+	_clear_connections_cached_list (priv);
 
-	g_hash_table_insert (priv->connections,
-	                     (gpointer) nm_connection_get_path (NM_CONNECTION (connection)),
-	                     g_object_ref (connection));
-	g_clear_pointer (&priv->connections_cached_list, g_free);
+	g_object_ref (connection);
+	priv->connections_len++;
+	c_list_link_tail (&priv->connections_lst_head, &connection->_connections_lst);
+
+	path = nm_dbus_object_export (NM_DBUS_OBJECT (connection));
 
-	nm_utils_log_connection_diff (NM_CONNECTION (connection), NULL, LOGL_DEBUG, LOGD_CORE, "new connection", "++ ");
+	nm_utils_log_connection_diff (NM_CONNECTION (connection), 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) {
-		/* Internal added signal */
+		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 (connection)));
+
 		g_signal_emit (self, signals[CONNECTION_ADDED], 0, connection);
 		_notify (self, PROP_CONNECTIONS);
-
-		/* Exported D-Bus signal */
-		g_signal_emit (self, signals[NEW_CONNECTION], 0, connection);
 	}
 
 	nm_settings_connection_added (connection);
@@ -1035,14 +1066,14 @@ nm_settings_add_connection (NMSettings *self,
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	GSList *iter;
 	NMSettingsConnection *added = NULL;
-	GHashTableIter citer;
-	NMConnection *candidate = NULL;
+	NMSettingsConnection *candidate = NULL;
+	const char *uuid;
+
+	uuid = nm_connection_get_uuid (connection);
 
 	/* Make sure a connection with this UUID doesn't already exist */
-	g_hash_table_iter_init (&citer, priv->connections);
-	while (g_hash_table_iter_next (&citer, NULL, (gpointer *) &candidate)) {
-		if (g_strcmp0 (nm_connection_get_uuid (connection),
-		               nm_connection_get_uuid (candidate)) == 0) {
+	c_list_for_each_entry (candidate, &priv->connections_lst_head, _connections_lst) {
+		if (nm_streq0 (uuid, nm_connection_get_uuid (NM_CONNECTION (candidate)))) {
 			g_set_error_literal (error,
 			                     NM_SETTINGS_ERROR,
 			                     NM_SETTINGS_ERROR_UUID_EXISTS,
@@ -1099,7 +1130,7 @@ send_agent_owned_secrets (NMSettings *self,
                           NMAuthSubject *subject)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	NMConnection *for_agent;
+	gs_unref_object NMConnection *for_agent = NULL;
 
 	/* Dupe the connection so we can clear out non-agent-owned secrets,
 	 * as agent-owned secrets are the only ones we send back to be saved.
@@ -1110,10 +1141,9 @@ send_agent_owned_secrets (NMSettings *self,
 	                                        secrets_filter_cb,
 	                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
 	nm_agent_manager_save_secrets (priv->agent_mgr,
-	                               nm_connection_get_path (NM_CONNECTION (connection)),
+	                               nm_dbus_object_get_path (NM_DBUS_OBJECT (connection)),
 	                               for_agent,
 	                               subject);
-	g_object_unref (for_agent);
 }
 
 static void
@@ -1170,7 +1200,7 @@ pk_add_cb (NMAuthChain *chain,
 		send_agent_owned_secrets (self, added, subject);
 
 	g_clear_error (&error);
-	nm_auth_chain_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 
 /* FIXME: remove if/when kernel supports adhoc wpa */
@@ -1218,7 +1248,6 @@ nm_settings_add_connection_dbus (NMSettings *self,
 	NMAuthSubject *subject = NULL;
 	NMAuthChain *chain;
 	GError *error = NULL, *tmp_error = NULL;
-	char *error_desc = NULL;
 	const char *perm;
 
 	g_return_if_fail (connection != NULL);
@@ -1261,18 +1290,12 @@ nm_settings_add_connection_dbus (NMSettings *self,
 		goto done;
 	}
 
-	/* Ensure the caller's username exists in the connection's permissions,
-	 * or that the permissions is empty (ie, visible by everyone).
-	 */
-	if (!nm_auth_is_subject_in_acl (connection,
-	                                subject,
-	                                &error_desc)) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             error_desc);
-		g_free (error_desc);
+	if (!nm_auth_is_subject_in_acl_set_error (connection,
+	                                          subject,
+	                                          NM_SETTINGS_ERROR,
+	                                          NM_SETTINGS_ERROR_PERMISSION_DENIED,
+	                                          &error))
 		goto done;
-	}
 
 	/* If the caller is the only user in the connection's permissions, then
 	 * we use the 'modify.own' permission instead of 'modify.system'.  If the
@@ -1312,30 +1335,30 @@ done:
 }
 
 static void
-impl_settings_add_connection_add_cb (NMSettings *self,
-                                     NMSettingsConnection *connection,
-                                     GError *error,
-                                     GDBusMethodInvocation *context,
-                                     NMAuthSubject *subject,
-                                     gpointer user_data)
+settings_add_connection_add_cb (NMSettings *self,
+                                NMSettingsConnection *connection,
+                                GError *error,
+                                GDBusMethodInvocation *context,
+                                NMAuthSubject *subject,
+                                gpointer 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);
 	} else {
-		g_dbus_method_invocation_return_value (
-		    context,
-		    g_variant_new ("(o)", nm_connection_get_path (NM_CONNECTION (connection))));
+		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);
 	}
 }
 
 static void
-impl_settings_add_connection_helper (NMSettings *self,
-                                     GDBusMethodInvocation *context,
-                                     GVariant *settings,
-                                     gboolean save_to_disk)
+settings_add_connection_helper (NMSettings *self,
+                                GDBusMethodInvocation *context,
+                                GVariant *settings,
+                                gboolean save_to_disk)
 {
 	gs_unref_object NMConnection *connection = NULL;
 	GError *error = NULL;
@@ -1355,77 +1378,111 @@ impl_settings_add_connection_helper (NMSettings *self,
 	                                 connection,
 	                                 save_to_disk,
 	                                 context,
-	                                 impl_settings_add_connection_add_cb,
+	                                 settings_add_connection_add_cb,
 	                                 NULL);
 }
 
 static void
-impl_settings_add_connection (NMSettings *self,
-                              GDBusMethodInvocation *context,
-                              GVariant *settings)
+impl_settings_add_connection (NMDBusObject *obj,
+                              const NMDBusInterfaceInfoExtended *interface_info,
+                              const NMDBusMethodInfoExtended *method_info,
+                              GDBusConnection *connection,
+                              const char *sender,
+                              GDBusMethodInvocation *invocation,
+                              GVariant *parameters)
 {
-	impl_settings_add_connection_helper (self, context, settings, TRUE);
+	NMSettings *self = NM_SETTINGS (obj);
+	gs_unref_variant GVariant *settings = NULL;
+
+	g_variant_get (parameters, "(@a{sa{sv}})", &settings);
+	settings_add_connection_helper (self, invocation, settings, TRUE);
 }
 
 static void
-impl_settings_add_connection_unsaved (NMSettings *self,
-                                      GDBusMethodInvocation *context,
-                                      GVariant *settings)
+impl_settings_add_connection_unsaved (NMDBusObject *obj,
+                                      const NMDBusInterfaceInfoExtended *interface_info,
+                                      const NMDBusMethodInfoExtended *method_info,
+                                      GDBusConnection *connection,
+                                      const char *sender,
+                                      GDBusMethodInvocation *invocation,
+                                      GVariant *parameters)
 {
-	impl_settings_add_connection_helper (self, context, settings, FALSE);
+	NMSettings *self = NM_SETTINGS (obj);
+	gs_unref_variant GVariant *settings = NULL;
+
+	g_variant_get (parameters, "(@a{sa{sv}})", &settings);
+	settings_add_connection_helper (self, invocation, settings, FALSE);
 }
 
 static void
-impl_settings_load_connections (NMSettings *self,
-                                GDBusMethodInvocation *context,
-                                char **filenames)
+impl_settings_load_connections (NMDBusObject *obj,
+                                const NMDBusInterfaceInfoExtended *interface_info,
+                                const NMDBusMethodInfoExtended *method_info,
+                                GDBusConnection *connection,
+                                const char *sender,
+                                GDBusMethodInvocation *invocation,
+                                GVariant *parameters)
 {
+	NMSettings *self = NM_SETTINGS (obj);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GPtrArray *failures;
+	gs_unref_ptrarray GPtrArray *failures = NULL;
 	GSList *iter;
-	int i;
+	guint i;
+	gs_free const char **filenames = NULL;
+
+	g_variant_get (parameters, "(^a&s)", &filenames);
 
 	/* 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
 	 * we'll be able to switch to polkit without breaking behavior.
 	 */
-	if (!nm_bus_manager_ensure_uid (nm_bus_manager_get (),
-	                                context,
-	                                G_MAXULONG,
-	                                NM_SETTINGS_ERROR,
-	                                NM_SETTINGS_ERROR_PERMISSION_DENIED))
+	if (!nm_dbus_manager_ensure_uid (nm_dbus_object_get_manager (obj),
+	                                 invocation,
+	                                 G_MAXULONG,
+	                                 NM_SETTINGS_ERROR,
+	                                 NM_SETTINGS_ERROR_PERMISSION_DENIED))
 		return;
 
-	failures = g_ptr_array_new ();
-
-	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) {
+		for (i = 0; filenames[i]; i++) {
+			for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
+				NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
 
-			if (nm_settings_plugin_load_connection (plugin, filenames[i]))
-				break;
-		}
+				if (nm_settings_plugin_load_connection (plugin, filenames[i]))
+					break;
+			}
 
-		if (!iter) {
-			if (!g_path_is_absolute (filenames[i]))
-				_LOGW ("connection filename '%s' is not an absolute path", filenames[i]);
-			g_ptr_array_add (failures, (char *) filenames[i]);
+			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]);
+			}
 		}
 	}
 
-	g_ptr_array_add (failures, NULL);
-	g_dbus_method_invocation_return_value (
-		context,
-		g_variant_new ("(b^as)",
-		               failures->len == 1,
-		               failures->pdata));
-	g_ptr_array_unref (failures);
+	if (failures)
+		g_ptr_array_add (failures, NULL);
+
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new ("(b^as)",
+	                                                      (gboolean) (!!failures),
+	                                                      failures
+	                                                        ? (const char **) failures->pdata
+	                                                        : NM_PTRARRAY_EMPTY (const char *)));
 }
 
 static void
-impl_settings_reload_connections (NMSettings *self,
-                                  GDBusMethodInvocation *context)
+impl_settings_reload_connections (NMDBusObject *obj,
+                                  const NMDBusInterfaceInfoExtended *interface_info,
+                                  const NMDBusMethodInfoExtended *method_info,
+                                  GDBusConnection *connection,
+                                  const char *sender,
+                                  GDBusMethodInvocation *invocation,
+                                  GVariant *parameters)
 {
+	NMSettings *self = NM_SETTINGS (obj);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	GSList *iter;
 
@@ -1433,11 +1490,11 @@ impl_settings_reload_connections (NMSettings *self,
 	 * that the caller is still alive so that clients are forced to wait and
 	 * we'll be able to switch to polkit without breaking behavior.
 	 */
-	if (!nm_bus_manager_ensure_uid (nm_bus_manager_get (),
-	                                context,
-	                                G_MAXULONG,
-	                                NM_SETTINGS_ERROR,
-	                                NM_SETTINGS_ERROR_PERMISSION_DENIED))
+	if (!nm_dbus_manager_ensure_uid (nm_dbus_object_get_manager (obj),
+	                                 invocation,
+	                                 G_MAXULONG,
+	                                 NM_SETTINGS_ERROR,
+	                                 NM_SETTINGS_ERROR_PERMISSION_DENIED))
 		return;
 
 	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
@@ -1446,7 +1503,7 @@ impl_settings_reload_connections (NMSettings *self,
 		nm_settings_plugin_reload_connections (plugin);
 	}
 
-	g_dbus_method_invocation_return_value (context, g_variant_new ("(b)", TRUE));
+	g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
 }
 
 /*****************************************************************************/
@@ -1494,41 +1551,46 @@ pk_hostname_cb (NMAuthChain *chain,
 	else
 		g_dbus_method_invocation_return_value (context, NULL);
 
-	nm_auth_chain_unref (chain);
+	nm_auth_chain_destroy (chain);
 }
 
 static void
-impl_settings_save_hostname (NMSettings *self,
-                             GDBusMethodInvocation *context,
-                             const char *hostname)
+impl_settings_save_hostname (NMDBusObject *obj,
+                             const NMDBusInterfaceInfoExtended *interface_info,
+                             const NMDBusMethodInfoExtended *method_info,
+                             GDBusConnection *connection,
+                             const char *sender,
+                             GDBusMethodInvocation *invocation,
+                             GVariant *parameters)
 {
+	NMSettings *self = NM_SETTINGS (obj);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	NMAuthChain *chain;
-	GError *error = NULL;
+	const char *hostname;
+
+	g_variant_get (parameters, "(&s)", &hostname);
 
 	/* Minimal validation of the hostname */
 	if (!nm_hostname_manager_validate_hostname (hostname)) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_INVALID_HOSTNAME,
-		                             "The hostname was too long or contained invalid characters.");
-		goto done;
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_SETTINGS_ERROR,
+		                                               NM_SETTINGS_ERROR_INVALID_HOSTNAME,
+		                                               "The hostname was too long or contained invalid characters.");
+		return;
 	}
 
-	chain = nm_auth_chain_new_context (context, pk_hostname_cb, self);
+	chain = nm_auth_chain_new_context (invocation, pk_hostname_cb, self);
 	if (!chain) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             "Unable to authenticate the request.");
-		goto done;
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_SETTINGS_ERROR,
+		                                               NM_SETTINGS_ERROR_PERMISSION_DENIED,
+		                                               "Unable to authenticate the request.");
+		return;
 	}
 
 	priv->auths = g_slist_append (priv->auths, 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);
-
-done:
-	if (error)
-		g_dbus_method_invocation_take_error (context, error);
 }
 
 /*****************************************************************************/
@@ -1537,27 +1599,24 @@ static gboolean
 have_connection_for_device (NMSettings *self, NMDevice *device)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GHashTableIter iter;
-	gpointer data;
 	NMSettingConnection *s_con;
 	NMSettingWired *s_wired;
 	const char *setting_hwaddr;
 	const char *perm_hw_addr;
+	NMSettingsConnection *connection;
 
 	g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
 
 	perm_hw_addr = nm_device_get_permanent_hw_address (device);
 
 	/* Find a wired connection locked to the given MAC address, if any */
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, &data)) {
-		NMConnection *connection = NM_CONNECTION (data);
+	c_list_for_each_entry (connection, &priv->connections_lst_head, _connections_lst) {
 		const char *ctype, *iface;
 
-		if (!nm_device_check_connection_compatible (device, connection))
+		if (!nm_device_check_connection_compatible (device, NM_CONNECTION (connection)))
 			continue;
 
-		s_con = nm_connection_get_setting_connection (connection);
+		s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
 
 		iface = nm_setting_connection_get_interface_name (s_con);
 		if (iface && strcmp (iface, nm_device_get_iface (device)) != 0)
@@ -1568,7 +1627,7 @@ have_connection_for_device (NMSettings *self, NMDevice *device)
 		    && strcmp (ctype, NM_SETTING_PPPOE_SETTING_NAME))
 			continue;
 
-		s_wired = nm_connection_get_setting_wired (connection);
+		s_wired = nm_connection_get_setting_wired (NM_CONNECTION (connection));
 
 		if (!s_wired && !strcmp (ctype, NM_SETTING_PPPOE_SETTING_NAME)) {
 			/* No wired setting; therefore the PPPoE connection applies to any device */
@@ -1596,26 +1655,6 @@ have_connection_for_device (NMSettings *self, NMDevice *device)
 	return FALSE;
 }
 
-static void default_wired_clear_tag (NMSettings *self,
-                                     NMDevice *device,
-                                     NMSettingsConnection *connection,
-                                     gboolean add_to_no_auto_default);
-
-static void
-default_wired_connection_removed_cb (NMSettingsConnection *connection, NMSettings *self)
-{
-	NMDevice *device;
-
-	/* 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
-	 * wired device to the config file and don't create a new default wired
-	 * connection for that device again.
-	 */
-	device = g_object_get_qdata (G_OBJECT (connection), _default_wired_device_quark ());
-	if (device)
-		default_wired_clear_tag (self, device, connection, TRUE);
-}
-
 static void
 default_wired_connection_updated_by_user_cb (NMSettingsConnection *connection, gboolean by_user, NMSettings *self)
 {
@@ -1648,7 +1687,6 @@ default_wired_clear_tag (NMSettings *self,
 	g_object_set_qdata (G_OBJECT (connection), _default_wired_device_quark (), NULL);
 	g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), NULL);
 
-	g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (default_wired_connection_removed_cb), self);
 	g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (default_wired_connection_updated_by_user_cb), self);
 
 	if (add_to_no_auto_default)
@@ -1700,8 +1738,6 @@ device_realized (NMDevice *device, GParamSpec *pspec, NMSettings *self)
 
 	g_signal_connect (added, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
 	                  G_CALLBACK (default_wired_connection_updated_by_user_cb), self);
-	g_signal_connect (added, NM_SETTINGS_CONNECTION_REMOVED,
-	                  G_CALLBACK (default_wired_connection_removed_cb), self);
 
 	_LOGI ("(%s): created default wired connection '%s'",
 	       nm_device_get_iface (device),
@@ -1774,10 +1810,8 @@ nm_settings_start (NMSettings *self, GError **error)
 	/* 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)) {
-		g_object_unref (self);
+	if (!load_plugins (self, (const char **) plugins, error))
 		return FALSE;
-	}
 
 	load_connections (self);
 	check_startup_complete (self);
@@ -1802,18 +1836,19 @@ get_property (GObject *object, guint prop_id,
 	NMSettings *self = NM_SETTINGS (object);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	const GSList *specs, *iter;
-	GHashTableIter citer;
-	GPtrArray *array;
-	const char *path;
+	guint i;
+	char **strvs;
+	const char **strv;
 
 	switch (prop_id) {
 	case PROP_UNMANAGED_SPECS:
-		array = g_ptr_array_new ();
 		specs = nm_settings_get_unmanaged_specs (self);
-		for (iter = specs; iter; iter = g_slist_next (iter))
-			g_ptr_array_add (array, g_strdup (iter->data));
-		g_ptr_array_add (array, NULL);
-		g_value_take_boxed (value, (char **) g_ptr_array_free (array, FALSE));
+		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);
 		break;
 	case PROP_HOSTNAME:
 		g_value_set_string (value,
@@ -1825,12 +1860,14 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_boolean (value, !!get_plugin (self, NM_SETTINGS_PLUGIN_CAP_MODIFY_CONNECTIONS));
 		break;
 	case PROP_CONNECTIONS:
-		array = g_ptr_array_sized_new (g_hash_table_size (priv->connections) + 1);
-		g_hash_table_iter_init (&citer, priv->connections);
-		while (g_hash_table_iter_next (&citer, (gpointer) &path, NULL))
-			g_ptr_array_add (array, g_strdup (path));
-		g_ptr_array_add (array, NULL);
-		g_value_take_boxed (value, (char **) g_ptr_array_free (array, FALSE));
+		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);
 		break;
 	case PROP_STARTUP_COMPLETE:
 		g_value_set_boolean (value, nm_settings_get_startup_complete (self));
@@ -1848,7 +1885,7 @@ nm_settings_init (NMSettings *self)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_object_unref);
+	c_list_init (&priv->connections_lst_head);
 
 	priv->agent_mgr = g_object_ref (nm_agent_manager_get ());
 	priv->config = g_object_ref (nm_config_get ());
@@ -1866,11 +1903,9 @@ dispose (GObject *object)
 	NMSettings *self = NM_SETTINGS (object);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	g_slist_free_full (priv->auths, (GDestroyNotify) nm_auth_chain_unref);
+	g_slist_free_full (priv->auths, (GDestroyNotify) nm_auth_chain_destroy);
 	priv->auths = NULL;
 
-	g_object_unref (priv->agent_mgr);
-
 	if (priv->hostname_manager) {
 		g_signal_handlers_disconnect_by_func (priv->hostname_manager,
 		                                      G_CALLBACK (_hostname_changed_cb),
@@ -1887,26 +1922,139 @@ finalize (GObject *object)
 	NMSettings *self = NM_SETTINGS (object);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	g_hash_table_destroy (priv->connections);
-	g_clear_pointer (&priv->connections_cached_list, g_free);
+	_clear_connections_cached_list (priv);
+
+	nm_assert (c_list_is_empty (&priv->connections_lst_head));
 
 	g_slist_free_full (priv->unmanaged_specs, g_free);
 	g_slist_free_full (priv->unrecognized_specs, g_free);
 
 	g_slist_free_full (priv->plugins, g_object_unref);
 
+	g_clear_object (&priv->agent_mgr);
+
 	g_clear_object (&priv->config);
 
 	G_OBJECT_CLASS (nm_settings_parent_class)->finalize (object);
 }
 
+static const GDBusSignalInfo signal_info_new_connection = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"NewConnection",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("connection", "o"),
+	),
+);
+
+static const GDBusSignalInfo signal_info_connection_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"ConnectionRemoved",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("connection", "o"),
+	),
+);
+
+static const NMDBusInterfaceInfoExtended interface_info_settings = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_SETTINGS,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"ListConnections",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connections", "ao"),
+					),
+				),
+				.handle = impl_settings_list_connections,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetConnectionByUuid",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("uuid", "s"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connection", "o"),
+					),
+				),
+				.handle = impl_settings_get_connection_by_uuid,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"AddConnection",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connection", "a{sa{sv}}"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("path", "o"),
+					),
+				),
+				.handle = impl_settings_add_connection,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"AddConnectionUnsaved",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("connection", "a{sa{sv}}"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("path", "o"),
+					),
+				),
+				.handle = impl_settings_add_connection_unsaved,
+			),
+			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"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("status", "b"),
+						NM_DEFINE_GDBUS_ARG_INFO ("failures", "as"),
+					),
+				),
+				.handle = impl_settings_load_connections,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"ReloadConnections",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("status", "b"),
+					),
+				),
+				.handle = impl_settings_reload_connections,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"SaveHostname",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("hostname", "s"),
+					),
+				),
+				.handle = impl_settings_save_hostname,
+			),
+		),
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+			&signal_info_new_connection,
+			&signal_info_connection_removed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Connections", "ao", NM_SETTINGS_CONNECTIONS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Hostname",    "s",  NM_SETTINGS_HOSTNAME),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("CanModify",   "b",  NM_SETTINGS_CAN_MODIFY),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_settings_class_init (NMSettingsClass *class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (class);
 
-	exported_object_class->export_path = NM_DBUS_PATH_SETTINGS;
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC (NM_DBUS_PATH_SETTINGS);
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_settings);
 
 	object_class->get_property = get_property;
 	object_class->dispose = dispose;
@@ -1975,23 +2123,4 @@ nm_settings_class_init (NMSettingsClass *class)
 	                  0, NULL, NULL,
 	                  g_cclosure_marshal_VOID__OBJECT,
 	                  G_TYPE_NONE, 1, NM_TYPE_SETTINGS_CONNECTION);
-
-	signals[NEW_CONNECTION] =
-	    g_signal_new ("new-connection",
-	                  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);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (class),
-	                                        NMDBUS_TYPE_SETTINGS_SKELETON,
-	                                        "ListConnections", impl_settings_list_connections,
-	                                        "GetConnectionByUuid", impl_settings_get_connection_by_uuid,
-	                                        "AddConnection", impl_settings_add_connection,
-	                                        "AddConnectionUnsaved", impl_settings_add_connection_unsaved,
-	                                        "LoadConnections", impl_settings_load_connections,
-	                                        "ReloadConnections", impl_settings_reload_connections,
-	                                        "SaveHostname", impl_settings_save_hostname,
-	                                        NULL);
 }
-
diff --git a/src/settings/nm-settings.h b/src/settings/nm-settings.h
index 0ecffb70..7d56f7b4 100644
--- a/src/settings/nm-settings.h
+++ b/src/settings/nm-settings.h
@@ -28,8 +28,6 @@
 
 #include "nm-connection.h"
 
-#include "nm-exported-object.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))
@@ -72,14 +70,6 @@ NMSettings *nm_settings_get (void);
 NMSettings *nm_settings_new (void);
 gboolean nm_settings_start (NMSettings *self, GError **error);
 
-typedef void (*NMSettingsForEachFunc) (NMSettings *settings,
-                                       NMSettingsConnection *connection,
-                                       gpointer user_data);
-
-void nm_settings_for_each_connection (NMSettings *settings,
-                                      NMSettingsForEachFunc for_each_func,
-                                      gpointer user_data);
-
 typedef void (*NMSettingsAddCallback) (NMSettings *settings,
                                        NMSettingsConnection *connection,
                                        GError *error,
diff --git a/src/settings/plugins/ibft/meson.build b/src/settings/plugins/ibft/meson.build
new file mode 100644
index 00000000..da9f5566
--- /dev/null
+++ b/src/settings/plugins/ibft/meson.build
@@ -0,0 +1,48 @@
+name = 'nm-settings-plugin-ibft'
+
+cflags = [
+  '-DSBINDIR="@0@"'.format(nm_sbindir),
+  '-DSYSCONFDIR="@0@"'.format(nm_sysconfdir)
+]
+
+libnms_ibft_core = static_library(
+  'nms-ibft-core',
+  'nms-ibft-reader.c',
+  dependencies: nm_dep,
+  c_args: cflags
+)
+
+sources = files(
+  'nms-ibft-connection.c',
+  'nms-ibft-plugin.c'
+)
+
+libnm_settings_plugin_ibft = shared_module(
+  name,
+  sources: sources,
+  dependencies: nm_dep,
+  c_args: cflags,
+  link_with: libnms_ibft_core,
+  link_args: ldflags_linker_script_settings,
+  link_depends: linker_script_settings,
+  install: true,
+  install_dir: nm_pkglibdir
+)
+
+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/tests/meson.build b/src/settings/plugins/ibft/tests/meson.build
new file mode 100644
index 00000000..7a9445dd
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/meson.build
@@ -0,0 +1,22 @@
+test_unit = 'test-ibft'
+
+test_ibft_dir = meson.current_source_dir()
+
+cflags = [
+  '-DTEST_IBFT_DIR="@0@"'.format(test_ibft_dir),
+  '-DTEST_SCRATCH_DIR="@0@"'.format(test_ibft_dir)
+]
+
+exe = executable(
+  test_unit,
+  test_unit + '.c',
+  dependencies: test_nm_dep,
+  c_args: cflags,
+  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
index 72d1a7db..fd2ec61b 100644
--- a/src/settings/plugins/ibft/tests/test-ibft.c
+++ b/src/settings/plugins/ibft/tests/test-ibft.c
@@ -181,7 +181,7 @@ test_read_ibft_malformed (gconstpointer user_data)
 
 	g_assert (g_file_test (iscsiadm_path, G_FILE_TEST_EXISTS));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*malformed iscsiadm record*");
+	NMTST_EXPECT_NM_WARN ("*malformed iscsiadm record*");
 
 	success = nms_ibft_reader_load_blocks (iscsiadm_path, &blocks, &error);
 	g_assert_no_error (error);
diff --git a/src/settings/plugins/ifcfg-rh/meson.build b/src/settings/plugins/ifcfg-rh/meson.build
new file mode 100644
index 00000000..fdf308be
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/meson.build
@@ -0,0 +1,77 @@
+install_data(
+  'nm-ifcfg-rh.conf',
+  install_dir: dbus_conf_dir
+)
+
+cflags = [
+  '-DSBINDIR="@0@"'.format(nm_sbindir),
+  '-DSYSCONFDIR="@0@"'.format(nm_sysconfdir)
+]
+
+name = 'nmdbus-ifcfg-rh'
+
+dbus_sources = gnome.gdbus_codegen(
+  name,
+  'nm-ifcfg-rh.xml',
+  interface_prefix: 'com.redhat',
+  namespace: 'NMDBus'
+)
+
+libnmdbus_ifcfg_rh = static_library(
+  name,
+  sources: dbus_sources,
+  dependencies: glib_dep,
+  c_args: cflags
+)
+
+sources = files(
+  'nm-inotify-helper.c',
+  'nms-ifcfg-rh-reader.c',
+  'nms-ifcfg-rh-utils.c',
+  'nms-ifcfg-rh-writer.c',
+  'shvar.c'
+)
+
+deps = [
+  crypto_dep,
+  nm_dep
+]
+
+libnms_ifcfg_rh_core = static_library(
+  'nms-ifcfg-rh-core',
+  sources: sources,
+  dependencies: deps,
+  c_args: cflags
+)
+
+sources = [dbus_sources] + files('nms-ifcfg-rh-connection.c')
+
+libnm_settings_plugin_ifcfg_rh = shared_module(
+  'nm-settings-plugin-ifcfg-rh',
+  sources: sources,
+  dependencies: deps,
+  c_args: cflags,
+  link_with: [libnms_ifcfg_rh_core],
+  link_args: ldflags_linker_script_settings,
+  link_depends: linker_script_settings,
+  install: true,
+  install_dir: nm_pkglibdir
+)
+
+core_plugins += libnm_settings_plugin_ifcfg_rh
+
+# FIXME: check_so_symbols replacement
+'''
+run_target(
+  'check-local-symbols-settings-ifcfg-rh',
+  command: [check_so_symbols, libnm_settings_plugin_ifcfg_rh.full_path()],
+  depends: libnm_settings_plugin_ifcfg_rh
+)
+
+check-local-symbols-settings-ifcfg-rh: src/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la
+  $(call check_so_symbols,$(builddir)/src/settings/plugins/ifcfg-rh/.libs/libnm-settings-plugin-ifcfg-rh.so)
+'''
+
+if enable_tests
+  subdir('tests')
+endif
diff --git a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
index 97417db9..2863df64 100644
--- a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
+++ b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
@@ -172,7 +172,7 @@ nm_inotify_helper_init (NMInotifyHelper *self)
 {
 	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
 
-	priv->wd_refs = g_hash_table_new (g_direct_hash, g_direct_equal);
+	priv->wd_refs = g_hash_table_new (nm_direct_hash, NULL);
 }
 
 static void
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 0743fc9f..c7207297 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -37,7 +37,6 @@
 #include "settings/nm-settings-plugin.h"
 #include "nm-config.h"
 #include "NetworkManagerUtils.h"
-#include "nm-exported-object.h"
 
 #include "nms-ifcfg-rh-connection.h"
 #include "nms-ifcfg-rh-common.h"
@@ -46,10 +45,10 @@
 #include "nms-ifcfg-rh-utils.h"
 #include "shvar.h"
 
-#include "settings/plugins/ifcfg-rh/nmdbus-ifcfg-rh.h"
-
-#define IFCFGRH1_DBUS_SERVICE_NAME "com.redhat.ifcfgrh1"
-#define IFCFGRH1_DBUS_OBJECT_PATH "/com/redhat/ifcfgrh1"
+#define IFCFGRH1_BUS_NAME                               "com.redhat.ifcfgrh1"
+#define IFCFGRH1_OBJECT_PATH                            "/com/redhat/ifcfgrh1"
+#define IFCFGRH1_IFACE1_NAME                            "com.redhat.ifcfgrh1"
+#define IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS        "GetIfcfgDetails"
 
 /*****************************************************************************/
 
@@ -58,9 +57,9 @@ typedef struct {
 
 	struct {
 		GDBusConnection *connection;
-		GDBusInterfaceSkeleton *interface;
 		GCancellable *cancellable;
 		gulong signal_id;
+		guint regist_id;
 	} dbus;
 
 	GHashTable *connections;  /* uuid::connection */
@@ -327,21 +326,21 @@ update_connection (SettingsPluginIfcfg *self,
 
 			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));
-					/* Remove the path so that claim_connection() doesn't complain later when
-					 * interface gets managed and connection is re-added. */
-					nm_connection_set_path (NM_CONNECTION (connection_by_uuid), NULL);
 
 					/* 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_connection_get_uuid (NM_CONNECTION (connection_by_uuid))),
-					                     connection_by_uuid);
+					                     connection_by_uuid /* we took reference above and pass it on */);
 				}
 			} else {
 				if (old_unmanaged /* && !new_unmanaged */) {
@@ -373,7 +372,9 @@ update_connection (SettingsPluginIfcfg *self,
 			_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);
+		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),
@@ -516,7 +517,7 @@ read_connections (SettingsPluginIfcfg *plugin)
 		return;
 	}
 
-	alive_connections = g_hash_table_new (NULL, NULL);
+	alive_connections = g_hash_table_new (nm_direct_hash, NULL);
 
 	filenames = g_ptr_array_new_with_free_func (g_free);
 	while ((item = g_dir_read_name (dir))) {
@@ -749,7 +750,7 @@ impl_ifcfgrh_get_ifcfg_details (SettingsPluginIfcfg *plugin,
 		return;
 	}
 
-	path = nm_connection_get_path (NM_CONNECTION (connection));
+	path = nm_dbus_object_get_path (NM_DBUS_OBJECT (connection));
 	if (!path) {
 		g_dbus_method_invocation_return_error (context,
 		                                       NM_SETTINGS_ERROR,
@@ -768,15 +769,15 @@ static void
 _dbus_clear (SettingsPluginIfcfg *self)
 {
 	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	guint id;
 
 	nm_clear_g_signal_handler (priv->dbus.connection, &priv->dbus.signal_id);
 
 	nm_clear_g_cancellable (&priv->dbus.cancellable);
 
-	if (priv->dbus.interface) {
-		g_dbus_interface_skeleton_unexport (priv->dbus.interface);
-		nm_exported_object_skeleton_release (priv->dbus.interface);
-		priv->dbus.interface = NULL;
+	if ((id = nm_steal_int (&priv->dbus.regist_id))) {
+		if (!g_dbus_connection_unregister_object (priv->dbus.connection, id))
+			_LOGW ("dbus: unexpected failure to unregister object");
 	}
 
 	g_clear_object (&priv->dbus.connection);
@@ -788,13 +789,56 @@ _dbus_connection_closed (GDBusConnection *connection,
                          GError          *error,
                          gpointer         user_data)
 {
-	_LOGW ("dbus: %s bus closed", IFCFGRH1_DBUS_SERVICE_NAME);
+	_LOGW ("dbus: %s bus closed", IFCFGRH1_BUS_NAME);
 	_dbus_clear (SETTINGS_PLUGIN_IFCFG (user_data));
 
 	/* Retry or recover? */
 }
 
 static void
+_method_call (GDBusConnection *connection,
+              const char *sender,
+              const char *object_path,
+              const char *interface_name,
+              const char *method_name,
+              GVariant *parameters,
+              GDBusMethodInvocation *invocation,
+              gpointer user_data)
+{
+	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;
+	}
+
+	g_variant_get (parameters, "(&s)", &ifcfg);
+	impl_ifcfgrh_get_ifcfg_details (self, invocation, ifcfg);
+}
+
+static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO (
+	IFCFGRH1_BUS_NAME,
+	.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+		NM_DEFINE_GDBUS_METHOD_INFO (
+			IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS,
+			.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+				NM_DEFINE_GDBUS_ARG_INFO ("ifcfg", "s"),
+			),
+			.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+				NM_DEFINE_GDBUS_ARG_INFO ("uuid", "s"),
+				NM_DEFINE_GDBUS_ARG_INFO ("path", "o"),
+			),
+		),
+	),
+);
+
+static void
 _dbus_request_name_done (GObject *source_object,
                          GAsyncResult *res,
                          gpointer user_data)
@@ -830,36 +874,27 @@ _dbus_request_name_done (GObject *source_object,
 	}
 
 	{
-		GType skeleton_type = NMDBUS_TYPE_IFCFGRH1_SKELETON;
-		gs_free char *method_name_get_ifcfg_details = NULL;
-		NMExportedObjectDBusMethodImpl methods[] = {
-			{
-				.method_name = (method_name_get_ifcfg_details = nm_exported_object_skeletonify_method_name ("GetIfcfgDetails")),
-				.impl = G_CALLBACK (impl_ifcfgrh_get_ifcfg_details),
-			},
+		static const GDBusInterfaceVTable interface_vtable = {
+			.method_call = _method_call,
 		};
 
-		priv->dbus.interface = nm_exported_object_skeleton_create (skeleton_type,
-		                                                           g_type_class_peek (SETTINGS_TYPE_PLUGIN_IFCFG),
-		                                                           methods,
-		                                                           G_N_ELEMENTS (methods),
-		                                                           (GObject *) self);
-
-		if (!g_dbus_interface_skeleton_export (priv->dbus.interface,
-		                                       priv->dbus.connection,
-		                                       IFCFGRH1_DBUS_OBJECT_PATH,
-		                                       &error)) {
-			nm_exported_object_skeleton_release (priv->dbus.interface);
-			priv->dbus.interface = NULL;
-			_LOGW ("dbus: failed exporting interface: %s", error->message);
+		priv->dbus.regist_id = g_dbus_connection_register_object (connection,
+		                                                          IFCFGRH1_OBJECT_PATH,
+		                                                          interface_info,
+		                                                          NM_UNCONST_PTR (GDBusInterfaceVTable, &interface_vtable),
+		                                                          self,
+		                                                          NULL,
+		                                                          &error);
+		if (!priv->dbus.regist_id) {
+			_LOGW ("dbus: couldn't register D-Bus service: %s", error->message);
 			_dbus_clear (self);
 			return;
 		}
 	}
 
 	_LOGD ("dbus: aquired D-Bus service %s and exported %s object",
-	       IFCFGRH1_DBUS_SERVICE_NAME,
-	       IFCFGRH1_DBUS_OBJECT_PATH);
+	       IFCFGRH1_BUS_NAME,
+	       IFCFGRH1_OBJECT_PATH);
 }
 
 static void
@@ -900,7 +935,7 @@ _dbus_create_done (GObject *source_object,
 	                        DBUS_INTERFACE_DBUS,
 	                        "RequestName",
 	                        g_variant_new ("(su)",
-	                                       IFCFGRH1_DBUS_SERVICE_NAME,
+	                                       IFCFGRH1_BUS_NAME,
 	                                       DBUS_NAME_FLAG_DO_NOT_QUEUE),
 	                        G_VARIANT_TYPE ("(u)"),
 	                        G_DBUS_CALL_FLAGS_NONE,
@@ -917,7 +952,7 @@ _dbus_setup (SettingsPluginIfcfg *self)
 	gs_free char *address = NULL;
 	gs_free_error GError *error = NULL;
 
-	g_return_if_fail (!priv->dbus.connection);
+	_dbus_clear (self);
 
 	address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
 	if (address == NULL) {
@@ -943,17 +978,22 @@ config_changed_cb (NMConfig *config,
                    NMConfigData *old_data,
                    SettingsPluginIfcfg *self)
 {
+	SettingsPluginIfcfgPrivate *priv;
+
 	/* If the dbus connection for some reason is borked the D-Bus service
 	 * won't be offered.
 	 *
 	 * On SIGHUP and SIGUSR1 try to re-connect to D-Bus. So in the unlikely
 	 * event that the D-Bus conneciton is broken, that allows for recovery
 	 * without need for restarting NetworkManager. */
-	if (NM_FLAGS_ANY (changes,   NM_CONFIG_CHANGE_CAUSE_SIGHUP
-	                           | NM_CONFIG_CHANGE_CAUSE_SIGUSR1)) {
-		if (!SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self)->dbus.connection)
-			_dbus_setup (self);
-	}
+	if (!NM_FLAGS_ANY (changes,   NM_CONFIG_CHANGE_CAUSE_SIGHUP
+	                            | NM_CONFIG_CHANGE_CAUSE_SIGUSR1))
+		return;
+
+	priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	if (   !priv->dbus.connection
+	    && !priv->dbus.cancellable)
+		_dbus_setup (self);
 }
 
 /*****************************************************************************/
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 c91cd253..6ef3f660 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -181,7 +181,7 @@ make_connection_setting (const char *file,
 	const char *v;
 	gs_free char *stable_id = NULL;
 	const char *const *iter;
-	int vint64;
+	int vint64, i_val;
 
 	ifcfg_name = utils_get_ifcfg_name (file, TRUE);
 	if (!ifcfg_name)
@@ -338,6 +338,13 @@ make_connection_setting (const char *file,
 	vint64 = svGetValueInt64 (ifcfg, "AUTH_RETRIES", 10, -1, G_MAXINT32, -1);
 	g_object_set (s_con, NM_SETTING_CONNECTION_AUTH_RETRIES, (gint) vint64, NULL);
 
+	i_val = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+	if (!svGetValueEnum (ifcfg, "MDNS",
+	                     nm_setting_connection_mdns_get_type (),
+	                     &i_val, NULL))
+		PARSE_WARNING ("invalid MDNS setting");
+	g_object_set (s_con, NM_SETTING_CONNECTION_MDNS, i_val, NULL);
+
 	return NM_SETTING (s_con);
 }
 
@@ -927,7 +934,7 @@ next:
 			                                                    : ""));
 			break;
 		case PARSE_LINE_TYPE_FLAG:
-			/* XXX: the flag (for "onlink") only allows to explictly set "TRUE".
+			/* NOTE: the flag (for "onlink") only allows to explictly set "TRUE".
 			 * There is no way to express an explicit "FALSE" setting
 			 * of this attribute, hence, the file format cannot encode
 			 * that configuration. */
@@ -1338,29 +1345,7 @@ make_ip4_setting (shvarFile *ifcfg,
 	} else if (!g_ascii_strcasecmp (v, "autoip")) {
 		method = NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL;
 	} else if (!g_ascii_strcasecmp (v, "shared")) {
-		int idx;
-
-		g_object_set (s_ip4,
-		              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_SHARED,
-		              NM_SETTING_IP_CONFIG_NEVER_DEFAULT, never_default,
-		              NULL);
-		/* 1 IP address is allowed for shared connections. Read it. */
-		if (is_any_ip4_address_defined (ifcfg, &idx)) {
-			guint32 gw;
-			NMIPAddress *addr = NULL;
-
-			if (!read_full_ip4_address (ifcfg, idx, NULL, &addr, NULL, error))
-				return NULL;
-			if (!read_ip4_address (ifcfg, "GATEWAY", NULL, &gw, error))
-				return NULL;
-			(void) nm_setting_ip_config_add_address (s_ip4, addr);
-			nm_ip_address_unref (addr);
-			if (never_default)
-				PARSE_WARNING ("GATEWAY will be ignored when DEFROUTE is disabled");
-			gateway = g_strdup (nm_utils_inet4_ntop (gw, inet_buf));
-			g_object_set (s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway, NULL);
-		}
-		return g_steal_pointer (&s_ip4);
+		method = NM_SETTING_IP4_CONFIG_METHOD_SHARED;
 	} else {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Unknown BOOTPROTO '%s'", v);
@@ -1387,7 +1372,7 @@ make_ip4_setting (shvarFile *ifcfg,
 	              NM_SETTING_IP_CONFIG_ROUTE_TABLE, (guint) route_table,
 	              NULL);
 
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0)
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
 		return g_steal_pointer (&s_ip4);
 
 	/* Handle DHCP settings */
@@ -1464,39 +1449,47 @@ make_ip4_setting (shvarFile *ifcfg,
 	if (gateway && never_default)
 		PARSE_WARNING ("GATEWAY will be ignored when DEFROUTE is disabled");
 
-	/* DNS servers
-	 * Pick up just IPv4 addresses (IPv6 addresses are taken by make_ip6_setting())
-	 */
-	for (i = 1; i <= 10; i++) {
-		char tag[256];
-
-		numbered_tag (tag, "DNS", i);
-		nm_clear_g_free (&value);
-		v = svGetValueStr (ifcfg, tag, &value);
-		if (v) {
-			if (nm_utils_ipaddr_valid (AF_INET, v)) {
-				if (!nm_setting_ip_config_add_dns (s_ip4, v))
-					PARSE_WARNING ("duplicate DNS server %s", tag);
-			} else if (nm_utils_ipaddr_valid (AF_INET6, v)) {
-				/* Ignore IPv6 addresses */
-			} else {
-				PARSE_WARNING ("invalid DNS server address %s", v);
-				return NULL;
+	/* We used to skip saving a lot of unused properties for the ipv4 shared method.
+	 * We want now to persist them but... unfortunately loading DNS or DOMAIN options
+	 * would cause a fail in the ipv4 verify() function. As we don't want any regression
+	 * in the unlikely event that someone has a working ifcfg file for an IPv4 shared ip
+	 * connection with a crafted "DNS" entry... don't load it. So we will avoid failing
+	 * the connection) */
+	if (!nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
+		/* DNS servers
+		 * Pick up just IPv4 addresses (IPv6 addresses are taken by make_ip6_setting())
+		 */
+		for (i = 1; i <= 10; i++) {
+			char tag[256];
+
+			numbered_tag (tag, "DNS", i);
+			nm_clear_g_free (&value);
+			v = svGetValueStr (ifcfg, tag, &value);
+			if (v) {
+				if (nm_utils_ipaddr_valid (AF_INET, v)) {
+					if (!nm_setting_ip_config_add_dns (s_ip4, v))
+						PARSE_WARNING ("duplicate DNS server %s", tag);
+				} else if (nm_utils_ipaddr_valid (AF_INET6, v)) {
+					/* Ignore IPv6 addresses */
+				} else {
+					PARSE_WARNING ("invalid DNS server address %s", v);
+					return NULL;
+				}
 			}
 		}
-	}
 
-	/* DNS searches */
-	nm_clear_g_free (&value);
-	v = svGetValueStr (ifcfg, "DOMAIN", &value);
-	if (v) {
-		gs_free const char **searches = NULL;
+		/* DNS searches */
+		nm_clear_g_free (&value);
+		v = svGetValueStr (ifcfg, "DOMAIN", &value);
+		if (v) {
+			gs_free const char **searches = NULL;
 
-		searches = nm_utils_strsplit_set (v, " ");
-		if (searches) {
-			for (item = searches; *item; item++) {
-				if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
-					PARSE_WARNING ("duplicate DNS domain '%s'", *item);
+			searches = nm_utils_strsplit_set (v, " ");
+			if (searches) {
+				for (item = searches; *item; item++) {
+					if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
+						PARSE_WARNING ("duplicate DNS domain '%s'", *item);
+				}
 			}
 		}
 	}
@@ -1545,7 +1538,8 @@ make_ip4_setting (shvarFile *ifcfg,
 	}
 
 	/* Legacy value NM used for a while but is incorrect (rh #459370) */
-	if (!nm_setting_ip_config_get_num_dns_searches (s_ip4)) {
+	if (   !nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)
+	    && !nm_setting_ip_config_get_num_dns_searches (s_ip4)) {
 		nm_clear_g_free (&value);
 		v = svGetValueStr (ifcfg, "SEARCH", &value);
 		if (v) {
@@ -1561,10 +1555,14 @@ make_ip4_setting (shvarFile *ifcfg,
 		}
 	}
 
-	timeout = svGetValueInt64 (ifcfg, "ARPING_WAIT", 10, -1,
-	                           NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX / 1000, -1);
-	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_DAD_TIMEOUT,
-	              (gint) (timeout <= 0 ? timeout : timeout * 1000), NULL);
+	timeout = svGetValueInt64 (ifcfg, "ACD_TIMEOUT", 10, -1, NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX, -2);
+	if (timeout == -2) {
+		timeout = svGetValueInt64 (ifcfg, "ARPING_WAIT", 10, -1,
+		                           NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX / 1000, -1);
+		if (timeout > 0)
+			timeout *= 1000;
+	}
+	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_DAD_TIMEOUT, (gint) timeout, NULL);
 
 	return g_steal_pointer (&s_ip4);
 }
@@ -1642,7 +1640,7 @@ read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *file
 			                            read_defroute ? &gateway : NULL,
 			                            &err);
 			if (ok) {
-				nm_ip_address_set_attribute (addr, "label", g_variant_new_string (device));
+				nm_ip_address_set_attribute (addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL, g_variant_new_string (device));
 				if (!nm_setting_ip_config_add_address (s_ip4, addr))
 					PARSE_WARNING ("duplicate IP4 address in alias file %s", item);
 				if (nm_streq0 (nm_setting_ip_config_get_method (s_ip4), NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
@@ -2001,11 +1999,15 @@ make_tc_setting (shvarFile *ifcfg)
 			break;
 
 		qdisc = nm_utils_tc_qdisc_from_str (value, &local);
-		if (!qdisc)
-			PARSE_WARNING ("ignoring bad qdisc: '%s': %s", value, local->message);
+		if (!qdisc) {
+			PARSE_WARNING ("ignoring bad tc qdisc: '%s': %s", value, local->message);
+			continue;
+		}
 
 		if (!nm_setting_tc_config_add_qdisc (s_tc, qdisc))
-			PARSE_WARNING ("duplicate qdisc");
+			PARSE_WARNING ("duplicate tc qdisc");
+
+		nm_tc_qdisc_unref (qdisc);
 	}
 
 	for (i = 1;; i++) {
@@ -2019,11 +2021,15 @@ make_tc_setting (shvarFile *ifcfg)
 			break;
 
 		tfilter = nm_utils_tc_tfilter_from_str (value, &local);
-		if (!tfilter)
-			PARSE_WARNING ("ignoring bad tfilter: '%s': %s", value, local->message);
+		if (!tfilter) {
+			PARSE_WARNING ("ignoring bad tc filter: '%s': %s", value, local->message);
+			continue;
+		}
 
 		if (!nm_setting_tc_config_add_tfilter (s_tc, tfilter))
-			PARSE_WARNING ("duplicate filter");
+			PARSE_WARNING ("duplicate tc filter");
+
+		nm_tc_tfilter_unref (tfilter);
 	}
 
 	if (   nm_setting_tc_config_get_num_qdiscs (s_tc) > 0
@@ -3552,6 +3558,13 @@ make_wpa_setting (shvarFile *ifcfg,
 		return NULL;
 	g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_PMF, i_val, NULL);
 
+	i_val = NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT;
+	if (!svGetValueEnum (ifcfg, "FILS",
+	                     nm_setting_wireless_security_fils_get_type (),
+	                     &i_val, error))
+		return NULL;
+	g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_FILS, i_val, NULL);
+
 	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "SECURITYMODE", &value);
 	if (NM_IN_STRSET (v, NULL, "open"))
@@ -5321,6 +5334,8 @@ connection_from_file_full (const char *filename,
 	g_return_val_if_fail (filename != NULL, NULL);
 	g_return_val_if_fail (out_unhandled && !*out_unhandled, NULL);
 
+	NM_SET_OUT (out_ignore_error, FALSE);
+
 	/* Non-NULL only for unit tests; normally use /etc/sysconfig/network */
 	if (!network_file)
 		network_file = SYSCONFDIR "/sysconfig/network";
@@ -5341,6 +5356,7 @@ connection_from_file_full (const char *filename,
 	if (!svGetValueBoolean (parsed, "NM_CONTROLLED", TRUE)) {
 		connection = create_unhandled_connection (filename, parsed, "unmanaged", out_unhandled);
 		if (!connection) {
+			NM_SET_OUT (out_ignore_error, TRUE);
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 			             "NM_CONTROLLED was false but device was not uniquely identified; device will be managed");
 		}
@@ -5350,8 +5366,7 @@ connection_from_file_full (const char *filename,
 	/* iBFT is handled by the iBFT settings plugin */
 	bootproto = svGetValueStr_cp (parsed, "BOOTPROTO");
 	if (bootproto && !g_ascii_strcasecmp (bootproto, "ibft")) {
-		if (out_ignore_error)
-			*out_ignore_error = TRUE;
+		NM_SET_OUT (out_ignore_error, TRUE);
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Ignoring iBFT configuration");
 		g_free (bootproto);
@@ -5397,8 +5412,7 @@ connection_from_file_full (const char *filename,
 		char *device;
 
 		if ((tmp = svGetValueStr_cp (parsed, "IPV6TUNNELIPV4"))) {
-			if (out_ignore_error)
-				*out_ignore_error = TRUE;
+			NM_SET_OUT (out_ignore_error, TRUE);
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Ignoring unsupported connection due to IPV6TUNNELIPV4");
 			return NULL;
@@ -5412,8 +5426,7 @@ connection_from_file_full (const char *filename,
 		}
 
 		if (!strcmp (device, "lo")) {
-			if (out_ignore_error)
-				*out_ignore_error = TRUE;
+			NM_SET_OUT (out_ignore_error, TRUE);
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Ignoring loopback device config.");
 			g_free (device);
@@ -5460,8 +5473,7 @@ connection_from_file_full (const char *filename,
 					memcpy (p_path, IFUP_PATH_PREFIX, NM_STRLEN (IFUP_PATH_PREFIX));
 					if (access (p_path, X_OK) == 0) {
 						/* for all other types, this is not something we want to handle. */
-						if (out_ignore_error)
-							*out_ignore_error = TRUE;
+						NM_SET_OUT (out_ignore_error, TRUE);
 						g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 						             "Ignore script for unknown device type which has a matching %s script",
 						             p_path);
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 e9dd08b7..8584772e 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -148,16 +148,7 @@ write_secrets (shvarFile *ifcfg,
 	/* we purge all existing secrets. */
 	svUnsetAll (keyfile, SV_KEY_TYPE_ANY);
 
-	/* sort the keys. */
-	secrets_keys = (const char **) g_hash_table_get_keys_as_array (secrets, &secrets_keys_n);
-	if (secrets_keys_n > 1) {
-		g_qsort_with_data (secrets_keys,
-		                   secrets_keys_n,
-		                   sizeof (const char *),
-		                   nm_strcmp_p_with_data,
-		                   NULL);
-	}
-
+	secrets_keys = nm_utils_strdict_get_keys (secrets, TRUE, &secrets_keys_n);
 	for (i = 0; i < secrets_keys_n; i++) {
 		const char *k = secrets_keys[i];
 		const char *v = g_hash_table_lookup (secrets, k);
@@ -794,6 +785,13 @@ write_wireless_security_setting (NMConnection *connection,
 		                nm_setting_wireless_security_get_pmf (s_wsec));
 	}
 
+	if (nm_setting_wireless_security_get_fils (s_wsec) == NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT)
+		svUnsetValue (ifcfg, "FILS");
+	else {
+		svSetValueEnum (ifcfg, "FILS", nm_setting_wireless_security_fils_get_type (),
+		                nm_setting_wireless_security_get_fils (s_wsec));
+	}
+
 	return TRUE;
 }
 
@@ -899,14 +897,16 @@ write_wireless_setting (NMConnection *connection,
 	}
 
 	mode = nm_setting_wireless_get_mode (s_wireless);
-	if (!mode || !strcmp (mode, "infrastructure")) {
+	if (!mode)
+		svUnsetValue(ifcfg, "MODE");
+	else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_INFRA))
 		svSetValueStr (ifcfg, "MODE", "Managed");
-	} else if (!strcmp (mode, "adhoc")) {
+	else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
 		svSetValueStr (ifcfg, "MODE", "Ad-Hoc");
 		adhoc = TRUE;
-	} else if (!strcmp (mode, "ap")) {
+	} else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_AP))
 		svSetValueStr (ifcfg, "MODE", "Ap");
-	} else {
+	else {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "Invalid mode '%s' in '%s' setting",
 		             mode, NM_SETTING_WIRELESS_SETTING_NAME);
@@ -1728,6 +1728,7 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	GString *str;
 	const char *master, *master_iface = NULL, *type;
 	gint vint;
+	NMSettingConnectionMdns mdns;
 	guint32 vuint32;
 	const char *tmp;
 
@@ -1749,9 +1750,7 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 
 	/* Only save the value for master connections */
 	type = nm_setting_connection_get_connection_type (s_con);
-	if (   !g_strcmp0 (type, NM_SETTING_BOND_SETTING_NAME)
-	    || !g_strcmp0 (type, NM_SETTING_TEAM_SETTING_NAME)
-	    || !g_strcmp0 (type, NM_SETTING_BRIDGE_SETTING_NAME)) {
+	if (_nm_connection_type_is_master (type)) {
 		NMSettingConnectionAutoconnectSlaves autoconnect_slaves;
 		autoconnect_slaves = nm_setting_connection_get_autoconnect_slaves (s_con);
 		svSetValueStr (ifcfg, "AUTOCONNECT_SLAVES",
@@ -1888,6 +1887,13 @@ 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);
+
+	mdns = nm_setting_connection_get_mdns (s_con);
+	if (mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) {
+		svSetValueEnum (ifcfg, "MDNS", nm_setting_connection_mdns_get_type (),
+		                mdns);
+	} else
+		svUnsetValue (ifcfg, "MDNS");
 }
 
 static char *
@@ -2281,7 +2287,7 @@ write_ip4_setting (NMConnection *connection,
 		if (i > 0) {
 			GVariant *label;
 
-			label = nm_ip_address_get_attribute (addr, "label");
+			label = nm_ip_address_get_attribute (addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL);
 			if (label)
 				continue;
 		}
@@ -2411,12 +2417,15 @@ write_ip4_setting (NMConnection *connection,
 	NM_SET_OUT (out_route_content, write_route_file (s_ip4));
 
 	timeout = nm_setting_ip_config_get_dad_timeout (s_ip4);
-	if (timeout < 0)
+	if (timeout < 0) {
+		svUnsetValue (ifcfg, "ACD_TIMEOUT");
 		svUnsetValue (ifcfg, "ARPING_WAIT");
-	else if (timeout == 0)
+	} else if (timeout == 0) {
+		svSetValueStr (ifcfg, "ACD_TIMEOUT", "0");
 		svSetValueStr (ifcfg, "ARPING_WAIT", "0");
-	else {
-		/* Round the value up to next integer */
+	} else {
+		svSetValueInt64 (ifcfg, "ACD_TIMEOUT", timeout);
+		/* Round the value up to next integer for initscripts */
 		svSetValueInt64 (ifcfg, "ARPING_WAIT", (timeout - 1) / 1000 + 1);
 	}
 
@@ -2484,7 +2493,7 @@ write_ip4_aliases (NMConnection *connection, const char *base_ifcfg_path)
 
 		addr = nm_setting_ip_config_get_address (s_ip4, i);
 
-		label_var = nm_ip_address_get_attribute (addr, "label");
+		label_var = nm_ip_address_get_attribute (addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL);
 		if (!label_var)
 			continue;
 		label = g_variant_get_string (label_var, NULL);
@@ -2987,7 +2996,7 @@ do_write_to_disk (NMConnection *connection,
 {
 	/* From here on, we persist data to disk. Before, it was all in-memory
 	 * only. But we loaded the ifcfg files from disk, and managled our
-	 * new settings (in-momory). */
+	 * new settings (in-memory). */
 
 	if (!svWriteFile (ifcfg, 0644, error))
 		return FALSE;
@@ -3138,10 +3147,10 @@ nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
 	 * 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)) {
+		                      svFileGetName (ifcfg),
+		                      out_reread,
+		                      out_reread_same,
+		                      &local)) {
 			_LOGW ("write: failure to re-read connection \"%s\": %s",
 			       svFileGetName (ifcfg), local->message);
 			g_clear_error (&local);
@@ -3188,4 +3197,3 @@ nms_ifcfg_rh_writer_can_write_connection (NMConnection *connection, GError **err
 	             NM_PRINT_FMT_QUOTE_STRING (type));
 	return FALSE;
 }
-
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index 2b64f3fc..9120b870 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -39,7 +39,7 @@
 #include "nm-core-internal.h"
 #include "nm-core-utils.h"
 #include "nm-utils/nm-enum-utils.h"
-#include "nm-utils/c-list.h"
+#include "c-list/src/c-list.h"
 
 /*****************************************************************************/
 
@@ -1278,7 +1278,7 @@ svSetValueEnum (shvarFile *s, const char *key, GType gtype, int value)
 {
 	gs_free char *v = NULL;
 
-	v = _nm_utils_enum_to_str_full (gtype, value, " ");
+	v = _nm_utils_enum_to_str_full (gtype, value, " ", NULL);
 	return svSetValueStr (s, key, v);
 }
 
diff --git a/src/settings/plugins/ifcfg-rh/tests/meson.build b/src/settings/plugins/ifcfg-rh/tests/meson.build
new file mode 100644
index 00000000..3596b642
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/meson.build
@@ -0,0 +1,22 @@
+test_unit = 'test-ifcfg-rh'
+
+test_ifcfg_dir = meson.current_source_dir()
+
+cflags = [
+  '-DTEST_IFCFG_DIR="@0@"'.format(test_ifcfg_dir),
+  '-DTEST_SCRATCH_DIR="@0@"'.format(test_ifcfg_dir)
+]
+
+exe = executable(
+  test_unit,
+  test_unit + '.c',
+  dependencies: test_nm_dep,
+  c_args: cflags,
+  link_with: libnms_ifcfg_rh_core
+)
+
+test(
+  'ifcfg-rh/' + test_unit,
+  test_script,
+  args: test_args + [exe.full_path()]
+)
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected
index 026993b8..cf325f35 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected
@@ -1,5 +1,4 @@
 ESSID="Test SSID"
-MODE=Managed
 SSID_HIDDEN=yes
 MAC_ADDRESS_RANDOMIZATION=default
 TYPE=Wireless
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected
index c0e47c48..cd8fc96f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected
@@ -14,6 +14,8 @@ DNS2=4.2.2.2
 DOMAIN="foobar.com lab.foobar.com"
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
+ACD_TIMEOUT=400
+ARPING_WAIT=1
 IPV6INIT=no
 NAME="Test Write Wired Static Routes"
 UUID=${UUID}
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc
new file mode 100644
index 00000000..d0a3c254
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc
@@ -0,0 +1,16 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:55
+BOOTPROTO=none
+ONBOOT=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+PREFIX=24
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+IPV6INIT=no
+QDISC1="root fq_codel"
+FILTER1="parent 1234: matchall action simple sdata Hello"
+NAME=ethernet-tc
+UUID=a42c8d4e-11a2-4144-92d2-5cbce8c6b2c4
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected
new file mode 100644
index 00000000..a67ca598
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected
@@ -0,0 +1,16 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+QDISC1="parent 2468:2 pfifo_fast"
+FILTER1="parent 1234: matchall action simple sdata Hello"
+BOOTPROTO=none
+IPADDR=1.1.1.3
+PREFIX=24
+GATEWAY=1.1.1.1
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Test Write TC config"
+UUID=${UUID}
+DEVICE=eth0
+ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1 b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1
index 1bc3d524..db09afdb 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1
@@ -10,3 +10,5 @@ PREFIX1=16
 IPADDR2=3.3.3.3
 PREFIX2=8
 GATEWAY=1.1.1.1
+ACD_TIMEOUT=2000
+ARPING_WAIT=1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2 b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2
index d7273e36..6972e279 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2
@@ -9,3 +9,5 @@ IPADDR2=9.8.7.6
 PREFIX2=16
 IPADDR3=3.3.3.3
 PREFIX3=8
+ACD_TIMEOUT=2000
+ARPING_WAIT=1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3 b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3
index f2457bd2..d9065994 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3
@@ -9,3 +9,5 @@ IPADDR3=9.8.7.6
 PREFIX3=16
 IPADDR4=3.3.3.3
 PREFIX4=8
+ACD_TIMEOUT=2000
+ARPING_WAIT=1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4 b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4
index e6b77141..935267f7 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4
@@ -9,3 +9,5 @@ IPADDR1=9.8.7.6
 PREFIX1=16
 IPADDR2=3.3.3.3
 PREFIX2=8
+ACD_TIMEOUT=2000
+ARPING_WAIT=1
diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index 6bf27556..57bd96e9 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -24,6 +24,7 @@
 #include <stdarg.h>
 #include <unistd.h>
 #include <string.h>
+#include <linux/pkt_sched.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -605,8 +606,7 @@ test_read_miscellaneous_variables (void)
 	int mac_blacklist_num, i;
 	guint64 expected_timestamp = 0;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*invalid MAC in HWADDR_BLACKLIST 'XX:aa:invalid'*");
+	NMTST_EXPECT_NM_WARN ("*invalid MAC in HWADDR_BLACKLIST 'XX:aa:invalid'*");
 	connection = _connection_from_file (TEST_IFCFG_DIR"/network-scripts/ifcfg-test-misc-variables",
 	                                    NULL, TYPE_ETHERNET, NULL);
 	g_test_assert_expected_messages ();
@@ -859,8 +859,7 @@ test_read_wired_static_no_prefix (gconstpointer user_data)
 	file = g_strdup_printf (TEST_IFCFG_DIR "/network-scripts/ifcfg-test-wired-static-no-prefix-%u", expected_prefix);
 	expected_id = g_strdup_printf ("System test-wired-static-no-prefix-%u", expected_prefix);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*missing PREFIX, assuming*");
+	NMTST_EXPECT_NM_WARN ("*missing PREFIX, assuming*");
 	connection = _connection_from_file (file, NULL, TYPE_ETHERNET, NULL);
 	g_test_assert_expected_messages ();
 
@@ -1081,8 +1080,7 @@ test_read_wired_global_gateway_ignore (void)
 	NMSettingIPConfig *s_ip4;
 	char *unmanaged = NULL;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring GATEWAY (/etc/sysconfig/network) for * because the connection has no static addresses");
+	NMTST_EXPECT_NM_WARN ("*ignoring GATEWAY (/etc/sysconfig/network) for * because the connection has no static addresses");
 	connection = _connection_from_file (TEST_IFCFG_DIR"/network-scripts/ifcfg-test-wired-global-gateway-ignore",
 	                                    TEST_IFCFG_DIR"/network-scripts/network-test-wired-global-gateway-ignore",
 	                                    TYPE_ETHERNET, &unmanaged);
@@ -1476,6 +1474,7 @@ test_read_wired_ipv4_manual (gconstpointer data)
 	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_dad_timeout (s_ip4), ==, 2000);
 
 	/* IP addresses */
 	g_assert_cmpint (nm_setting_ip_config_get_num_addresses (s_ip4), ==, 3);
@@ -1513,8 +1512,7 @@ test_read_wired_ipv6_manual (void)
 	NMIPAddress *ip6_addr;
 	NMIPRoute *ip6_route;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring manual default route*");
+	NMTST_EXPECT_NM_WARN ("*ignoring manual default route*");
 	connection = _connection_from_file (TEST_IFCFG_DIR"/network-scripts/ifcfg-test-wired-ipv6-manual",
 	                                    NULL, TYPE_ETHERNET, &unmanaged);
 	g_test_assert_expected_messages ();
@@ -1897,8 +1895,7 @@ test_read_write_802_1X_subj_matches (void)
 	gs_unref_object NMConnection *reread = NULL;
 	NMSetting8021x *s_8021x;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*missing IEEE_8021X_CA_CERT*peap*");
+	NMTST_EXPECT_NM_WARN ("*missing IEEE_8021X_CA_CERT*peap*");
 	connection = _connection_from_file (TEST_IFCFG_DIR"/network-scripts/ifcfg-test-wired-802-1X-subj-matches",
 	                                    NULL, TYPE_ETHERNET, NULL);
 	g_test_assert_expected_messages ();
@@ -1919,16 +1916,14 @@ test_read_write_802_1X_subj_matches (void)
 	g_assert_cmpstr (nm_setting_802_1x_get_phase2_altsubject_match (s_8021x, 0), ==, "x.yourdomain.tld");
 	g_assert_cmpstr (nm_setting_802_1x_get_phase2_altsubject_match (s_8021x, 1), ==, "y.yourdomain.tld");
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*missing IEEE_8021X_CA_CERT for EAP method 'peap'; this is insecure!");
+	NMTST_EXPECT_NM_WARN ("*missing IEEE_8021X_CA_CERT for EAP method 'peap'; this is insecure!");
 	_writer_new_connec_exp (connection,
 	                        TEST_SCRATCH_DIR "/network-scripts/",
 	                        TEST_IFCFG_DIR "/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected",
 	                        &testfile);
 	g_test_assert_expected_messages ();
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*missing IEEE_8021X_CA_CERT for EAP method 'peap'; this is insecure!");
+	NMTST_EXPECT_NM_WARN ("*missing IEEE_8021X_CA_CERT for EAP method 'peap'; this is insecure!");
 	reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
 	g_test_assert_expected_messages ();
 
@@ -2079,7 +2074,7 @@ test_read_wired_aliases_good (gconstpointer test_data)
 		g_assert (j < expected_num_addresses);
 
 		g_assert_cmpint (nm_ip_address_get_prefix (ip4_addr), ==, 24);
-		label = nm_ip_address_get_attribute (ip4_addr, "label");
+		label = nm_ip_address_get_attribute (ip4_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL);
 		if (expected_label[j])
 			g_assert_cmpstr (g_variant_get_string (label, NULL), ==, expected_label[j]);
 		else
@@ -2131,7 +2126,7 @@ test_read_wired_aliases_bad (const char *base, const char *expected_id)
 	g_assert (ip4_addr != NULL);
 	g_assert_cmpstr (nm_ip_address_get_address (ip4_addr), ==, "192.168.1.5");
 	g_assert_cmpint (nm_ip_address_get_prefix (ip4_addr), ==, 24);
-	g_assert (nm_ip_address_get_attribute (ip4_addr, "label") == NULL);
+	g_assert (nm_ip_address_get_attribute (ip4_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL) == NULL);
 
 	/* Gateway */
 	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, "192.168.1.1");
@@ -2142,16 +2137,14 @@ test_read_wired_aliases_bad (const char *base, const char *expected_id)
 static void
 test_read_wired_aliases_bad_1 (void)
 {
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*aliasem1:1*has no DEVICE*");
+	NMTST_EXPECT_NM_WARN ("*aliasem1:1*has no DEVICE*");
 	test_read_wired_aliases_bad (TEST_IFCFG_DIR "/network-scripts/ifcfg-aliasem1", "System aliasem1");
 }
 
 static void
 test_read_wired_aliases_bad_2 (void)
 {
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*aliasem2:1*has invalid DEVICE*");
+	NMTST_EXPECT_NM_WARN ("*aliasem2:1*has invalid DEVICE*");
 	test_read_wired_aliases_bad (TEST_IFCFG_DIR "/network-scripts/ifcfg-aliasem2", "System aliasem2");
 }
 
@@ -3522,7 +3515,6 @@ test_write_wifi_hidden (void)
 
 	g_object_set (s_wifi,
 	              NM_SETTING_WIRELESS_SSID, ssid,
-	              NM_SETTING_WIRELESS_MODE, "infrastructure",
 	              NM_SETTING_WIRELESS_HIDDEN, TRUE,
 	              NULL);
 
@@ -4774,6 +4766,7 @@ test_write_wired_static_routes (void)
 	g_object_set (s_ip4,
 	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
 	              NM_SETTING_IP_CONFIG_GATEWAY, "1.1.1.1",
+	              NM_SETTING_IP_CONFIG_DAD_TIMEOUT, 400,
 	              NULL);
 
 	addr = nm_ip_address_new (AF_INET, "1.1.1.3", 24, &error);
@@ -5141,7 +5134,7 @@ test_write_wired_aliases (void)
 		addr = nm_ip_address_new (AF_INET, ip[i], 24, &error);
 		g_assert_no_error (error);
 		if (label[i])
-			nm_ip_address_set_attribute (addr, "label", g_variant_new_string (label[i]));
+			nm_ip_address_set_attribute (addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL, g_variant_new_string (label[i]));
 		nm_setting_ip_config_add_address (s_ip4, addr);
 		nm_ip_address_unref (addr);
 	}
@@ -5200,9 +5193,9 @@ test_write_wired_aliases (void)
 		else {
 			g_assert_cmpint (nm_ip_address_get_prefix (addr), ==, 24);
 			if (label[j])
-				g_assert_cmpstr (g_variant_get_string (nm_ip_address_get_attribute (addr, "label"), NULL), ==, label[j]);
+				g_assert_cmpstr (g_variant_get_string (nm_ip_address_get_attribute (addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL), NULL), ==, label[j]);
 			else
-				g_assert (nm_ip_address_get_attribute (addr, "label") == NULL);
+				g_assert (nm_ip_address_get_attribute (addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL) == NULL);
 			ip[j] = NULL;
 		}
 	}
@@ -6285,7 +6278,9 @@ test_write_wifi_wpa_eap_tls (void)
 	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
 
-	g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL);
+	g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap",
+	              NM_SETTING_WIRELESS_SECURITY_FILS, (int) NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED,
+	              NULL);
 	nm_setting_wireless_security_add_proto (s_wsec, "wpa");
 	nm_setting_wireless_security_add_pairwise (s_wsec, "tkip");
 	nm_setting_wireless_security_add_group (s_wsec, "tkip");
@@ -7033,7 +7028,7 @@ test_write_wired_ctc_dhcp (void)
 	                        TEST_SCRATCH_DIR "/network-scripts/",
 	                        &testfile);
 
-	/* Ensure the CTCPROT item gets written out as it's own option */
+	/* Ensure the CTCPROT item gets written out as its own option */
 	ifcfg = _svOpenFile (testfile);
 
 	_svGetValue_check (ifcfg, "CTCPROT", "0");
@@ -7713,8 +7708,7 @@ test_read_vlan_reorder_hdr_1 (void)
 	NMConnection *connection;
 	NMSettingVlan *s_vlan;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*REORDER_HDR key is deprecated, use VLAN_FLAGS*");
+	NMTST_EXPECT_NM_WARN ("*REORDER_HDR key is deprecated, use VLAN_FLAGS*");
 	connection = _connection_from_file (TEST_IFCFG_DIR"/network-scripts/ifcfg-test-vlan-reorder-hdr-1",
 	                                        NULL, TYPE_ETHERNET, NULL);
 	g_test_assert_expected_messages ();
@@ -8508,8 +8502,7 @@ test_read_dcb_bad_booleans (void)
 {
 	gs_free_error GError *error = NULL;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*invalid DCB_PG_STRICT value*not all 0s and 1s*");
+	NMTST_EXPECT_NM_WARN ("*invalid DCB_PG_STRICT value*not all 0s and 1s*");
 	_connection_from_file_fail (TEST_IFCFG_DIR "/network-scripts/ifcfg-test-dcb-bad-booleans",
 	                            NULL, TYPE_ETHERNET, &error);
 	g_test_assert_expected_messages ();
@@ -8523,8 +8516,7 @@ test_read_dcb_short_booleans (void)
 {
 	gs_free_error GError *error = NULL;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*DCB_PG_STRICT value*8 characters*");
+	NMTST_EXPECT_NM_WARN ("*DCB_PG_STRICT value*8 characters*");
 	_connection_from_file_fail (TEST_IFCFG_DIR "/network-scripts/ifcfg-test-dcb-short-booleans",
 	                            NULL, TYPE_ETHERNET, &error);
 	g_test_assert_expected_messages ();
@@ -8538,8 +8530,7 @@ test_read_dcb_bad_uints (void)
 {
 	gs_free_error GError *error = NULL;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*invalid DCB_PG_UP2TC value*not 0 - 7*");
+	NMTST_EXPECT_NM_WARN ("*invalid DCB_PG_UP2TC value*not 0 - 7*");
 	_connection_from_file_fail (TEST_IFCFG_DIR "/network-scripts/ifcfg-test-dcb-bad-uints",
 	                            NULL, TYPE_ETHERNET, &error);
 	g_test_assert_expected_messages ();
@@ -8553,8 +8544,7 @@ test_read_dcb_short_uints (void)
 {
 	gs_free_error GError *error = NULL;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*DCB_PG_UP2TC value*8 characters*");
+	NMTST_EXPECT_NM_WARN ("*DCB_PG_UP2TC value*8 characters*");
 	_connection_from_file_fail (TEST_IFCFG_DIR "/network-scripts/ifcfg-test-dcb-short-uints",
 	                            NULL, TYPE_ETHERNET, &error);
 	g_test_assert_expected_messages ();
@@ -8568,8 +8558,7 @@ test_read_dcb_bad_percent (void)
 {
 	gs_free_error GError *error = NULL;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*invalid DCB_PG_PCT percentage value*");
+	NMTST_EXPECT_NM_WARN ("*invalid DCB_PG_PCT percentage value*");
 	_connection_from_file_fail (TEST_IFCFG_DIR "/network-scripts/ifcfg-test-dcb-bad-percent",
 	                            NULL, TYPE_ETHERNET, &error);
 	g_test_assert_expected_messages ();
@@ -8583,8 +8572,7 @@ test_read_dcb_short_percent (void)
 {
 	gs_free_error GError *error = NULL;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*invalid DCB_PG_PCT percentage list value*");
+	NMTST_EXPECT_NM_WARN ("*invalid DCB_PG_PCT percentage list value*");
 	_connection_from_file_fail (TEST_IFCFG_DIR "/network-scripts/ifcfg-test-dcb-short-percent",
 	                            NULL, TYPE_ETHERNET, &error);
 	g_test_assert_expected_messages ();
@@ -8598,8 +8586,7 @@ test_read_dcb_pgpct_not_100 (void)
 {
 	gs_free_error GError *error = NULL;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*DCB_PG_PCT percentages do not equal 100*");
+	NMTST_EXPECT_NM_WARN ("*DCB_PG_PCT percentages do not equal 100*");
 	_connection_from_file_fail (TEST_IFCFG_DIR "/network-scripts/ifcfg-test-dcb-pgpct-not-100",
 	                            NULL, TYPE_ETHERNET, &error);
 	g_test_assert_expected_messages ();
@@ -8720,7 +8707,7 @@ test_read_team_master_invalid (gconstpointer user_data)
 	NMSettingConnection *s_con;
 	NMSettingTeam *s_team;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*ignoring invalid team configuration*");
+	NMTST_EXPECT_NM_WARN ("*ignoring invalid team configuration*");
 	connection = _connection_from_file (PATH_NAME, NULL, TYPE_ETHERNET, NULL);
 	g_test_assert_expected_messages ();
 
@@ -9645,6 +9632,125 @@ test_utils_ignore (void)
 	do_test_utils_ignored ("ignored-augtmp", "ifcfg-FooBar" AUGTMP_TAG, TRUE);
 }
 
+static void
+test_tc_read (void)
+{
+	NMConnection *connection;
+	NMSettingTCConfig *s_tc;
+	NMTCQdisc *qdisc;
+	NMTCTfilter *filter;
+	char *str;
+
+	connection = _connection_from_file (TEST_IFCFG_DIR "/network-scripts/ifcfg-test-tc",
+	                                    NULL, TYPE_ETHERNET,NULL);
+
+	g_assert_cmpstr (nm_connection_get_interface_name (connection), ==, "eth0");
+
+	s_tc = nm_connection_get_setting_tc_config (connection);
+	g_assert (s_tc);
+
+	g_assert_cmpint (nm_setting_tc_config_get_num_qdiscs (s_tc), ==, 1);
+	qdisc = nm_setting_tc_config_get_qdisc (s_tc, 0);
+	g_assert (qdisc);
+	g_assert_cmpint (nm_tc_qdisc_get_parent (qdisc), ==, TC_H_ROOT);
+	g_assert_cmpint (nm_tc_qdisc_get_handle (qdisc), ==, TC_H_UNSPEC);
+	g_assert_cmpstr (nm_tc_qdisc_get_kind (qdisc), ==, "fq_codel");
+
+	g_assert_cmpint (nm_setting_tc_config_get_num_tfilters (s_tc), ==, 1);
+	filter = nm_setting_tc_config_get_tfilter (s_tc, 0);
+	g_assert (filter);
+	str = nm_utils_tc_tfilter_to_str (filter, NULL);
+	g_assert_cmpstr (str, ==, "parent 1234: matchall action simple sdata Hello");
+	g_free (str);
+
+	g_object_unref (connection);
+}
+
+static void
+test_tc_write (void)
+{
+	nmtst_auto_unlinkfile char *testfile = NULL;
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	NMSettingConnection *s_con;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
+	NMSettingWired *s_wired;
+	NMSettingTCConfig *s_tc;
+	NMTCQdisc *qdisc;
+	NMTCTfilter *tfilter;
+	NMIPAddress *addr;
+	GError *error = NULL;
+
+	connection = nm_simple_connection_new ();
+
+	/* Connection setting */
+	s_con = (NMSettingConnection *) nm_setting_connection_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_con));
+
+	g_object_set (s_con,
+	              NM_SETTING_CONNECTION_ID, "Test Write TC config",
+	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
+	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
+	              NM_SETTING_CONNECTION_INTERFACE_NAME, "eth0",
+	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME,
+	              NULL);
+
+	/* Wired setting */
+	s_wired = (NMSettingWired *) nm_setting_wired_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wired));
+
+	/* IP4 setting */
+	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+
+	g_object_set (s_ip4,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+	              NM_SETTING_IP_CONFIG_GATEWAY, "1.1.1.1",
+	              NM_SETTING_IP_CONFIG_MAY_FAIL, TRUE,
+	              NULL);
+
+	addr = nm_ip_address_new (AF_INET, "1.1.1.3", 24, &error);
+	g_assert_no_error (error);
+	nm_setting_ip_config_add_address (s_ip4, addr);
+	nm_ip_address_unref (addr);
+
+	/* IP6 setting */
+	s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip6));
+
+	g_object_set (s_ip6,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+	              NULL);
+
+	/* TC setting */
+	s_tc = (NMSettingTCConfig *) nm_setting_tc_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_tc));
+
+	qdisc = nm_tc_qdisc_new ("pfifo_fast", TC_H_MAKE (0x2468 << 16, 0x2), &error);
+	g_assert_no_error (error);
+	nm_setting_tc_config_add_qdisc (s_tc, qdisc);
+	nm_tc_qdisc_unref (qdisc);
+
+	tfilter = nm_utils_tc_tfilter_from_str ("parent 1234: matchall action simple sdata Hello", &error);
+	g_assert_no_error (error);
+	nm_setting_tc_config_add_tfilter (s_tc, tfilter);
+	nm_tc_tfilter_unref (tfilter);
+
+	nm_connection_add_setting (connection, nm_setting_proxy_new ());
+
+	nmtst_assert_connection_verifies_without_normalization (connection);
+
+	_writer_new_connec_exp (connection,
+	                        TEST_SCRATCH_DIR "/network-scripts/",
+	                        TEST_IFCFG_DIR "/network-scripts/ifcfg-test-tc-write.cexpected",
+	                        &testfile);
+
+	reread = _connection_from_file (testfile, NULL, TYPE_BOND, NULL);
+
+	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
+}
+
 /*****************************************************************************/
 
 #define TPATH "/settings/plugins/ifcfg-rh/"
@@ -9927,5 +10033,8 @@ int main (int argc, char **argv)
 	g_test_add_func (TPATH "utils/path", test_utils_path);
 	g_test_add_func (TPATH "utils/ignore", test_utils_ignore);
 
+	g_test_add_func (TPATH "tc/read", test_tc_read);
+	g_test_add_func (TPATH "tc/write", test_tc_write);
+
 	return g_test_run ();
 }
diff --git a/src/settings/plugins/ifnet/nms-ifnet-connection-parser.c b/src/settings/plugins/ifnet/nms-ifnet-connection-parser.c
deleted file mode 100644
index ed0a757f..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-connection-parser.c
+++ /dev/null
@@ -1,2912 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ifnet-connection-parser.h"
-
-#include <string.h>
-#include <arpa/inet.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include "settings/nm-settings-plugin.h"
-#include "nm-core-internal.h"
-#include "NetworkManagerUtils.h"
-#include "nm-meta-setting.h"
-
-#include "nms-ifnet-net-utils.h"
-#include "nms-ifnet-wpa-parser.h"
-#include "nms-ifnet-connection.h"
-
-static char *
-connection_id_from_ifnet_name (const char *conn_name)
-{
-	int name_len = strlen (conn_name);
-
-	/* Convert a hex-encoded conn_name (only used for wifi SSIDs) to human-readable one */
-	if ((name_len > 2) && (g_str_has_prefix (conn_name, "0x"))) {
-		GBytes *bytes = nm_utils_hexstr2bin (conn_name);
-		char *buf;
-
-		if (bytes) {
-			buf = g_strndup (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes));
-			g_bytes_unref (bytes);
-			return buf;
-		}
-	}
-
-	return g_strdup (conn_name);
-}
-
-static gboolean eap_simple_reader (const char *eap_method,
-                                   const char *ssid,
-                                   NMSetting8021x *s_8021x,
-                                   gboolean phase2,
-                                   const char *basepath,
-                                   GError **error);
-
-static gboolean eap_tls_reader (const char *eap_method,
-                                const char *ssid,
-                                NMSetting8021x *s_8021x,
-                                gboolean phase2,
-                                const char *basepath,
-                                GError **error);
-
-static gboolean eap_peap_reader (const char *eap_method,
-                                 const char *ssid,
-                                 NMSetting8021x *s_8021x,
-                                 gboolean phase2,
-                                 const char *basepath,
-                                 GError **error);
-
-static gboolean eap_ttls_reader (const char *eap_method,
-                                 const char *ssid,
-                                 NMSetting8021x *s_8021x,
-                                 gboolean phase2,
-                                 const char *basepath,
-                                 GError **error);
-
-typedef struct {
-	const char *method;
-	 gboolean (*reader) (const char *eap_method,
-	                     const char *ssid,
-	                     NMSetting8021x *s_8021x,
-	                     gboolean phase2,
-	                     const char *basepath,
-	                     GError **error);
-	gboolean wifi_phase2_only;
-} EAPReader;
-
-static EAPReader eap_readers[] = {
-	{"md5", eap_simple_reader, TRUE},
-	{"pwd", eap_simple_reader, TRUE},
-	{"pap", eap_simple_reader, TRUE},
-	{"chap", eap_simple_reader, TRUE},
-	{"mschap", eap_simple_reader, TRUE},
-	{"mschapv2", eap_simple_reader, TRUE},
-	{"leap", eap_simple_reader, TRUE},
-	{"tls", eap_tls_reader, FALSE},
-	{"peap", eap_peap_reader, FALSE},
-	{"ttls", eap_ttls_reader, FALSE},
-	{NULL, NULL}
-};
-
-/* reading identity and password */
-static gboolean
-eap_simple_reader (const char *eap_method,
-                   const char *ssid,
-                   NMSetting8021x *s_8021x,
-                   gboolean phase2,
-                   const char *basepath,
-                   GError **error)
-{
-	const char *value;
-
-	/* identity */
-	value = wpa_get_value (ssid, "identity");
-	if (!value) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing IEEE_8021X_IDENTITY for EAP method '%s'.",
-			     eap_method);
-		return FALSE;
-	}
-	g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, value, NULL);
-
-	/* password */
-	value = wpa_get_value (ssid, "password");
-	if (!value) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing IEEE_8021X_PASSWORD for EAP method '%s'.",
-			     eap_method);
-		return FALSE;
-	}
-
-	g_object_set (s_8021x, NM_SETTING_802_1X_PASSWORD, value, NULL);
-
-	return TRUE;
-}
-
-static char *
-get_cert (const char *ssid, const char *key, const char *basepath)
-{
-	const char *orig;
-
-	/* If it's a relative path, convert to absolute using 'basepath' */
-	orig = wpa_get_value (ssid, key);
-	if (g_path_is_absolute (orig))
-		return g_strdup (orig);
-	return g_strdup_printf ("%s/%s", basepath, orig);
-}
-
-static gboolean
-eap_tls_reader (const char *eap_method,
-                const char *ssid,
-                NMSetting8021x *s_8021x,
-                gboolean phase2,
-                const char *basepath,
-                GError **error)
-{
-	const char *value;
-	char *ca_cert = NULL;
-	char *client_cert = NULL;
-	char *privkey = NULL;
-	const char *privkey_password = NULL;
-	gboolean success = FALSE;
-	NMSetting8021xCKFormat privkey_format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
-
-	/* identity */
-	value = wpa_get_value (ssid, "identity");
-	if (!value) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing IEEE_8021X_IDENTITY for EAP method '%s'.",
-			     eap_method);
-		return FALSE;
-	}
-	g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, value, NULL);
-
-	/* ca cert */
-	ca_cert = get_cert (ssid, phase2 ? "ca_cert2" : "ca_cert", basepath);
-	if (ca_cert) {
-		if (phase2) {
-			if (!nm_setting_802_1x_set_phase2_ca_cert (s_8021x,
-								   ca_cert,
-								   NM_SETTING_802_1X_CK_SCHEME_PATH,
-								   NULL, error))
-				goto done;
-		} else {
-			if (!nm_setting_802_1x_set_ca_cert (s_8021x,
-							    ca_cert,
-							    NM_SETTING_802_1X_CK_SCHEME_PATH,
-							    NULL, error))
-				goto done;
-		}
-	} else {
-		nm_log_warn (LOGD_SETTINGS, "    missing %s for EAP method '%s'; this is insecure!",
-		             phase2 ? "IEEE_8021X_INNER_CA_CERT" :
-		             "IEEE_8021X_CA_CERT", eap_method);
-	}
-
-	/* Private key password */
-	privkey_password = wpa_get_value (ssid,
-					  phase2 ? "private_key2_passwd" :
-					  "private_key_passwd");
-
-	if (!privkey_password) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing %s for EAP method '%s'.",
-			     phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD" :
-			     "IEEE_8021X_PRIVATE_KEY_PASSWORD", eap_method);
-		goto done;
-	}
-
-	/* The private key itself */
-	privkey = get_cert (ssid, phase2 ? "private_key2" : "private_key", basepath);
-	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);
-		goto done;
-	}
-
-	if (phase2) {
-		if (!nm_setting_802_1x_set_phase2_private_key (s_8021x,
-							       privkey,
-							       privkey_password,
-							       NM_SETTING_802_1X_CK_SCHEME_PATH,
-							       &privkey_format,
-							       error))
-			goto done;
-	} else {
-		if (!nm_setting_802_1x_set_private_key (s_8021x,
-							privkey,
-							privkey_password,
-							NM_SETTING_802_1X_CK_SCHEME_PATH,
-							&privkey_format, error))
-			goto done;
-	}
-
-	/* Only set the client certificate if the private key is not PKCS#12 format,
-	 * as NM (due to supplicant restrictions) requires.  If the key was PKCS#12,
-	 * then nm_setting_802_1x_set_private_key() already set the client certificate
-	 * to the same value as the private key.
-	 */
-	if (privkey_format == NM_SETTING_802_1X_CK_FORMAT_RAW_KEY
-	    || privkey_format == NM_SETTING_802_1X_CK_FORMAT_X509) {
-		client_cert = get_cert (ssid, phase2 ? "client_cert2" : "client_cert", basepath);
-		if (!client_cert) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "Missing %s for EAP method '%s'.",
-				     phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" :
-				     "IEEE_8021X_CLIENT_CERT", eap_method);
-			goto done;
-		}
-
-		if (phase2) {
-			if (!nm_setting_802_1x_set_phase2_client_cert (s_8021x,
-								       client_cert,
-								       NM_SETTING_802_1X_CK_SCHEME_PATH,
-								       NULL,
-								       error))
-				goto done;
-		} else {
-			if (!nm_setting_802_1x_set_client_cert (s_8021x,
-								client_cert,
-								NM_SETTING_802_1X_CK_SCHEME_PATH,
-								NULL, error))
-				goto done;
-		}
-	}
-
-	success = TRUE;
-
-done:
-	g_free (ca_cert);
-	g_free (client_cert);
-	g_free (privkey);
-	return success;
-}
-
-static gboolean
-eap_peap_reader (const char *eap_method,
-                 const char *ssid,
-                 NMSetting8021x *s_8021x,
-                 gboolean phase2,
-                 const char *basepath,
-                 GError **error)
-{
-	char *ca_cert = NULL;
-	const char *inner_auth = NULL;
-	const char *peapver = NULL;
-	char **list = NULL, **iter, *lower;
-	gboolean success = FALSE;
-
-	ca_cert = get_cert (ssid, "ca_cert", basepath);
-	if (ca_cert) {
-		if (!nm_setting_802_1x_set_ca_cert (s_8021x,
-						    ca_cert,
-						    NM_SETTING_802_1X_CK_SCHEME_PATH,
-						    NULL, error))
-			goto done;
-	} else {
-		nm_log_warn (LOGD_SETTINGS, "    missing IEEE_8021X_CA_CERT for EAP method '%s'; this is insecure!",
-		             eap_method);
-	}
-
-	peapver = wpa_get_value (ssid, "phase1");
-	/* peap version, default is automatic */
-	if (peapver && strstr (peapver, "peapver")) {
-		if (strstr (peapver, "peapver=0"))
-			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_PEAPVER,
-				      "0", NULL);
-		else if (strstr (peapver, "peapver=1"))
-			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_PEAPVER,
-				      "1", NULL);
-		else {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "Unknown IEEE_8021X_PEAP_VERSION value '%s'",
-				     peapver);
-			goto done;
-		}
-	}
-
-	/* peaplabel */
-	if (peapver && strstr (peapver, "peaplabel=1"))
-		g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_PEAPLABEL, "1",
-			      NULL);
-
-	inner_auth = wpa_get_value (ssid, "phase2");
-	if (!inner_auth) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing IEEE_8021X_INNER_AUTH_METHODS.");
-		goto done;
-	}
-	/* Handle options for the inner auth method */
-	list = g_strsplit (inner_auth, " ", 0);
-	for (iter = list; iter && *iter; iter++) {
-		gchar *pos = NULL;
-
-		if (!strlen (*iter))
-			continue;
-
-		if (!(pos = strstr (*iter, "MSCHAPV2"))
-		    || !(pos = strstr (*iter, "MD5"))
-		    || !(pos = strstr (*iter, "GTC"))) {
-			if (!eap_simple_reader (pos, ssid, s_8021x, TRUE, basepath, error))
-				goto done;
-		} else if (!(pos = strstr (*iter, "TLS"))) {
-			if (!eap_tls_reader (pos, ssid, s_8021x, TRUE, basepath, error))
-				goto done;
-		} else {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "Unknown IEEE_8021X_INNER_AUTH_METHOD '%s'.",
-				     *iter);
-			goto done;
-		}
-
-		pos = strchr (*iter, '=');
-		if (pos && *pos) {
-			pos++;
-			lower = g_ascii_strdown (pos, -1);
-			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, lower,
-				      NULL);
-			g_free (lower);
-		} else {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "No IEEE_8021X_INNER_AUTH_METHOD.");
-			goto done;
-		}
-		break;
-	}
-
-	if (!nm_setting_802_1x_get_phase2_auth (s_8021x)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "No valid IEEE_8021X_INNER_AUTH_METHODS found.");
-		goto done;
-	}
-
-	success = TRUE;
-
-done:
-	g_free (ca_cert);
-	if (list)
-		g_strfreev (list);
-	return success;
-}
-
-static gboolean
-eap_ttls_reader (const char *eap_method,
-                 const char *ssid,
-                 NMSetting8021x *s_8021x,
-                 gboolean phase2,
-                 const char *basepath,
-                 GError **error)
-{
-	gboolean success = FALSE;
-	const char *anon_ident = NULL;
-	char *ca_cert = NULL;
-	const char *tmp;
-	char **list = NULL, **iter, *inner_auth = NULL;
-
-	/* ca cert */
-	ca_cert = get_cert (ssid, "ca_cert", basepath);
-	if (ca_cert) {
-		if (!nm_setting_802_1x_set_ca_cert (s_8021x,
-						    ca_cert,
-						    NM_SETTING_802_1X_CK_SCHEME_PATH,
-						    NULL, error))
-			goto done;
-	} else {
-		nm_log_warn (LOGD_SETTINGS, "    missing IEEE_8021X_CA_CERT for EAP method '%s'; this is insecure!",
-		             eap_method);
-	}
-
-	/* anonymous indentity for tls */
-	anon_ident = wpa_get_value (ssid, "anonymous_identity");
-	if (anon_ident && strlen (anon_ident))
-		g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY,
-			      anon_ident, NULL);
-
-	tmp = wpa_get_value (ssid, "phase2");
-	if (!tmp) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing IEEE_8021X_INNER_AUTH_METHODS.");
-		goto done;
-	}
-
-	/* Handle options for the inner auth method */
-	inner_auth = g_ascii_strdown (tmp, -1);
-	list = g_strsplit (inner_auth, " ", 0);
-	for (iter = list; iter && *iter; iter++) {
-		gchar *pos = NULL;
-
-		if (!strlen (*iter))
-			continue;
-		if ((pos = strstr (*iter, "mschapv2")) != NULL
-		    || (pos = strstr (*iter, "mschap")) != NULL
-		    || (pos = strstr (*iter, "pap")) != NULL
-		    || (pos = strstr (*iter, "chap")) != NULL) {
-			if (!eap_simple_reader (pos, ssid, s_8021x, TRUE, basepath, error))
-				goto done;
-			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH,
-				      pos, NULL);
-		} else if ((pos = strstr (*iter, "tls")) != NULL) {
-			if (!eap_tls_reader (pos, ssid, s_8021x, TRUE, basepath, error))
-				goto done;
-			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP,
-				      "tls", NULL);
-		} else if ((pos = strstr (*iter, "mschapv2")) != NULL
-			   || (pos = strstr (*iter, "md5")) != NULL) {
-			if (!eap_simple_reader (pos, ssid, s_8021x, TRUE, basepath, error)) {
-				nm_log_warn (LOGD_SETTINGS, "SIMPLE ERROR");
-				goto done;
-			}
-			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP,
-				      pos, NULL);
-		} else {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "Unknown IEEE_8021X_INNER_AUTH_METHOD '%s'.",
-				     *iter);
-			goto done;
-		}
-		break;
-	}
-
-	success = TRUE;
-done:
-	g_free (ca_cert);
-	if (list)
-		g_strfreev (list);
-	g_free (inner_auth);
-	return success;
-}
-
-/* type is already decided by net_parser, this function is just used to
- * doing tansformation*/
-static const gchar *
-guess_connection_type (const char *conn_name)
-{
-	const gchar *type = ifnet_get_data (conn_name, "type");
-	const gchar *ret_type = NULL;
-
-	if (!g_strcmp0 (type, "ppp"))
-		ret_type = NM_SETTING_PPPOE_SETTING_NAME;
-
-	if (!g_strcmp0 (type, "wireless"))
-		ret_type = NM_SETTING_WIRELESS_SETTING_NAME;
-
-	if (!ret_type)
-		ret_type = NM_SETTING_WIRED_SETTING_NAME;
-
-	nm_log_info (LOGD_SETTINGS, "guessed connection type (%s) = %s", conn_name, ret_type);
-	return ret_type;
-}
-
-/* Reading mac address for setting connection option.
- * Unmanaged device mac address is required by NetworkManager*/
-static gboolean
-read_mac_address (const char *conn_name, const char **mac, GError **error)
-{
-	const char *value = ifnet_get_data (conn_name, "mac");
-
-	if (!value || !strlen (value))
-		return TRUE;
-
-	if (!nm_utils_hwaddr_valid (value, ETH_ALEN)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "The MAC address '%s' was invalid.", value);
-		return FALSE;
-	}
-
-	*mac = value;
-	return TRUE;
-}
-
-static gboolean
-make_wired_connection_setting (NMConnection *connection,
-                               const char *conn_name,
-                               GError **error)
-{
-	const char *mac = NULL;
-	NMSettingWired *s_wired = NULL;
-	const char *value = NULL;
-
-	s_wired = NM_SETTING_WIRED (nm_setting_wired_new ());
-
-	/* mtu_xxx */
-	value = ifnet_get_data (conn_name, "mtu");
-	if (value) {
-		long int mtu;
-
-		errno = 0;
-		mtu = strtol (value, NULL, 10);
-		if (errno || mtu < 0 || mtu > 65535) {
-			nm_log_warn (LOGD_SETTINGS, "    invalid MTU '%s' for %s", value, conn_name);
-		} else
-			g_object_set (s_wired, NM_SETTING_WIRED_MTU,
-				      (guint32) mtu, NULL);
-	}
-
-	if (!read_mac_address (conn_name, &mac, error)) {
-		g_object_unref (s_wired);
-		return FALSE;
-	}
-
-	if (mac)
-		g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NULL);
-	nm_connection_add_setting (connection, NM_SETTING (s_wired));
-
-	return TRUE;
-}
-
-/* add NM_SETTING_IP_CONFIG_DHCP_HOSTNAME,
- * NM_SETTING_IP_CONFIG_DHCP_CLIENT_ID in future*/
-static gboolean
-make_ip4_setting (NMConnection *connection,
-                  const char *conn_name,
-                  GError **error)
-{
-
-	NMSettingIPConfig *ip4_setting =
-	    NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ());
-	const char *value, *method = NULL;
-	gboolean is_static_block = is_static_ip4 (conn_name);
-	ip_block *iblock = NULL;
-
-	/* set dhcp options (dhcp_xxx) */
-	value = ifnet_get_data (conn_name, "dhcp");
-	g_object_set (ip4_setting, NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, value
-		      && strstr (value, "nodns") ? TRUE : FALSE,
-		      NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, value
-		      && strstr (value, "nogateway") ? TRUE : FALSE, NULL);
-
-	if (!is_static_block) {
-		method = ifnet_get_data (conn_name, "config");
-		if (!method){
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-						 "Unknown config for %s", conn_name);
-			g_object_unref (ip4_setting);
-			return FALSE;
-		}
-		if (strstr (method, "dhcp"))
-			g_object_set (ip4_setting,
-						  NM_SETTING_IP_CONFIG_METHOD,
-						  NM_SETTING_IP4_CONFIG_METHOD_AUTO,
-						  NM_SETTING_IP_CONFIG_NEVER_DEFAULT, FALSE, NULL);
-		else if (strstr (method, "autoip")) {
-			g_object_set (ip4_setting,
-						  NM_SETTING_IP_CONFIG_METHOD,
-						  NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL,
-						  NM_SETTING_IP_CONFIG_NEVER_DEFAULT, FALSE, NULL);
-			nm_connection_add_setting (connection, NM_SETTING (ip4_setting));
-			return TRUE;
-		} else if (strstr (method, "shared")) {
-			g_object_set (ip4_setting,
-						  NM_SETTING_IP_CONFIG_METHOD,
-						  NM_SETTING_IP4_CONFIG_METHOD_SHARED,
-						  NM_SETTING_IP_CONFIG_NEVER_DEFAULT, FALSE, NULL);
-			nm_connection_add_setting (connection, NM_SETTING (ip4_setting));
-			return TRUE;
-		} else {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-						 "Unknown config for %s", conn_name);
-			g_object_unref (ip4_setting);
-			return FALSE;
-		}
-		nm_log_info (LOGD_SETTINGS, "Using %s method for %s", method, conn_name);
-	}else {
-		iblock = convert_ip4_config_block (conn_name);
-		if (!iblock) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "Ifnet plugin: can't aquire ip configuration for %s",
-				     conn_name);
-			g_object_unref (ip4_setting);
-			return FALSE;
-		}
-		/************** add all ip settings to the connection**********/
-		while (iblock) {
-			ip_block *current_iblock;
-			NMIPAddress *ip4_addr;
-			GError *local = NULL;
-
-			ip4_addr = nm_ip_address_new (AF_INET, iblock->ip, iblock->prefix, &local);
-			if (iblock->next_hop)
-				g_object_set (ip4_setting,
-					      NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES,
-					      TRUE, NULL);
-
-			if (ip4_addr) {
-				if (!nm_setting_ip_config_add_address (ip4_setting, ip4_addr))
-					nm_log_warn (LOGD_SETTINGS, "ignoring duplicate IP4 address");
-				nm_ip_address_unref (ip4_addr);
-			} else {
-				nm_log_warn (LOGD_SETTINGS, "    ignoring invalid address entry: %s", local->message);
-				g_clear_error (&local);
-			}
-
-			current_iblock = iblock;
-			iblock = iblock->next;
-			destroy_ip_block (current_iblock);
-
-		}
-		g_object_set (ip4_setting,
-		              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
-		              NM_SETTING_IP_CONFIG_NEVER_DEFAULT, !has_default_ip4_route (conn_name),
-		              NULL);
-	}
-
-	/* add dhcp hostname and client id */
-	if (!is_static_block && strstr (method, "dhcp")) {
-		gchar *dhcp_hostname, *client_id;
-
-		get_dhcp_hostname_and_client_id (&dhcp_hostname, &client_id);
-		if (dhcp_hostname) {
-			g_object_set (ip4_setting,
-				      NM_SETTING_IP_CONFIG_DHCP_HOSTNAME,
-				      dhcp_hostname, NULL);
-			nm_log_info (LOGD_SETTINGS, "DHCP hostname: %s", dhcp_hostname);
-			g_free (dhcp_hostname);
-		}
-		if (client_id) {
-			g_object_set (ip4_setting,
-				      NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID,
-				      client_id, NULL);
-			nm_log_info (LOGD_SETTINGS, "DHCP client id: %s", client_id);
-			g_free (client_id);
-		}
-	}
-
-	/* add all IPv4 dns servers, IPv6 servers will be ignored */
-	set_ip4_dns_servers (ip4_setting, conn_name);
-
-	/* DNS searches */
-	value = ifnet_get_data (conn_name, "dns_search");
-	if (value) {
-		gs_free char *stripped = g_strdup (value);
-		char **searches = NULL;
-
-		strip_string (stripped, '"');
-
-		searches = g_strsplit (stripped, " ", 0);
-		if (searches) {
-			char **item;
-
-			for (item = searches; *item; item++) {
-				if (strlen (*item)) {
-					if (!nm_setting_ip_config_add_dns_search (ip4_setting, *item))
-						nm_log_warn (LOGD_SETTINGS, "    duplicate DNS domain '%s'", *item);
-				}
-			}
-			g_strfreev (searches);
-		}
-	}
-
-	/* static routes */
-	iblock = convert_ip4_routes_block (conn_name);
-	while (iblock) {
-		ip_block *current_iblock = iblock;
-		const char *metric_str;
-		char *stripped;
-		gint64 metric;
-		NMIPRoute *route;
-		GError *local = NULL;
-
-		if ((metric_str = ifnet_get_data (conn_name, "metric")) != NULL) {
-			metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1);
-		} else {
-			metric_str = ifnet_get_global_data ("metric");
-			if (metric_str) {
-				stripped = g_strdup (metric_str);
-				strip_string (stripped, '"');
-				metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1);
-				g_free (stripped);
-			} else
-				metric = -1;
-		}
-
-		route = nm_ip_route_new (AF_INET, iblock->ip, iblock->prefix, iblock->next_hop, metric, &local);
-		if (route) {
-			if (nm_setting_ip_config_add_route (ip4_setting, route))
-				nm_log_info (LOGD_SETTINGS, "new IP4 route:%s\n", iblock->ip);
-			else
-				nm_log_warn (LOGD_SETTINGS, "duplicate IP4 route");
-			nm_ip_route_unref (route);
-		} else {
-			nm_log_warn (LOGD_SETTINGS, "    ignoring invalid route entry: %s", local->message);
-			g_clear_error (&local);
-		}
-
-		current_iblock = iblock;
-		iblock = iblock->next;
-		destroy_ip_block (current_iblock);
-	}
-
-	/* Finally add setting to connection */
-	nm_connection_add_setting (connection, NM_SETTING (ip4_setting));
-
-	return TRUE;
-}
-
-static gboolean
-make_ip6_setting (NMConnection *connection,
-                  const char *conn_name,
-                  GError **error)
-{
-	NMSettingIPConfig *s_ip6 = NULL;
-	gboolean is_static_block = is_static_ip6 (conn_name);
-
-	// used to disable IPv6
-	gboolean ipv6_enabled = FALSE;
-	gchar *method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
-	const char *value;
-	ip_block *iblock;
-	gboolean never_default = !has_default_ip6_route (conn_name);
-
-	s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new ();
-
-	value = ifnet_get_data (conn_name, "enable_ipv6");
-	if (value && is_true (value))
-		ipv6_enabled = TRUE;
-
-	//FIXME Handle other methods that NM supports in future
-	// Currently only Manual and DHCP are supported
-	if (!ipv6_enabled) {
-		g_object_set (s_ip6,
-			      NM_SETTING_IP_CONFIG_METHOD,
-			      NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
-		goto done;
-	} else if (!is_static_block) {
-		// config_eth* contains "dhcp6"
-		method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
-		never_default = FALSE;
-	}
-	// else if (!has_ip6_address(conn_name))
-	// doesn't have "dhcp6" && doesn't have any ipv6 address
-	// method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL;
-	else
-		// doesn't have "dhcp6" && has at least one ipv6 address
-		method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
-	nm_log_info (LOGD_SETTINGS, "IPv6 for %s enabled, using %s", conn_name, method);
-
-	g_object_set (s_ip6,
-		      NM_SETTING_IP_CONFIG_METHOD, method,
-		      NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, FALSE,
-		      NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, FALSE,
-		      NM_SETTING_IP_CONFIG_NEVER_DEFAULT, never_default, NULL);
-
-	/* Make manual settings */
-	if (!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
-		ip_block *current_iblock;
-
-		iblock = convert_ip6_config_block (conn_name);
-		if (!iblock) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "Ifnet plugin: can't aquire ip6 configuration for %s",
-				     conn_name);
-			goto error;
-		}
-		/* add all IPv6 addresses */
-		while (iblock) {
-			NMIPAddress *ip6_addr;
-			GError *local = NULL;
-
-			ip6_addr = nm_ip_address_new (AF_INET6, iblock->ip, iblock->prefix, &local);
-			if (ip6_addr) {
-				if (nm_setting_ip_config_add_address (s_ip6, ip6_addr)) {
-					nm_log_info (LOGD_SETTINGS, "ipv6 addresses count: %d",
-					             nm_setting_ip_config_get_num_addresses (s_ip6));
-				} else {
-					nm_log_warn (LOGD_SETTINGS, "ignoring duplicate IP6 address");
-				}
-				nm_ip_address_unref (ip6_addr);
-			} else {
-				nm_log_warn (LOGD_SETTINGS, "    ignoring invalid address entry: %s", local->message);
-				g_clear_error (&local);
-			}
-
-			current_iblock = iblock;
-			iblock = iblock->next;
-			destroy_ip_block (current_iblock);
-		}
-
-	} else if (!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
-		/* - autoconf or DHCPv6 stuff goes here */
-	}
-	// DNS Servers, set NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS TRUE here
-	set_ip6_dns_servers (s_ip6, conn_name);
-
-	/* DNS searches ('DOMAIN' key) are read by make_ip4_setting() and included in NMSettingIPConfig */
-
-	// Add routes
-	iblock = convert_ip6_routes_block (conn_name);
-	if (iblock)
-		g_object_set (s_ip6, NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES,
-			      TRUE, NULL);
-	/* Add all IPv6 routes */
-	while (iblock) {
-		ip_block *current_iblock = iblock;
-		const char *metric_str;
-		char *stripped;
-		gint64 metric;
-		NMIPRoute *route;
-		GError *local = NULL;
-
-		/* metric is not per routes configuration right now
-		 * global metric is also supported (metric="x") */
-		if ((metric_str = ifnet_get_data (conn_name, "metric")) != NULL)
-			metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1);
-		else {
-			metric_str = ifnet_get_global_data ("metric");
-			if (metric_str) {
-				stripped = g_strdup (metric_str);
-				strip_string (stripped, '"');
-				metric = _nm_utils_ascii_str_to_int64 (metric_str, 10, 0, G_MAXUINT32, -1);
-				g_free (stripped);
-			} else
-				metric = 1;
-		}
-
-		route = nm_ip_route_new (AF_INET6, iblock->ip, iblock->prefix, iblock->next_hop, metric, &local);
-		if (route) {
-			if (nm_setting_ip_config_add_route (s_ip6, route))
-				nm_log_info (LOGD_SETTINGS, "    new IP6 route");
-			else
-				nm_log_warn (LOGD_SETTINGS, "    duplicate IP6 route");
-			nm_ip_route_unref (route);
-		} else {
-			nm_log_warn (LOGD_SETTINGS, "    ignoring invalid route entry: %s", local->message);
-			g_clear_error (&local);
-		}
-
-		current_iblock = iblock;
-		iblock = iblock->next;
-		destroy_ip_block (current_iblock);
-	}
-
-done:
-	nm_connection_add_setting (connection, NM_SETTING (s_ip6));
-	return TRUE;
-
-error:
-	g_object_unref (s_ip6);
-	nm_log_warn (LOGD_SETTINGS, "    Ignore IPv6 for %s", conn_name);
-	return FALSE;
-}
-
-static NMSetting *
-make_wireless_connection_setting (const char *conn_name,
-                                  NMSetting8021x **s_8021x,
-                                  GError **error)
-{
-	const char *mac = NULL;
-	NMSettingWireless *wireless_setting = NULL;
-	gboolean adhoc = FALSE;
-	const char *value;
-	const char *type;
-
-	/* PPP over WIFI is not supported yet */
-	g_return_val_if_fail (conn_name != NULL
-			      && strcmp (ifnet_get_data (conn_name, "type"),
-					 "ppp") != 0, NULL);
-	type = ifnet_get_data (conn_name, "type");
-	if (strcmp (type, "ppp") == 0) {
-		nm_log_warn (LOGD_SETTINGS, "PPP over WIFI is not supported yet");
-		return NULL;
-	}
-
-	wireless_setting = NM_SETTING_WIRELESS (nm_setting_wireless_new ());
-	if (read_mac_address (conn_name, &mac, error)) {
-		if (mac) {
-			g_object_set (wireless_setting,
-				      NM_SETTING_WIRELESS_MAC_ADDRESS, mac,
-				      NULL);
-		}
-	} else {
-		g_object_unref (wireless_setting);
-		return NULL;
-	}
-
-	/* handle ssid (hex and ascii) */
-	if (conn_name) {
-		GBytes *bytes;
-		gsize ssid_len = 0, value_len = strlen (conn_name);
-
-		ssid_len = value_len;
-		if ((value_len > 2) && (g_str_has_prefix (conn_name, "0x"))) {
-			/* Hex representation */
-			if (value_len % 2) {
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-					     "Invalid SSID '%s' size (looks like hex but length not multiple of 2)",
-					     conn_name);
-				goto error;
-			}
-
-			bytes = nm_utils_hexstr2bin (conn_name);
-			if (!bytes) {
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Invalid SSID '%s' (looks like hex SSID but isn't)",
-				             conn_name);
-				goto error;
-			}
-		} else
-			bytes = g_bytes_new (conn_name, value_len);
-
-		ssid_len = g_bytes_get_size (bytes);
-		if (ssid_len > 32 || ssid_len == 0) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "Invalid SSID '%s' (size %zu not between 1 and 32 inclusive)",
-				     conn_name, ssid_len);
-			goto error;
-		}
-
-		g_object_set (wireless_setting, NM_SETTING_WIRELESS_SSID, bytes, NULL);
-		g_bytes_unref (bytes);
-	} else {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing SSID");
-		goto error;
-	}
-
-	/* mode=0: infrastructure
-	 * mode=1: adhoc */
-	value = wpa_get_value (conn_name, "mode");
-	if (value)
-		adhoc = strcmp (value, "1") == 0 ? TRUE : FALSE;
-
-	if (exist_ssid (conn_name)) {
-		const char *mode = adhoc ? "adhoc" : "infrastructure";
-
-		g_object_set (wireless_setting, NM_SETTING_WIRELESS_MODE, mode,
-			      NULL);
-		nm_log_info (LOGD_SETTINGS, "Using mode: %s", mode);
-	}
-
-	/* BSSID setting */
-	value = wpa_get_value (conn_name, "bssid");
-	if (value) {
-		if (!nm_utils_hwaddr_valid (value, ETH_ALEN)) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-						 "Invalid BSSID '%s'", value);
-			goto error;
-		}
-
-		g_object_set (wireless_setting, NM_SETTING_WIRELESS_BSSID,
-			      value, NULL);
-
-	}
-
-	/* mtu_ssid="xx" */
-	value = ifnet_get_data (conn_name, "mtu");
-	if (value) {
-		long int mtu;
-
-		errno = 0;
-		mtu = strtol (value, NULL, 10);
-		if (errno || mtu < 0 || mtu > 50000) {
-			nm_log_warn (LOGD_SETTINGS, "    invalid MTU '%s' for %s", value, conn_name);
-		} else
-			g_object_set (wireless_setting, NM_SETTING_WIRELESS_MTU,
-				      (guint32) mtu, NULL);
-
-	}
-
-	nm_log_info (LOGD_SETTINGS, "wireless_setting added for %s", conn_name);
-	return NM_SETTING (wireless_setting);
-error:
-	if (wireless_setting)
-		g_object_unref (wireless_setting);
-	return NULL;
-
-}
-
-static NMSettingWirelessSecurity *
-make_leap_setting (const char *ssid, GError **error)
-{
-	NMSettingWirelessSecurity *wsec;
-	const char *value;
-
-	wsec =
-	    NM_SETTING_WIRELESS_SECURITY (nm_setting_wireless_security_new ());
-
-	value = wpa_get_value (ssid, "password");
-	if (value && strlen (value))
-		g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD,
-			      value, NULL);
-
-	value = wpa_get_value (ssid, "identity");
-	if (!value || !strlen (value)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing LEAP identity");
-		goto error;
-	}
-	g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, value,
-		      NULL);
-
-	g_object_set (wsec,
-		      NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x",
-		      NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", NULL);
-
-	return wsec;
-error:
-	if (wsec)
-		g_object_unref (wsec);
-	return NULL;
-}
-
-static gboolean
-add_one_wep_key (const char *ssid,
-                 const char *key,
-                 int key_idx,
-                 NMSettingWirelessSecurity *s_wsec,
-                 GError **error)
-{
-	const char *value;
-	char *converted = NULL;
-	gboolean success = FALSE;
-
-	g_return_val_if_fail (ssid != NULL, FALSE);
-	g_return_val_if_fail (key != NULL, FALSE);
-	g_return_val_if_fail (key_idx >= 0 && key_idx <= 3, FALSE);
-	g_return_val_if_fail (s_wsec != NULL, FALSE);
-
-	value = wpa_get_value (ssid, key);
-	if (!value)
-		return TRUE;
-
-	/* Validate keys */
-	if (strlen (value) == 10 || strlen (value) == 26) {
-		/* Hexadecimal WEP key */
-		if (!is_hex (value)) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid hexadecimal WEP key.");
-			goto out;
-		}
-		converted = g_strdup (value);
-	} else if (value[0] == '"'
-		   && (strlen (value) == 7 || strlen (value) == 15)) {
-		/* ASCII passphrase */
-		char *tmp = g_strdup (value);
-		char *p = strip_string (tmp, '"');
-
-		if (!is_ascii (p)) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid ASCII WEP passphrase.");
-			g_free (tmp);
-			goto out;
-
-		}
-
-		converted = nm_utils_bin2hexstr (tmp, strlen (tmp), -1);
-		g_free (tmp);
-	} else {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Invalid WEP key length. Key: %s", value);
-		goto out;
-	}
-
-	if (converted) {
-		nm_setting_wireless_security_set_wep_key (s_wsec, key_idx, converted);
-		g_free (converted);
-		success = TRUE;
-	}
-
-out:
-	return success;
-}
-
-static gboolean
-add_wep_keys (const char *ssid,
-              NMSettingWirelessSecurity *s_wsec,
-              GError **error)
-{
-	if (!add_one_wep_key (ssid, "wep_key0", 0, s_wsec, error))
-		return FALSE;
-	if (!add_one_wep_key (ssid, "wep_key1", 1, s_wsec, error))
-		return FALSE;
-	if (!add_one_wep_key (ssid, "wep_key2", 2, s_wsec, error))
-		return FALSE;
-	if (!add_one_wep_key (ssid, "wep_key3", 3, s_wsec, error))
-		return FALSE;
-	return TRUE;
-
-}
-
-static NMSettingWirelessSecurity *
-make_wep_setting (const char *ssid, GError **error)
-{
-	const char *auth_alg, *value;
-	int default_key_idx = 0;
-	NMSettingWirelessSecurity *s_wireless_sec;
-
-	s_wireless_sec =
-	    NM_SETTING_WIRELESS_SECURITY (nm_setting_wireless_security_new ());
-	g_object_set (s_wireless_sec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
-		      "none", NULL);
-
-	/* default key index */
-	value = wpa_get_value (ssid, "wep_tx_keyidx");
-	if (value) {
-		default_key_idx = atoi (value);
-		if (default_key_idx >= 0 && default_key_idx <= 3) {
-			g_object_set (s_wireless_sec,
-				      NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX,
-				      default_key_idx, NULL);
-			nm_log_info (LOGD_SETTINGS, "Default key index: %d", default_key_idx);
-		} else {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "Invalid default WEP key '%s'", value);
-			goto error;
-		}
-	}
-
-	if (!add_wep_keys (ssid, s_wireless_sec, error))
-		goto error;
-
-	/* If there's a default key, ensure that key exists */
-	if ((default_key_idx == 1)
-	    && !nm_setting_wireless_security_get_wep_key (s_wireless_sec, 1)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Default WEP key index was 2, but no valid KEY2 exists.");
-		goto error;
-	} else if ((default_key_idx == 2)
-		   && !nm_setting_wireless_security_get_wep_key (s_wireless_sec,
-								 2)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Default WEP key index was 3, but no valid KEY3 exists.");
-		goto error;
-	} else if ((default_key_idx == 3)
-		   && !nm_setting_wireless_security_get_wep_key (s_wireless_sec,
-								 3)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Default WEP key index was 4, but no valid KEY4 exists.");
-		goto error;
-	}
-
-	/* authentication algorithms */
-	auth_alg = wpa_get_value (ssid, "auth_alg");
-	if (auth_alg) {
-		if (strcmp (auth_alg, "OPEN") == 0) {
-			g_object_set (s_wireless_sec,
-				      NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
-				      "open", NULL);
-			nm_log_info (LOGD_SETTINGS, "WEP: Use open system authentication");
-		} else if (strcmp (auth_alg, "SHARED") == 0) {
-			g_object_set (s_wireless_sec,
-				      NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
-				      "shared", NULL);
-			nm_log_info (LOGD_SETTINGS, "WEP: Use shared system authentication");
-		} else {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "Invalid WEP authentication algorithm '%s'",
-				     auth_alg);
-			goto error;
-		}
-
-	}
-
-	if (!nm_setting_wireless_security_get_wep_key (s_wireless_sec, 0)
-	    && !nm_setting_wireless_security_get_wep_key (s_wireless_sec, 1)
-	    && !nm_setting_wireless_security_get_wep_key (s_wireless_sec, 2)
-	    && !nm_setting_wireless_security_get_wep_key (s_wireless_sec, 3)
-	    && !nm_setting_wireless_security_get_wep_tx_keyidx (s_wireless_sec)) {
-		if (auth_alg && !strcmp (auth_alg, "shared")) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "WEP Shared Key authentication is invalid for "
-				     "unencrypted connections.");
-			goto error;
-		}
-		/* Unencrypted */
-		g_object_unref (s_wireless_sec);
-		s_wireless_sec = NULL;
-	}
-	return s_wireless_sec;
-
-error:
-	if (s_wireless_sec)
-		g_object_unref (s_wireless_sec);
-	return NULL;
-}
-
-static char *
-parse_wpa_psk (const char *psk, GError **error)
-{
-	char *hashed = NULL;
-	gboolean quoted = FALSE;
-
-	if (!psk) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing WPA_PSK for WPA-PSK key management");
-		return NULL;
-	}
-
-	/* Passphrase must be between 10 and 66 characters in length because WPA
-	 * hex keys are exactly 64 characters (no quoting), and WPA passphrases
-	 * are between 8 and 63 characters (inclusive), plus optional quoting if
-	 * the passphrase contains spaces.
-	 */
-
-	if (psk[0] == '"' && psk[strlen (psk) - 1] == '"')
-		quoted = TRUE;
-	if (!quoted && (strlen (psk) == 64)) {
-		/* Verify the hex PSK; 64 digits */
-		if (!is_hex (psk)) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid WPA_PSK (contains non-hexadecimal characters)");
-			goto out;
-		}
-		hashed = g_strdup (psk);
-	} else {
-		char *stripped = g_strdup (psk);
-
-		strip_string (stripped, '"');
-
-		/* Length check */
-		if (strlen (stripped) < 8 || strlen (stripped) > 63) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "Invalid WPA_PSK (passphrases must be between "
-				     "8 and 63 characters long (inclusive))");
-			g_free (stripped);
-			goto out;
-		}
-
-		hashed = g_strdup (stripped);
-		g_free (stripped);
-	}
-
-	if (!hashed) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Invalid WPA_PSK (doesn't look like a passphrase or hex key)");
-		goto out;
-	}
-
-out:
-	return hashed;
-}
-
-static gboolean
-fill_wpa_ciphers (const char *ssid,
-                  NMSettingWirelessSecurity *wsec,
-                  gboolean group,
-                  gboolean adhoc)
-{
-	const char *value;
-	char **list = NULL, **iter;
-	int i = 0;
-
-	value = wpa_get_value (ssid, group ? "group" : "pairwise");
-	if (!value)
-		return TRUE;
-
-	list = g_strsplit_set (value, " ", 0);
-	for (iter = list; iter && *iter; iter++, i++) {
-		/* Ad-Hoc configurations cannot have pairwise ciphers, and can only
-		 * have one group cipher.  Ignore any additional group ciphers and
-		 * any pairwise ciphers specified.
-		 */
-		if (adhoc) {
-			if (group && (i > 0)) {
-				nm_log_warn (LOGD_SETTINGS, "    ignoring group cipher '%s' (only one group cipher allowed in Ad-Hoc mode)",
-				             *iter);
-				continue;
-			} else if (!group) {
-				nm_log_warn (LOGD_SETTINGS, "    ignoring pairwise cipher '%s' (pairwise not used in Ad-Hoc mode)",
-				             *iter);
-				continue;
-			}
-		}
-
-		if (!strcmp (*iter, "CCMP")) {
-			if (group)
-				nm_setting_wireless_security_add_group (wsec,
-									"ccmp");
-			else
-				nm_setting_wireless_security_add_pairwise (wsec,
-									   "ccmp");
-		} else if (!strcmp (*iter, "TKIP")) {
-			if (group)
-				nm_setting_wireless_security_add_group (wsec,
-									"tkip");
-			else
-				nm_setting_wireless_security_add_pairwise (wsec,
-									   "tkip");
-		} else if (group && !strcmp (*iter, "WEP104"))
-			nm_setting_wireless_security_add_group (wsec, "wep104");
-		else if (group && !strcmp (*iter, "WEP40"))
-			nm_setting_wireless_security_add_group (wsec, "wep40");
-		else {
-			nm_log_warn (LOGD_SETTINGS, "    ignoring invalid %s cipher '%s'",
-			             group ? "CIPHER_GROUP" : "CIPHER_PAIRWISE",
-			             *iter);
-		}
-	}
-
-	if (list)
-		g_strfreev (list);
-	return TRUE;
-}
-
-static NMSetting8021x *
-fill_8021x (const char *ssid,
-            const char *key_mgmt,
-            gboolean wifi,
-            const char *basepath,
-            GError **error)
-{
-	NMSetting8021x *s_8021x;
-	const char *value;
-	char **list, **iter;
-
-	value = wpa_get_value (ssid, "eap");
-	if (!value) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing IEEE_8021X_EAP_METHODS for key management '%s'",
-			     key_mgmt);
-		return NULL;
-	}
-
-	list = g_strsplit (value, " ", 0);
-
-	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
-	/* Validate and handle each EAP method */
-	for (iter = list; iter && *iter; iter++) {
-		EAPReader *eap = &eap_readers[0];
-		gboolean found = FALSE;
-		char *lower = NULL;
-
-		lower = g_ascii_strdown (*iter, -1);
-		while (eap->method) {
-			if (strcmp (eap->method, lower))
-				goto next;
-
-			/* Some EAP methods don't provide keying material, thus they
-			 * cannot be used with WiFi unless they are an inner method
-			 * used with TTLS or PEAP or whatever.
-			 */
-			if (wifi && eap->wifi_phase2_only) {
-				nm_log_warn (LOGD_SETTINGS, "    ignored invalid IEEE_8021X_EAP_METHOD '%s'; not allowed for wifi.",
-				             lower);
-				goto next;
-			}
-
-			/* Parse EAP method specific options */
-			if (!(*eap->reader) (lower, ssid, s_8021x, FALSE, basepath, error)) {
-				g_free (lower);
-				goto error;
-			}
-			nm_setting_802_1x_add_eap_method (s_8021x, lower);
-			found = TRUE;
-			break;
-
-		next:
-			eap++;
-		}
-
-		if (!found) {
-			nm_log_warn (LOGD_SETTINGS, "    ignored unknown IEEE_8021X_EAP_METHOD '%s'.", lower);
-		}
-		g_free (lower);
-	}
-	g_strfreev (list);
-
-	if (nm_setting_802_1x_get_num_eap_methods (s_8021x) == 0) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "No valid EAP methods found in IEEE_8021X_EAP_METHODS.");
-		goto error;
-	}
-
-	return s_8021x;
-
-error:
-	g_object_unref (s_8021x);
-	return NULL;
-}
-
-static NMSettingWirelessSecurity *
-make_wpa_setting (const char *ssid,
-                  const char *basepath,
-                  NMSetting8021x **s_8021x,
-                  GError **error)
-{
-	NMSettingWirelessSecurity *wsec;
-	const char *value;
-	char *lower;
-	gboolean adhoc = FALSE;
-
-	if (!exist_ssid (ssid)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "No security info found for ssid: %s", ssid);
-		return NULL;
-	}
-
-	wsec = NM_SETTING_WIRELESS_SECURITY (nm_setting_wireless_security_new ());
-
-	/* mode=1: adhoc
-	 * mode=0: infrastructure */
-	value = wpa_get_value (ssid, "mode");
-	if (value)
-		adhoc = strcmp (value, "1") == 0 ? TRUE : FALSE;
-
-	/* Pairwise and Group ciphers */
-	fill_wpa_ciphers (ssid, wsec, FALSE, adhoc);
-	fill_wpa_ciphers (ssid, wsec, TRUE, adhoc);
-
-	/* WPA and/or RSN */
-	if (adhoc) {
-		/* Ad-Hoc mode only supports WPA proto for now */
-		nm_setting_wireless_security_add_proto (wsec, "wpa");
-	} else {
-		nm_setting_wireless_security_add_proto (wsec, "wpa");
-		nm_setting_wireless_security_add_proto (wsec, "rsn");
-
-	}
-
-	value = wpa_get_value (ssid, "key_mgmt");
-	if (!strcmp (value, "WPA-PSK")) {
-		char *psk = parse_wpa_psk (wpa_get_value (ssid, "psk"), error);
-
-		if (!psk)
-			goto error;
-		g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_PSK, psk,
-			      NULL);
-		g_free (psk);
-
-		if (adhoc)
-			g_object_set (wsec,
-				      NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
-				      "wpa-none", NULL);
-		else
-			g_object_set (wsec,
-				      NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
-				      "wpa-psk", NULL);
-	} else if (!strcmp (value, "WPA-EAP") || !strcmp (value, "IEEE8021X")) {
-		if (adhoc) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				     "Ad-Hoc mode cannot be used with KEY_MGMT type '%s'",
-				     value);
-			goto error;
-		}
-		*s_8021x = fill_8021x (ssid, value, TRUE, basepath, error);
-		if (!*s_8021x)
-			goto error;
-
-		lower = g_ascii_strdown (value, -1);
-		g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
-			      lower, NULL);
-		g_free (lower);
-	} else {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Unknown wireless KEY_MGMT type '%s'", value);
-		goto error;
-	}
-	return wsec;
-error:
-	if (wsec)
-		g_object_unref (wsec);
-	return NULL;
-}
-
-static NMSettingWirelessSecurity *
-make_wireless_security_setting (const char *conn_name,
-                                const char *basepath,
-                                NMSetting8021x **s_8021x,
-                                GError ** error)
-{
-	NMSettingWirelessSecurity *wsec = NULL;
-	const char *ssid;
-	gboolean adhoc = FALSE;
-	const char *value;
-
-	g_return_val_if_fail (conn_name != NULL
-			      && strcmp (ifnet_get_data (conn_name, "type"),
-					 "ppp") != 0, NULL);
-	nm_log_info (LOGD_SETTINGS, "updating wireless security settings (%s).", conn_name);
-
-	ssid = conn_name;
-	value = wpa_get_value (ssid, "mode");
-	if (value)
-		adhoc = strcmp (value, "1") == 0 ? TRUE : FALSE;
-
-	value = wpa_get_value (ssid, "key_mgmt");
-	if (!adhoc && g_strcmp0 (value, "IEEE8021X") == 0) {
-		value = wpa_get_value (ssid, "eap");
-		if (value && strcasecmp (value, "LEAP") == 0) {
-			wsec = make_leap_setting (ssid, error);
-			if (wsec == NULL)
-				goto error;
-		}
-	} else if (g_strcmp0 (value, "WPA-PSK") == 0 || g_strcmp0 (value, "WPA-EAP") == 0) {
-		wsec = make_wpa_setting (ssid, basepath, s_8021x, error);
-		if (wsec == NULL)
-			goto error;
-	}
-	if (!wsec) {
-		wsec = make_wep_setting (ssid, error);
-		if (wsec == NULL)
-			goto error;
-	}
-	return wsec;
-
-error:
-	g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		     "Can't handle security information for ssid: %s",
-		     conn_name);
-	return NULL;
-}
-
-/* Currently only support username and password */
-static gboolean
-make_pppoe_connection_setting (NMConnection *connection,
-                               const char *conn_name,
-                               GError **error)
-{
-	NMSettingPppoe *s_pppoe;
-	NMSettingPpp *s_ppp;
-	const char *value;
-
-	s_pppoe = NM_SETTING_PPPOE (nm_setting_pppoe_new ());
-
-	/* username */
-	value = ifnet_get_data (conn_name, "username");
-	if (!value) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "ppp requires at lease a username");
-		return FALSE;
-	}
-	g_object_set (s_pppoe, NM_SETTING_PPPOE_USERNAME, value, NULL);
-
-	/* password */
-	value = ifnet_get_data (conn_name, "password");
-	if (!value) {
-		value = "";
-	}
-
-	g_object_set (s_pppoe, NM_SETTING_PPPOE_PASSWORD, value, NULL);
-	nm_connection_add_setting (connection, NM_SETTING (s_pppoe));
-
-	/* PPP setting */
-	s_ppp = (NMSettingPpp *) nm_setting_ppp_new ();
-	nm_connection_add_setting (connection, NM_SETTING (s_ppp));
-
-	return TRUE;
-}
-
-NMConnection *
-ifnet_update_connection_from_config_block (const char *conn_name,
-                                           const char *basepath,
-                                           GError **error)
-{
-	const gchar *type = NULL;
-	NMConnection *connection = NULL;
-	NMSettingConnection *setting = NULL;
-	NMSetting8021x *s_8021x = NULL;
-	NMSettingWirelessSecurity *wsec = NULL;
-	gboolean auto_conn = TRUE;
-	const char *value = NULL;
-	gchar *id, *uuid;
-	gboolean success = FALSE;
-
-	connection = nm_simple_connection_new ();
-	setting = nm_connection_get_setting_connection (connection);
-	if (!setting) {
-		setting = NM_SETTING_CONNECTION (nm_setting_connection_new ());
-		g_assert (setting);
-		nm_connection_add_setting (connection, NM_SETTING (setting));
-	}
-
-	type = guess_connection_type (conn_name);
-	value = ifnet_get_data (conn_name, "auto");
-	if (value && !strcmp (value, "false"))
-		auto_conn = FALSE;
-
-	/* Try to read UUID from the ifnet block, otherwise generate UUID from
-	 * the connection ID.
-	 */
-	id = connection_id_from_ifnet_name (conn_name);
-	uuid = g_strdup (ifnet_get_data (conn_name, "uuid"));
-	if (!uuid)
-		uuid = nm_utils_uuid_generate_from_string (id, -1, NM_UTILS_UUID_TYPE_LEGACY, NULL);
-
-	g_object_set (setting,
-	              NM_SETTING_CONNECTION_TYPE, type,
-	              NM_SETTING_CONNECTION_ID, id,
-	              NM_SETTING_CONNECTION_UUID, uuid,
-	              NM_SETTING_CONNECTION_INTERFACE_NAME, conn_name,
-	              NM_SETTING_CONNECTION_READ_ONLY, FALSE,
-	              NM_SETTING_CONNECTION_AUTOCONNECT, auto_conn,
-	              NULL);
-	nm_log_info (LOGD_SETTINGS, "name:%s, id:%s, uuid: %s", conn_name, id, uuid);
-	g_free (id);
-	g_free (uuid);
-
-	if (!strcmp (NM_SETTING_WIRED_SETTING_NAME, type)
-	    || !strcmp (NM_SETTING_PPPOE_SETTING_NAME, type)) {
-		/* wired setting */
-		if (!make_wired_connection_setting (connection, conn_name, error))
-			goto error;
-
-		/* pppoe setting */
-		if (!strcmp (NM_SETTING_PPPOE_SETTING_NAME, type)) {
-			if (!make_pppoe_connection_setting (connection, conn_name, error))
-				goto error;
-		}
-	} else if (!strcmp (NM_SETTING_WIRELESS_SETTING_NAME, type)) {
-		/* wireless setting */
-		NMSetting *wireless_setting;
-
-		wireless_setting = make_wireless_connection_setting (conn_name, &s_8021x, error);
-		if (!wireless_setting)
-			goto error;
-		nm_connection_add_setting (connection, wireless_setting);
-
-		/* wireless security setting */
-		if (wpa_get_value (conn_name, "ssid")) {
-			wsec = make_wireless_security_setting (conn_name, basepath, &s_8021x, error);
-			if (!wsec)
-				goto error;
-			nm_connection_add_setting (connection, NM_SETTING (wsec));
-			if (s_8021x)
-				nm_connection_add_setting (connection, NM_SETTING (s_8021x));
-		}
-	} else
-		goto error;
-
-	/* IPv4 setting */
-	if (!make_ip4_setting (connection, conn_name, error))
-		goto error;
-
-	/* IPv6 setting */
-	if (!make_ip6_setting (connection, conn_name, error))
-		goto error;
-
-	if (nm_connection_verify (connection, error)) {
-		nm_log_info (LOGD_SETTINGS, "Connection verified %s:%d", conn_name, success);
-	} else {
-		goto error;
-	}
-
-	return connection;
-error:
-	g_object_unref (connection);
-	return NULL;
-}
-
-typedef struct Setting8021xSchemeVtable {
-	const NMSetting8021xSchemeVtable *vtable;
-	const char *ifnet_key;
-} 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],
-		.ifnet_key              = "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],
-		.ifnet_key              = "ca_cert2",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT],
-		.ifnet_key              = "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],
-		.ifnet_key              = "client_cert2",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY],
-		.ifnet_key              = "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],
-		.ifnet_key              = "private_key2",
-	},
-};
-
-static gboolean
-write_object (NMSetting8021x *s_8021x,
-              const char *conn_name,
-              GBytes *override_data,
-              const Setting8021xSchemeVtable *objtype,
-              GError **error)
-{
-	NMSetting8021xCKScheme scheme;
-	const char *path = NULL;
-	GBytes *blob = NULL;
-
-	g_return_val_if_fail (conn_name != NULL, FALSE);
-	g_return_val_if_fail (objtype != NULL, FALSE);
-	if (override_data)
-		/* if given explicit data to save, always use that instead of asking
-		 * the setting what to do.
-		 */
-		blob = override_data;
-	else {
-		scheme = (*(objtype->vtable->scheme_func)) (s_8021x);
-		switch (scheme) {
-		case NM_SETTING_802_1X_CK_SCHEME_BLOB:
-			blob = (*(objtype->vtable->blob_func)) (s_8021x);
-			break;
-		case NM_SETTING_802_1X_CK_SCHEME_PATH:
-			path = (*(objtype->vtable->path_func)) (s_8021x);
-			break;
-		default:
-			break;
-		}
-	}
-
-	/* If the object path was specified, prefer that over any raw cert data that
-	 * may have been sent.
-	 */
-	if (path) {
-		wpa_set_data (conn_name, (gchar *) objtype->ifnet_key,
-		              (gchar *) path);
-		return TRUE;
-	}
-
-	/* does not support writing encryption data now */
-	if (blob)
-		nm_log_warn (LOGD_SETTINGS, "    Currently we do not support cert writing.");
-
-	return TRUE;
-}
-
-static gboolean
-write_8021x_certs (NMSetting8021x *s_8021x,
-                   gboolean phase2,
-                   const char *conn_name,
-                   GError **error)
-{
-	char *password = NULL;
-	const Setting8021xSchemeVtable *otype = NULL;
-	gboolean is_pkcs12 = FALSE, success = FALSE;
-	GBytes *blob = NULL;
-	GBytes *enc_key = NULL;
-	gchar *generated_pw = NULL;
-
-	/* CA certificate */
-	otype = 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];
-
-	if (!write_object (s_8021x, conn_name, NULL, otype, error))
-		return FALSE;
-
-	/* Private key */
-	if (phase2) {
-		if (nm_setting_802_1x_get_phase2_private_key_scheme (s_8021x) !=
-		    NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) {
-			if (nm_setting_802_1x_get_phase2_private_key_format
-			    (s_8021x) == NM_SETTING_802_1X_CK_FORMAT_PKCS12)
-				is_pkcs12 = TRUE;
-		}
-		password = (char *)
-		    nm_setting_802_1x_get_phase2_private_key_password (s_8021x);
-	} else {
-		if (nm_setting_802_1x_get_private_key_scheme (s_8021x) !=
-		    NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) {
-			if (nm_setting_802_1x_get_private_key_format (s_8021x)
-			    == NM_SETTING_802_1X_CK_FORMAT_PKCS12)
-				is_pkcs12 = TRUE;
-		}
-		password = (char *)
-		    nm_setting_802_1x_get_private_key_password (s_8021x);
-	}
-
-	otype = phase2
-	        ? &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY]
-	        : &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY];
-
-	if ((*(otype->vtable->scheme_func)) (s_8021x) ==
-	    NM_SETTING_802_1X_CK_SCHEME_BLOB)
-		blob = (*(otype->vtable->blob_func)) (s_8021x);
-
-	/* Only do the private key re-encrypt dance if we got the raw key data, which
-	 * by definition will be unencrypted.  If we're given a direct path to the
-	 * private key file, it'll be encrypted, so we don't need to re-encrypt.
-	 */
-	if (blob && !is_pkcs12) {
-		GByteArray *tmp_enc_key;
-
-		/* Encrypt the unencrypted private key with the fake password */
-		tmp_enc_key =
-		    nm_utils_rsa_key_encrypt (g_bytes_get_data (blob, NULL), g_bytes_get_size (blob),
-		                              password, &generated_pw, error);
-		if (!tmp_enc_key)
-			goto out;
-
-		enc_key = g_byte_array_free_to_bytes (tmp_enc_key);
-
-		if (generated_pw)
-			password = generated_pw;
-	}
-
-	/* Save the private key */
-	if (!write_object
-	    (s_8021x, conn_name, enc_key ? enc_key : blob, otype, error))
-		goto out;
-
-	if (phase2)
-		wpa_set_data (conn_name, "private_key2_passwd", password);
-	else
-		wpa_set_data (conn_name, "private_key_passwd", password);
-
-	/* Client certificate */
-	if (is_pkcs12) {
-		wpa_set_data (conn_name,
-		              phase2 ? "client_cert2" : "client_cert", NULL);
-	} else {
-		otype = 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];
-
-		/* Save the client certificate */
-		if (!write_object (s_8021x, conn_name, NULL, otype, error))
-			goto out;
-	}
-
-	success = TRUE;
-out:
-	if (generated_pw) {
-		memset (generated_pw, 0, strlen (generated_pw));
-		g_free (generated_pw);
-	}
-	if (enc_key) {
-		memset ((gpointer) g_bytes_get_data (enc_key, NULL), 0, g_bytes_get_size (enc_key));
-		g_bytes_unref (enc_key);
-	}
-	return success;
-}
-
-static gboolean
-write_8021x_setting (NMConnection *connection,
-                     const char *conn_name,
-                     gboolean wired,
-                     GError **error)
-{
-	NMSetting8021x *s_8021x;
-	const char *value;
-	char *tmp = NULL;
-	gboolean success = FALSE;
-	GString *phase2_auth;
-	GString *phase1;
-
-	s_8021x = nm_connection_get_setting_802_1x (connection);
-	if (!s_8021x) {
-		return TRUE;
-	}
-
-	nm_log_info (LOGD_SETTINGS, "Adding 8021x setting for %s", conn_name);
-
-	/* If wired, write KEY_MGMT */
-	if (wired)
-		wpa_set_data (conn_name, "key_mgmt", "IEEE8021X");
-
-	/* EAP method */
-	if (nm_setting_802_1x_get_num_eap_methods (s_8021x)) {
-		value = nm_setting_802_1x_get_eap_method (s_8021x, 0);
-		if (value)
-			tmp = g_ascii_strup (value, -1);
-	}
-	wpa_set_data (conn_name, "eap", tmp ? tmp : NULL);
-	g_free (tmp);
-
-	wpa_set_data (conn_name, "identity",
-		      (gchar *) nm_setting_802_1x_get_identity (s_8021x));
-
-	wpa_set_data (conn_name, "anonymous_identity", (gchar *)
-		      nm_setting_802_1x_get_anonymous_identity (s_8021x));
-
-	wpa_set_data (conn_name, "password",
-		      (gchar *) nm_setting_802_1x_get_password (s_8021x));
-
-	phase1 = g_string_new (NULL);
-
-	/* PEAP version */
-	wpa_set_data (conn_name, "phase1", NULL);
-	value = nm_setting_802_1x_get_phase1_peapver (s_8021x);
-	if (value && (!strcmp (value, "0") || !strcmp (value, "1")))
-		g_string_append_printf (phase1, "peapver=%s ", value);
-
-	/* PEAP label */
-	value = nm_setting_802_1x_get_phase1_peaplabel (s_8021x);
-	if (value && !strcmp (value, "1"))
-		g_string_append_printf (phase1, "peaplabel=%s ", value);
-	if (phase1->len) {
-		tmp = g_strstrip (g_strdup (phase1->str));
-		wpa_set_data (conn_name, "phase1", tmp);
-		g_free (tmp);
-	}
-
-	/* Phase2 auth methods */
-	wpa_set_data (conn_name, "phase2", NULL);
-	phase2_auth = g_string_new (NULL);
-
-	value = nm_setting_802_1x_get_phase2_auth (s_8021x);
-	if (value) {
-		tmp = g_ascii_strup (value, -1);
-		g_string_append_printf (phase2_auth, "auth=%s ", tmp);
-		g_free (tmp);
-	}
-
-	/* Phase2 auth heap */
-	value = nm_setting_802_1x_get_phase2_autheap (s_8021x);
-	if (value) {
-		tmp = g_ascii_strup (value, -1);
-		g_string_append_printf (phase2_auth, "autheap=%s ", tmp);
-		g_free (tmp);
-	}
-	tmp = g_strstrip (g_strdup (phase2_auth->str));
-	wpa_set_data (conn_name, "phase2", phase2_auth->len ? tmp : NULL);
-	g_free (tmp);
-
-	g_string_free (phase2_auth, TRUE);
-	g_string_free (phase1, TRUE);
-
-	success = write_8021x_certs (s_8021x, FALSE, conn_name, error);
-	if (success) {
-		/* phase2/inner certs */
-		success = write_8021x_certs (s_8021x, TRUE, conn_name, error);
-	}
-
-	return success;
-}
-
-static gboolean
-write_wireless_security_setting (NMConnection * connection,
-				 gchar * conn_name,
-				 gboolean adhoc,
-				 gboolean * no_8021x, GError ** error)
-{
-	NMSettingWirelessSecurity *s_wsec;
-	const char *key_mgmt, *auth_alg, *key, *cipher, *psk;
-	gboolean wep = FALSE, wpa = FALSE;
-	char *tmp;
-	guint32 i, num;
-	GString *str;
-
-	s_wsec = nm_connection_get_setting_wireless_security (connection);
-	if (!s_wsec) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing '%s' setting",
-			     NM_SETTING_WIRELESS_SECURITY_SETTING_NAME);
-		return FALSE;
-	}
-
-	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec);
-	g_assert (key_mgmt);
-
-	auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec);
-
-	if (!strcmp (key_mgmt, "none")) {
-		wpa_set_data (conn_name, "key_mgmt", "NONE");
-		wep = TRUE;
-		*no_8021x = TRUE;
-	} else if (!strcmp (key_mgmt, "wpa-none")
-		   || !strcmp (key_mgmt, "wpa-psk")) {
-		wpa_set_data (conn_name, "key_mgmt", "WPA-PSK");
-		wpa = TRUE;
-		*no_8021x = TRUE;
-	} else if (!strcmp (key_mgmt, "ieee8021x")) {
-		wpa_set_data (conn_name, "key_mgmt", "IEEE8021X");
-	} else if (!strcmp (key_mgmt, "wpa-eap")) {
-		wpa_set_data (conn_name, "key_mgmt", "WPA-EAP");
-		wpa = TRUE;
-	} else
-		nm_log_warn (LOGD_SETTINGS, "Unknown key_mgmt: %s", key_mgmt);
-
-	if (auth_alg) {
-		if (!strcmp (auth_alg, "shared"))
-			wpa_set_data (conn_name, "auth_alg", "SHARED");
-		else if (!strcmp (auth_alg, "open"))
-			wpa_set_data (conn_name, "auth_alg", "OPEN");
-		else if (!strcmp (auth_alg, "leap")) {
-			wpa_set_data (conn_name, "auth_alg", "LEAP");
-			wpa_set_data (conn_name, "eap", "LEAP");
-			wpa_set_data (conn_name, "identity", (gchar *)
-				      nm_setting_wireless_security_get_leap_username
-				      (s_wsec));
-			wpa_set_data (conn_name, "password", (gchar *)
-				      nm_setting_wireless_security_get_leap_password
-				      (s_wsec));
-			*no_8021x = TRUE;
-		}
-	} else
-		wpa_set_data (conn_name, "auth_alg", NULL);
-
-	/* Default WEP TX key index */
-	if (wep) {
-		tmp =
-		    g_strdup_printf ("%d",
-				     nm_setting_wireless_security_get_wep_tx_keyidx
-				     (s_wsec));
-		wpa_set_data (conn_name, "wep_tx_keyidx", tmp);
-		g_free (tmp);
-	} else
-		wpa_set_data (conn_name, "wep_tx_keyidx", NULL);
-
-	/* WEP keys */
-	for (i = 0; i < 4; i++) {
-		int length;
-
-		key = nm_setting_wireless_security_get_wep_key (s_wsec, i);
-		if (!key)
-			continue;
-		tmp = g_strdup_printf ("wep_key%d", i);
-		length = strlen (key);
-		if (length == 10 || length == 26 || length == 58)
-			wpa_set_data (conn_name, tmp, (gchar *) key);
-		else {
-			gchar *tmp_key = g_strdup_printf ("\"%s\"", key);
-
-			wpa_set_data (conn_name, tmp, tmp_key);
-			g_free (tmp_key);
-		}
-		g_free (tmp);
-	}
-
-	/* WPA Pairwise ciphers */
-	wpa_set_data (conn_name, "pairwise", NULL);
-	str = g_string_new (NULL);
-	num = nm_setting_wireless_security_get_num_pairwise (s_wsec);
-	for (i = 0; i < num; i++) {
-		if (i > 0)
-			g_string_append_c (str, ' ');
-		cipher = nm_setting_wireless_security_get_pairwise (s_wsec, i);
-		tmp = g_ascii_strup (cipher, -1);
-		g_string_append (str, tmp);
-		g_free (tmp);
-	}
-	if (strlen (str->str))
-		wpa_set_data (conn_name, "pairwise", str->str);
-	g_string_free (str, TRUE);
-
-	/* WPA Group ciphers */
-	wpa_set_data (conn_name, "group", NULL);
-	str = g_string_new (NULL);
-	num = nm_setting_wireless_security_get_num_groups (s_wsec);
-	for (i = 0; i < num; i++) {
-		if (i > 0)
-			g_string_append_c (str, ' ');
-		cipher = nm_setting_wireless_security_get_group (s_wsec, i);
-		tmp = g_ascii_strup (cipher, -1);
-		g_string_append (str, tmp);
-		g_free (tmp);
-	}
-	if (strlen (str->str))
-		wpa_set_data (conn_name, "group", str->str);
-	g_string_free (str, TRUE);
-
-	/* WPA Passphrase */
-	if (wpa) {
-		GString *quoted = NULL;
-
-		psk = nm_setting_wireless_security_get_psk (s_wsec);
-		if (psk && (strlen (psk) != 64)) {
-			quoted = g_string_sized_new (strlen (psk) + 2);
-			g_string_append_c (quoted, '"');
-			g_string_append (quoted, psk);
-			g_string_append_c (quoted, '"');
-		}
-		/* psk will be lost here if we don't check it for NULL */
-		if (psk)
-			wpa_set_data (conn_name, "psk",
-					  quoted ? quoted->str : (gchar *) psk);
-		if (quoted)
-			g_string_free (quoted, TRUE);
-	} else
-		wpa_set_data (conn_name, "psk", NULL);
-
-	return TRUE;
-}
-
-/* remove old ssid and add new one*/
-static void
-update_wireless_ssid (NMConnection *connection,
-                      const char *conn_name,
-                      const char *ssid,
-                      gboolean hex)
-{
-	if(strcmp (conn_name, ssid)){
-		ifnet_delete_network (conn_name);
-		wpa_delete_security (conn_name);
-	}
-
-	ifnet_add_network (ssid, "wireless");
-	wpa_add_security (ssid);
-}
-
-static gboolean
-write_wireless_setting (NMConnection *connection,
-                        const char *conn_name,
-                        gboolean *no_8021x,
-                        const char **out_new_name,
-                        GError **error)
-{
-	NMSettingWireless *s_wireless;
-	GBytes *ssid;
-	const guint8 *ssid_data;
-	gsize ssid_len;
-	const char *mac, *bssid, *mode;
-	char buf[33];
-	guint32 mtu, i;
-	gboolean adhoc = FALSE, hex_ssid = FALSE;
-	gchar *ssid_str, *tmp;
-
-	s_wireless = nm_connection_get_setting_wireless (connection);
-	if (!s_wireless) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing '%s' setting",
-			     NM_SETTING_WIRELESS_SETTING_NAME);
-		return FALSE;
-	}
-
-	ssid = nm_setting_wireless_get_ssid (s_wireless);
-	if (!ssid) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing SSID in '%s' setting",
-			     NM_SETTING_WIRELESS_SETTING_NAME);
-		return FALSE;
-	}
-	ssid_data = g_bytes_get_data (ssid, &ssid_len);
-	if (!ssid_len || ssid_len > 32) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Invalid SSID in '%s' setting",
-			     NM_SETTING_WIRELESS_SETTING_NAME);
-		return FALSE;
-	}
-
-	/* If the SSID contains any non-alnum characters, we need to use
-	 * the hex notation of the SSID instead. (Because openrc doesn't
-	 * support these characters, see bug #356337)
-	 */
-	for (i = 0; i < ssid_len; i++) {
-		if (!g_ascii_isalnum (ssid_data[i])) {
-			hex_ssid = TRUE;
-			break;
-		}
-	}
-
-	if (hex_ssid) {
-		GString *str;
-
-		/* Hex SSIDs don't get quoted */
-		str = g_string_sized_new (ssid_len * 2 + 3);
-		g_string_append (str, "0x");
-		for (i = 0; i < ssid_len; i++)
-			g_string_append_printf (str, "%02X", ssid_data[i]);
-		update_wireless_ssid (connection, conn_name, str->str, hex_ssid);
-		ssid_str = g_string_free (str, FALSE);
-	} else {
-		/* Printable SSIDs get quoted */
-		memset (buf, 0, sizeof (buf));
-		memcpy (buf, ssid_data, ssid_len);
-		g_strstrip (buf);
-		update_wireless_ssid (connection, conn_name, buf, hex_ssid);
-		ssid_str = g_strdup (buf);
-	}
-
-	ifnet_set_data (ssid_str, "mac", NULL);
-	mac = nm_setting_wireless_get_mac_address (s_wireless);
-	if (mac)
-		ifnet_set_data (ssid_str, "mac", mac);
-
-	ifnet_set_data (ssid_str, "mtu", NULL);
-	mtu = nm_setting_wireless_get_mtu (s_wireless);
-	if (mtu) {
-		tmp = g_strdup_printf ("%u", mtu);
-		ifnet_set_data (ssid_str, "mtu", tmp);
-		g_free (tmp);
-	}
-
-	ifnet_set_data (ssid_str, "mode", NULL);
-	mode = nm_setting_wireless_get_mode (s_wireless);
-	if (!mode || !strcmp (mode, "infrastructure")) {
-		wpa_set_data (ssid_str, "mode", "0");
-	} else if (!strcmp (mode, "adhoc")) {
-		wpa_set_data (ssid_str, "mode", "1");
-		adhoc = TRUE;
-	} else {
-		nm_log_warn (LOGD_SETTINGS, "Invalid mode '%s' in '%s' setting",
-		             mode, NM_SETTING_WIRELESS_SETTING_NAME);
-		return FALSE;
-	}
-
-	wpa_set_data (ssid_str, "bssid", NULL);
-	bssid = nm_setting_wireless_get_bssid (s_wireless);
-	if (bssid)
-		wpa_set_data (ssid_str, "bssid", bssid);
-
-	if (nm_connection_get_setting_wireless_security (connection)) {
-		if (!write_wireless_security_setting
-		    (connection, ssid_str, adhoc, no_8021x, error))
-			return FALSE;
-	} else
-		wpa_delete_security (ssid_str);
-
-	if (out_new_name)
-		*out_new_name = ifnet_get_data (ssid_str, "name");
-	g_free (ssid_str);
-	return TRUE;
-}
-
-static gboolean
-write_wired_setting (NMConnection *connection,
-                     const char *conn_name,
-                     GError **error)
-{
-	NMSettingWired *s_wired;
-	const char *mac;
-	char *tmp;
-	guint32 mtu;
-
-	s_wired = nm_connection_get_setting_wired (connection);
-	if (!s_wired) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing '%s' setting",
-			     NM_SETTING_WIRED_SETTING_NAME);
-		return FALSE;
-	}
-
-	ifnet_set_data (conn_name, "mac", NULL);
-	mac = nm_setting_wired_get_mac_address (s_wired);
-	if (mac)
-		ifnet_set_data (conn_name, "mac", mac);
-
-	ifnet_set_data (conn_name, "mtu", NULL);
-	mtu = nm_setting_wired_get_mtu (s_wired);
-	if (mtu) {
-		tmp = g_strdup_printf ("%u", mtu);
-		ifnet_set_data (conn_name, "mtu", tmp);
-		g_free (tmp);
-	}
-	//FIXME may add connection type in future
-	//ifnet_set_data (conn_name, "TYPE", TYPE_ETHERNET);
-
-	return TRUE;
-}
-
-static gboolean
-write_ip4_setting (NMConnection *connection, const char *conn_name, GError **error)
-{
-	NMSettingIPConfig *s_ip4;
-	const char *value;
-	guint32 i, num;
-	GString *searches;
-	GString *ips;
-	GString *routes;
-	GString *dns;
-	gboolean success = FALSE;
-
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	if (!s_ip4) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing '%s' setting",
-			     NM_SETTING_IP4_CONFIG_SETTING_NAME);
-		return FALSE;
-	}
-	routes = g_string_new (NULL);
-
-	value = nm_setting_ip_config_get_method (s_ip4);
-	g_assert (value);
-	if (!strcmp (value, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
-
-		num = nm_setting_ip_config_get_num_addresses (s_ip4);
-		ips = g_string_new (NULL);
-		/* IPv4 addresses */
-		for (i = 0; i < num; i++) {
-			NMIPAddress *addr;
-
-			addr = nm_setting_ip_config_get_address (s_ip4, i);
-
-			g_string_append_printf (ips, "\"%s/%u",
-			                        nm_ip_address_get_address (addr),
-			                        nm_ip_address_get_prefix (addr));
-
-			/* only the first gateway will be written */
-			if (i == 0 && nm_setting_ip_config_get_gateway (s_ip4)) {
-				g_string_append_printf (routes,
-				                        "\"default via %s\" ",
-				                        nm_setting_ip_config_get_gateway (s_ip4));
-			}
-		}
-		ifnet_set_data (conn_name, "config", ips->str);
-		g_string_free (ips, TRUE);
-	} else if (!strcmp (value, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
-		ifnet_set_data (conn_name, "config", "shared");
-	else if (!strcmp (value, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
-		ifnet_set_data (conn_name, "config", "autoip");
-	else
-		ifnet_set_data (conn_name, "config", "dhcp");
-
-	/* DNS Servers */
-	num = nm_setting_ip_config_get_num_dns (s_ip4);
-	if (num > 0) {
-		dns = g_string_new (NULL);
-		for (i = 0; i < num; i++) {
-			const char *ip;
-
-			ip = nm_setting_ip_config_get_dns (s_ip4, i);
-			g_string_append_printf (dns, " %s", ip);
-		}
-		ifnet_set_data (conn_name, "dns_servers", dns->str);
-		g_string_free (dns, TRUE);
-	} else
-		ifnet_set_data (conn_name, "dns_servers", NULL);
-
-	/* DNS Searches */
-	num = nm_setting_ip_config_get_num_dns_searches (s_ip4);
-	if (num > 0) {
-		searches = g_string_new (NULL);
-		for (i = 0; i < num; i++) {
-			if (i > 0)
-				g_string_append_c (searches, ' ');
-			g_string_append (searches,
-					 nm_setting_ip_config_get_dns_search
-					 (s_ip4, i));
-		}
-		ifnet_set_data (conn_name, "dns_search", searches->str);
-		g_string_free (searches, TRUE);
-	} else
-		ifnet_set_data (conn_name, "dns_search", NULL);
-	/* FIXME Will be implemented when configuration supports it
-	   if (!strcmp(value, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
-	   value = nm_setting_ip_config_get_dhcp_hostname(s_ip4);
-	   if (value)
-	   ifnet_set_data(conn_name, "DHCP_HOSTNAME", value,
-	   FALSE);
-
-	   value = nm_setting_ip_config_get_dhcp_client_id(s_ip4);
-	   if (value)
-	   ifnet_set_data(conn_name, "DHCP_CLIENT_ID", value,
-	   FALSE);
-	   }
-	 */
-
-	/* Static routes */
-	num = nm_setting_ip_config_get_num_routes (s_ip4);
-	if (num > 0) {
-		for (i = 0; i < num; i++) {
-			NMIPRoute *route;
-			const char *next_hop;
-
-			route = nm_setting_ip_config_get_route (s_ip4, i);
-
-			next_hop = nm_ip_route_get_next_hop (route);
-			if (!next_hop)
-				next_hop = "0.0.0.0";
-
-			g_string_append_printf (routes, "\"%s/%u via %s\" ",
-			                        nm_ip_route_get_dest (route),
-			                        nm_ip_route_get_prefix (route),
-			                        next_hop);
-		}
-	}
-	if (routes->len > 0)
-		ifnet_set_data (conn_name, "routes", routes->str);
-	else
-		ifnet_set_data (conn_name, "routes", NULL);
-	g_string_free (routes, TRUE);
-
-	success = TRUE;
-
-	return success;
-}
-
-static void
-write_route6_file (NMSettingIPConfig *s_ip6, const char *conn_name)
-{
-	NMIPRoute *route;
-	const char *next_hop;
-	guint32 i, num;
-	GString *routes_string;
-	const char *old_routes;
-
-	g_return_if_fail (s_ip6 != NULL);
-	num = nm_setting_ip_config_get_num_routes (s_ip6);
-	if (num == 0)
-		return;
-
-	old_routes = ifnet_get_data (conn_name, "routes");
-	routes_string = g_string_new (old_routes);
-	if (old_routes)
-		g_string_append (routes_string, "\" ");
-	for (i = 0; i < num; i++) {
-		route = nm_setting_ip_config_get_route (s_ip6, i);
-
-		next_hop = nm_ip_route_get_next_hop (route);
-		if (!next_hop)
-			next_hop = "::";
-
-		g_string_append_printf (routes_string, "\"%s/%u via %s\" ",
-		                        nm_ip_route_get_dest (route),
-		                        nm_ip_route_get_prefix (route),
-		                        next_hop);
-	}
-	if (num > 0)
-		ifnet_set_data (conn_name, "routes", routes_string->str);
-	g_string_free (routes_string, TRUE);
-}
-
-static gboolean
-write_ip6_setting (NMConnection *connection, const char *conn_name, GError **error)
-{
-	NMSettingIPConfig *s_ip6;
-	const char *value;
-	guint32 i, num;
-	GString *searches;
-	NMIPAddress *addr;
-
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	if (!s_ip6) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing '%s' setting",
-			     NM_SETTING_IP6_CONFIG_SETTING_NAME);
-		return FALSE;
-	}
-
-	value = nm_setting_ip_config_get_method (s_ip6);
-	g_assert (value);
-	if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
-		ifnet_set_data (conn_name, "enable_ipv6", "false");
-		return TRUE;
-	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
-		/* nothing to do now */
-	} else {
-		// if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
-		const char *config = ifnet_get_data (conn_name, "config");
-		gchar *tmp;
-
-		if (!config)
-			tmp = g_strdup_printf ("dhcp6");
-		else
-			tmp = g_strdup_printf ("%s\" \"dhcp6\"", config);
-		ifnet_set_data (conn_name, "config", tmp);
-		g_free (tmp);
-	}
-	/* else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
-	   } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
-	   } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
-	   } */
-
-	/* Remember to set IPv6 enabled */
-	ifnet_set_data (conn_name, "enable_ipv6", "true");
-
-	if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
-		const char *config = ifnet_get_data (conn_name, "config");
-		gchar *tmp;
-		GString *ip_str;
-
-		if (!config)
-			config = "";
-		num = nm_setting_ip_config_get_num_addresses (s_ip6);
-
-		/* IPv6 addresses */
-		ip_str = g_string_new (NULL);
-		for (i = 0; i < num; i++) {
-			addr = nm_setting_ip_config_get_address (s_ip6, i);
-
-			g_string_append_printf (ip_str, "\"%s/%u\"",
-			                        nm_ip_address_get_address (addr),
-			                        nm_ip_address_get_prefix (addr));
-		}
-		tmp = g_strdup_printf ("%s\" %s", config, ip_str->str);
-		ifnet_set_data (conn_name, "config", tmp);
-		g_free (tmp);
-		g_string_free (ip_str, TRUE);
-	}
-
-	/* DNS Servers */
-	num = nm_setting_ip_config_get_num_dns (s_ip6);
-	if (num > 0) {
-		const char *dns_servers = ifnet_get_data (conn_name, "dns_servers");
-		gchar *tmp;
-		GString *dns_string = g_string_new (NULL);
-		const char *dns;
-
-		if (!dns_servers)
-			dns_servers = "";
-		for (i = 0; i < num; i++) {
-			dns = nm_setting_ip_config_get_dns (s_ip6, i);
-
-			if (!strstr (dns_servers, dns))
-				g_string_append_printf (dns_string, "%s ", dns);
-		}
-		tmp = g_strdup_printf ("%s %s", dns_servers, dns_string->str);
-		ifnet_set_data (conn_name, "dns_servers", tmp);
-		g_free (tmp);
-		g_string_free (dns_string, TRUE);
-
-	} else
-		/* DNS Searches */
-		num = nm_setting_ip_config_get_num_dns_searches (s_ip6);
-	if (num > 0) {
-		const char *ip4_domains;
-
-		ip4_domains = ifnet_get_data (conn_name, "dns_search");
-		if (!ip4_domains)
-			ip4_domains = "";
-		searches = g_string_new (ip4_domains);
-		for (i = 0; i < num; i++) {
-			const gchar *search = NULL;
-
-			search =
-			    nm_setting_ip_config_get_dns_search (s_ip6, i);
-			if (search && !strstr (searches->str, search)) {
-				if (searches->len > 0)
-					g_string_append_c (searches, ' ');
-				g_string_append (searches, search);
-			}
-		}
-		ifnet_set_data (conn_name, "dns_search", searches->str);
-		g_string_free (searches, TRUE);
-	}
-
-	write_route6_file (s_ip6, conn_name);
-	return TRUE;
-}
-
-static gboolean
-write_pppoe_setting (const char *conn_name, NMSettingPppoe * s_pppoe)
-{
-	const gchar *value;
-
-	value = nm_setting_pppoe_get_username (s_pppoe);
-	if (!value) {
-		return FALSE;
-	}
-	ifnet_set_data (conn_name, "username", (gchar *) value);
-
-	value = nm_setting_pppoe_get_password (s_pppoe);
-	/* password could be NULL here */
-	if (value) {
-		ifnet_set_data (conn_name, "password", (gchar *) value);
-	}
-	return TRUE;
-}
-
-gboolean
-ifnet_update_parsers_by_connection (NMConnection *connection,
-                                    const char *conn_name,
-                                    const char *config_file,
-                                    const char *wpa_file,
-                                    gchar **out_new_name,
-                                    gchar **out_backup,
-                                    GError **error)
-{
-	NMSettingConnection *s_con;
-	NMSettingIPConfig *s_ip6;
-	gboolean success = FALSE;
-	const char *type;
-	gboolean no_8021x = FALSE;
-	gboolean wired = FALSE, pppoe = TRUE;
-	const char *new_name = NULL;
-
-	if (!ifnet_can_write_connection (connection, error))
-		return FALSE;
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-
-	type = nm_setting_connection_get_connection_type (s_con);
-	if (!type) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			     "Missing connection type!");
-		goto out;
-	}
-
-	if (!strcmp (type, NM_SETTING_WIRED_SETTING_NAME)) {
-		/* Writing wired setting */
-		if (!write_wired_setting (connection, conn_name, error))
-			goto out;
-		wired = TRUE;
-		no_8021x = TRUE;
-	} else if (!strcmp (type, NM_SETTING_WIRELESS_SETTING_NAME)) {
-		/* Writing wireless setting */
-		if (!write_wireless_setting (connection, conn_name, &no_8021x, &new_name, error))
-			goto out;
-	} else if (!strcmp (type, NM_SETTING_PPPOE_SETTING_NAME)) {
-		NMSettingPppoe *s_pppoe;
-
-		/* Writing pppoe setting */
-		s_pppoe = nm_connection_get_setting_pppoe (connection);
-		if (!write_pppoe_setting (conn_name, s_pppoe))
-			goto out;
-		pppoe = TRUE;
-		wired = TRUE;
-		no_8021x = TRUE;
-	} else {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_NOT_SUPPORTED,
-			     "Can't write connection type '%s'", type);
-		goto out;
-	}
-
-	/* connection name may have been updated; use it when writing out
-	 * the rest of the settings.
-	 */
-	if (new_name)
-		conn_name = new_name;
-
-	//FIXME wired connection doesn't support 8021x now
-	if (!no_8021x) {
-		if (!write_8021x_setting (connection, conn_name, wired, error))
-			goto out;
-	}
-
-	/* IPv4 Setting */
-	if (!write_ip4_setting (connection, conn_name, error))
-		goto out;
-
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	if (s_ip6) {
-		/* IPv6 Setting */
-		if (!write_ip6_setting (connection, conn_name, error))
-			goto out;
-	}
-
-	/* Connection Setting */
-	ifnet_set_data (conn_name, "auto",
-	                nm_setting_connection_get_autoconnect (s_con) ? "true" : "false");
-	ifnet_set_data (conn_name, "uuid", nm_connection_get_uuid (connection));
-
-	/* Write changes to disk */
-	success = ifnet_flush_to_file (config_file, out_backup);
-	if (success)
-		wpa_flush_to_file (wpa_file);
-
-	if (out_new_name)
-		*out_new_name = g_strdup (conn_name);
-
-out:
-	return success;
-}
-
-gboolean
-ifnet_delete_connection_in_parsers (const char *conn_name,
-                                    const char *config_file,
-                                    const char *wpa_file,
-                                    gchar **out_backup)
-{
-	gboolean result = FALSE;
-
-	ifnet_delete_network (conn_name);
-	result = ifnet_flush_to_file (config_file, out_backup);
-	if (result) {
-		/* connection may not have security information
-		 * so simply ignore the return value*/
-		wpa_delete_security (conn_name);
-		wpa_flush_to_file (wpa_file);
-	}
-
-	return result;
-}
-
-static void
-check_unsupported_secrets (NMSetting  *setting,
-                           const char *key,
-                           const GValue *value,
-                           GParamFlags flags,
-                           gpointer user_data)
-{
-	gboolean *unsupported_secret = user_data;
-
-	if (flags & NM_SETTING_PARAM_SECRET) {
-		NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-
-		if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL))
-			g_return_if_reached ();
-		if (secret_flags != NM_SETTING_SECRET_FLAG_NONE)
-			*unsupported_secret = TRUE;
-	}
-}
-
-gboolean
-ifnet_can_write_connection (NMConnection *connection, GError **error)
-{
-	NMSettingConnection *s_con;
-	gboolean has_unsupported_secrets = FALSE;
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-
-	/* If the connection is not available for all users, ignore
-	 * it as this plugin only deals with System Connections */
-	if (nm_setting_connection_get_num_permissions (s_con)) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_NOT_SUPPORTED,
-		                     "The ifnet plugin does not support non-system-wide connections.");
-		return FALSE;
-	}
-
-	/* Only support wired, wifi, and PPPoE */
-	if (   !nm_connection_is_type (connection, NM_SETTING_WIRED_SETTING_NAME)
-	    && !nm_connection_is_type (connection, NM_SETTING_WIRELESS_SETTING_NAME)
-	    && !nm_connection_is_type (connection, NM_SETTING_PPPOE_SETTING_NAME)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_NOT_SUPPORTED,
-		             "The ifnet plugin cannot write the connection '%s' (type '%s')",
-		             nm_connection_get_id (connection),
-		             nm_setting_connection_get_connection_type (s_con));
-		return FALSE;
-	}
-
-	/* If the connection has flagged secrets, ignore
-	 * it as this plugin does not deal with user agent service */
-	nm_connection_for_each_setting_value (connection,
-	                                      check_unsupported_secrets,
-	                                      &has_unsupported_secrets);
-	if (has_unsupported_secrets) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_NOT_SUPPORTED,
-		                     "The ifnet plugin only supports persistent system secrets.");
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
-/* get the available wired name(eth*). */
-static gchar *
-get_wired_name (void)
-{
-	int i = 0;
-
-	for (; i < 256; i++) {
-		gchar *conn_name = g_strdup_printf ("eth%d", i);
-
-		if (!ifnet_has_network (conn_name)) {
-			return conn_name;
-		} else
-			g_free (conn_name);
-	}
-	return NULL;
-}
-
-/* get the available pppoe name(ppp*). */
-static gchar *
-get_ppp_name (void)
-{
-	int i = 0;
-
-	for (; i < 256; i++) {
-		gchar *conn_name = g_strdup_printf ("ppp%d", i);
-
-		if (!ifnet_has_network (conn_name)) {
-			return conn_name;
-		} else
-			g_free (conn_name);
-	}
-	return NULL;
-}
-
-/* get wireless ssid */
-static gchar *
-get_wireless_name (NMConnection * connection)
-{
-	NMSettingWireless *s_wireless;
-	GBytes *ssid;
-	const guint8 *ssid_data;
-	gsize ssid_len;
-	gboolean hex_ssid = FALSE;
-	gchar *result = NULL;
-	char buf[33];
-	int i = 0;
-
-	s_wireless = nm_connection_get_setting_wireless (connection);
-	if (!s_wireless)
-		return NULL;
-
-	ssid = nm_setting_wireless_get_ssid (s_wireless);
-	ssid_data = g_bytes_get_data (ssid, &ssid_len);
-	if (!ssid_len || ssid_len > 32) {
-		return NULL;
-	}
-
-	for (i = 0; i < ssid_len; i++) {
-		if (!g_ascii_isprint (ssid_data[i])) {
-			hex_ssid = TRUE;
-			break;
-		}
-	}
-
-	if (hex_ssid) {
-		GString *str;
-
-		str = g_string_sized_new (ssid_len * 2 + 3);
-		g_string_append (str, "0x");
-		for (i = 0; i < ssid_len; i++)
-			g_string_append_printf (str, "%02X", ssid_data[i]);
-		result = g_strdup (str->str);
-		g_string_free (str, TRUE);
-	} else {
-		memset (buf, 0, sizeof (buf));
-		memcpy (buf, ssid_data, ssid_len);
-		result = g_strdup_printf ("%s", buf);
-		g_strstrip (result);
-	}
-
-	return result;
-}
-
-gboolean
-ifnet_add_new_connection (NMConnection *connection,
-                          const char *config_file,
-                          const char *wpa_file,
-                          gchar **out_new_name,
-                          gchar **out_backup,
-                          GError **error)
-{
-	NMSettingConnection *s_con;
-	gboolean success = FALSE;
-	const char *type;
-	gchar *new_type, *new_name = NULL;
-
-	if (!ifnet_can_write_connection (connection, error))
-		return FALSE;
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-	type = nm_setting_connection_get_connection_type (s_con);
-	g_assert (type);
-
-	nm_log_info (LOGD_SETTINGS, "Adding %s connection", type);
-
-	/* get name and type
-	 * Wireless type: wireless
-	 * Wired type: wired
-	 * PPPoE type: ppp*/
-	if (!strcmp (type, NM_SETTING_WIRED_SETTING_NAME)) {
-		new_name = get_wired_name ();
-		if (!new_name)
-			goto out;
-		new_type = "wired";
-	} else if (!strcmp (type, NM_SETTING_WIRELESS_SETTING_NAME)) {
-		new_name = get_wireless_name (connection);
-		new_type = "wireless";
-	} else if (!strcmp (type, NM_SETTING_PPPOE_SETTING_NAME)) {
-		new_name = get_ppp_name ();
-		if (!new_name)
-			goto out;
-		new_type = "ppp";
-	} else {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_NOT_SUPPORTED,
-			     "Can't write connection type '%s'", type);
-		goto out;
-	}
-
-	if (ifnet_add_network (new_name, new_type)) {
-		success = ifnet_update_parsers_by_connection (connection,
-		                                              new_name,
-		                                              config_file,
-		                                              wpa_file,
-		                                              NULL,
-		                                              out_backup,
-		                                              error);
-	}
-
-	nm_log_info (LOGD_SETTINGS, "Added new connection: %s, result: %s",
-	             new_name, success ? "success" : "fail");
-
-out:
-	if (!success || !out_new_name)
-		g_free (new_name);
-	else if (out_new_name)
-		*out_new_name = new_name;
-	return success;
-}
-
diff --git a/src/settings/plugins/ifnet/nms-ifnet-connection-parser.h b/src/settings/plugins/ifnet/nms-ifnet-connection-parser.h
deleted file mode 100644
index 51bc34b9..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-connection-parser.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* 
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#ifndef _CONNECTION_PARSER_H
-#define _CONNECTION_PARSER_H
-
-#include "nm-connection.h"
-
-#include "nms-ifnet-net-parser.h"
-
-gboolean ifnet_can_write_connection (NMConnection *connection, GError **error);
-
-NMConnection *ifnet_update_connection_from_config_block (const char *conn_name,
-                                                         const char *basepath,
-                                                         GError **error);
-
-/* nm_conn_name is used to update nm_ifnet_connection's priv data */
-gboolean ifnet_update_parsers_by_connection (NMConnection *connection,
-                                             const char *conn_name,
-                                             const char *config_file,
-                                             const char *wpa_file,
-                                             gchar **out_new_name,
-                                             gchar **out_backup,
-                                             GError **error);
-
-gboolean ifnet_delete_connection_in_parsers (const char *conn_name,
-                                             const char *config_file,
-                                             const char *wpa_file,
-                                             gchar **out_backup);
-
-gboolean ifnet_add_new_connection (NMConnection *connection,
-                                   const char *config_file,
-                                   const char *wpa_file,
-                                   gchar **out_new_name,
-                                   gchar **out_backup,
-                                   GError ** error);
-#endif
diff --git a/src/settings/plugins/ifnet/nms-ifnet-connection.c b/src/settings/plugins/ifnet/nms-ifnet-connection.c
deleted file mode 100644
index ce0b3f2b..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-connection.c
+++ /dev/null
@@ -1,233 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ifnet-connection.h"
-
-#include <string.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-ifnet-connection-parser.h"
-#include "nms-ifnet-net-parser.h"
-#include "nms-ifnet-net-utils.h"
-#include "nms-ifnet-wpa-parser.h"
-#include "nms-ifnet-plugin.h"
-
-/*****************************************************************************/
-
-enum {
-	IFNET_SETUP_MONITORS,
-	IFNET_CANCEL_MONITORS,
-	IFNET_LAST_SIGNAL
-};
-
-static guint signals[IFNET_LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	gchar *conn_name;
-	NMSettingsPlugin *config;
-} NMIfnetConnectionPrivate;
-
-struct _NMIfnetConnection {
-	NMSettingsConnection parent;
-	NMIfnetConnectionPrivate _priv;
-};
-
-struct _NMIfnetConnectionClass {
-	NMSettingsConnectionClass parent;
-};
-
-G_DEFINE_TYPE (NMIfnetConnection, nm_ifnet_connection, NM_TYPE_SETTINGS_CONNECTION)
-
-#define NM_IFNET_CONNECTION_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMIfnetConnection, NM_IS_IFNET_CONNECTION)
-
-/*****************************************************************************/
-
-const char *
-nm_ifnet_connection_get_conn_name (NMIfnetConnection *connection)
-{
-	return NM_IFNET_CONNECTION_GET_PRIVATE (connection)->conn_name;
-}
-
-static gboolean
-commit_changes (NMSettingsConnection *connection,
-                NMConnection *new_connection,
-                NMSettingsConnectionCommitReason commit_reason,
-                NMConnection **out_reread_connection,
-                char **out_logmsg_change,
-                GError **error)
-{
-	NMIfnetConnectionPrivate *priv = NM_IFNET_CONNECTION_GET_PRIVATE ((NMIfnetConnection *) connection);
-	char *new_name = NULL;
-	gboolean success = FALSE;
-	gboolean added = FALSE;
-
-	nm_assert (out_reread_connection && !*out_reread_connection);
-	nm_assert (!out_logmsg_change || !*out_logmsg_change);
-
-	g_signal_emit (connection, signals[IFNET_CANCEL_MONITORS], 0);
-
-	if (priv->conn_name) {
-		success = ifnet_update_parsers_by_connection (new_connection,
-		                                              priv->conn_name,
-		                                              CONF_NET_FILE,
-		                                              WPA_SUPPLICANT_CONF,
-		                                              &new_name,
-		                                              NULL,
-		                                              error);
-	} else {
-		added = TRUE;
-		success = ifnet_add_new_connection (new_connection,
-		                                    CONF_NET_FILE,
-		                                    WPA_SUPPLICANT_CONF,
-		                                    &new_name,
-		                                    NULL,
-		                                    error);
-	}
-
-	g_assert (!!success == (new_name != NULL));
-	if (success) {
-		g_free (priv->conn_name);
-		priv->conn_name = new_name;
-	}
-
-	reload_parsers ();
-
-	g_signal_emit (connection, signals[IFNET_SETUP_MONITORS], 0);
-
-	if (success) {
-		NM_SET_OUT (out_logmsg_change,
-		            g_strdup_printf ("ifcfg-rh: %s %s",
-		                             added ? "persist" : "updated",
-		                             new_name));
-	}
-	return success;
-}
-
-static gboolean
-delete (NMSettingsConnection *connection,
-        GError **error)
-{
-	NMIfnetConnectionPrivate *priv = NM_IFNET_CONNECTION_GET_PRIVATE ((NMIfnetConnection *) connection);
-
-	/* Only connections which exist in /etc/conf.d/net will have a conn_name */
-	if (priv->conn_name) {
-		g_signal_emit (connection, signals[IFNET_CANCEL_MONITORS], 0);
-
-		if (!ifnet_delete_connection_in_parsers (priv->conn_name, CONF_NET_FILE, WPA_SUPPLICANT_CONF, NULL)) {
-			nm_log_warn (LOGD_SETTINGS, "Failed to delete %s", priv->conn_name);
-			reload_parsers ();
-			/* let's not return an error. */
-		}
-
-		g_signal_emit (connection, signals[IFNET_SETUP_MONITORS], 0);
-	}
-
-	return TRUE;
-}
-
-/*****************************************************************************/
-
-static void
-nm_ifnet_connection_init (NMIfnetConnection * connection)
-{
-}
-
-NMIfnetConnection *
-nm_ifnet_connection_new (NMConnection *source, const char *conn_name)
-{
-	NMConnection *tmp;
-	GObject *object;
-	GError *error = NULL;
-	gboolean update_unsaved = TRUE;
-
-	g_return_val_if_fail (source || conn_name, NULL);
-
-	if (source)
-		tmp = g_object_ref (source);
-	else {
-		tmp = ifnet_update_connection_from_config_block (conn_name, NULL, &error);
-		if (!tmp) {
-			nm_log_warn (LOGD_SETTINGS, "Could not read connection '%s': %s",
-			             conn_name, error->message);
-			g_error_free (error);
-			return NULL;
-		}
-
-		/* If we just read the connection from disk, it's clearly not Unsaved */
-		update_unsaved = FALSE;
-	}
-
-	object = (GObject *) g_object_new (NM_TYPE_IFNET_CONNECTION, NULL);
-
-	NM_IFNET_CONNECTION_GET_PRIVATE ((NMIfnetConnection *) object)->conn_name = g_strdup (conn_name);
-	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,
-	                                    NULL)) {
-		g_object_unref (object);
-		return NULL;
-	}
-	g_object_unref (tmp);
-
-	return NM_IFNET_CONNECTION (object);
-}
-
-static void
-finalize (GObject * object)
-{
-	g_free (NM_IFNET_CONNECTION_GET_PRIVATE ((NMIfnetConnection *) object)->conn_name);
-	G_OBJECT_CLASS (nm_ifnet_connection_parent_class)->finalize (object);
-}
-
-static void
-nm_ifnet_connection_class_init (NMIfnetConnectionClass * ifnet_connection_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (ifnet_connection_class);
-	NMSettingsConnectionClass *settings_class = NM_SETTINGS_CONNECTION_CLASS (ifnet_connection_class);
-
-	object_class->finalize = finalize;
-
-	settings_class->delete = delete;
-	settings_class->commit_changes = commit_changes;
-
-	signals[IFNET_SETUP_MONITORS] =
-	    g_signal_new ("ifnet_setup_monitors",
-	                  G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST,
-	                  0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
-	                  G_TYPE_NONE, 0);
-	signals[IFNET_CANCEL_MONITORS] =
-	    g_signal_new ("ifnet_cancel_monitors",
-	                  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/ifnet/nms-ifnet-connection.h b/src/settings/plugins/ifnet/nms-ifnet-connection.h
deleted file mode 100644
index 1bc06644..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-connection.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#ifndef __NETWORKMANAGER_IFNET_CONNECTION_H__
-#define __NETWORKMANAGER_IFNET_CONNECTION_H__
-
-#include "settings/nm-settings-connection.h"
-
-#include "nms-ifnet-net-parser.h"
-
-#define NM_TYPE_IFNET_CONNECTION            (nm_ifnet_connection_get_type ())
-#define NM_IFNET_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IFNET_CONNECTION, NMIfnetConnection))
-#define NM_IFNET_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_IFNET_CONNECTION, NMIfnetConnectionClass))
-#define NM_IS_IFNET_CONNECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_IFNET_CONNECTION))
-#define NM_IS_IFNET_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_IFNET_CONNECTION))
-#define NM_IFNET_CONNECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_IFNET_CONNECTION, NMIfnetConnectionClass))
-
-typedef struct _NMIfnetConnection NMIfnetConnection;
-typedef struct _NMIfnetConnectionClass NMIfnetConnectionClass;
-
-GType nm_ifnet_connection_get_type (void);
-
-NMIfnetConnection *nm_ifnet_connection_new (NMConnection *source,
-                                            const char *conn_name);
-
-const char *nm_ifnet_connection_get_conn_name (NMIfnetConnection *connection);
-
-#endif /* __NETWORKMANAGER_IFNET_CONNECTION_H__ */
diff --git a/src/settings/plugins/ifnet/nms-ifnet-net-parser.c b/src/settings/plugins/ifnet/nms-ifnet-net-parser.c
deleted file mode 100644
index d3e47219..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-net-parser.c
+++ /dev/null
@@ -1,734 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ifnet-net-parser.h"
-
-#include <string.h>
-#include <stdio.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-#include "settings/nm-settings-plugin.h"
-#include "platform/nm-platform.h"
-
-#include "nms-ifnet-plugin.h"
-#include "nms-ifnet-net-utils.h"
-
-/* Save all the connection information */
-static GHashTable *conn_table;
-
-/* Save global settings which are used for writing*/
-static GHashTable *global_settings_table;
-
-/* Save functions */
-static GList *functions_list;
-
-/* Used to decide whether to write changes to file*/
-static gboolean net_parser_data_changed = FALSE;
-
-static GHashTable *
-add_new_connection_config (const gchar * type, const gchar * name)
-{
-	GHashTable *new_conn;
-	gchar *new_name;
-
-	if (!name)
-		return NULL;
-
-	/* Return existing connection */
-	if ((new_conn = g_hash_table_lookup (conn_table, name)) != NULL)
-		return new_conn;
-	new_conn = g_hash_table_new (nm_str_hash, g_str_equal);
-	new_name = g_strdup (name);
-	g_hash_table_insert (new_conn, g_strdup ("name"), new_name);
-	g_hash_table_insert (new_conn, g_strdup ("type"), g_strdup (type));
-	g_hash_table_insert (conn_table, new_name, new_conn);
-	return new_conn;
-}
-
-gboolean
-ifnet_add_network (const char *name, const char *type)
-{
-	if (ifnet_has_network (name))
-		return TRUE;
-	if (add_new_connection_config (type, name)) {
-		nm_log_info (LOGD_SETTINGS, "Adding network for %s", name);
-		net_parser_data_changed = TRUE;
-		return TRUE;
-	}
-	return FALSE;
-}
-
-gboolean
-ifnet_has_network (const char *conn_name)
-{
-	return g_hash_table_lookup (conn_table, conn_name) != NULL;
-}
-
-static GHashTable *
-get_connection_config (const char *name)
-{
-	return g_hash_table_lookup (conn_table, name);
-}
-
-/* Ignored name won't be treated as wireless ssid */
-static gchar *ignore_name[] = {
-	"vlan", "bond", "atm", "ath", "ippp", "vpn", "tap", "tun", "1",
-	"br", "nas", "6to4", "timeout", "kvm", "force", NULL
-};
-
-static gboolean
-ignore_connection_name (const char *name)
-{
-	gboolean result = FALSE;
-	guint i = 0;
-
-	/* check ignore_name list */
-	while (ignore_name[i] != NULL) {
-		if (g_ascii_strncasecmp
-		    (name, ignore_name[i], strlen (ignore_name[i])) == 0) {
-			return TRUE;
-		}
-		i++;
-	}
-	/* Ignore mac address based configuration */
-	if (strlen (name) == 12 && is_hex (name))
-		result = TRUE;
-	return result;
-}
-
-static gboolean
-is_global_setting (char *key)
-{
-	static gchar *global_settings[] = { "wpa_supplicant_", NULL };
-	int i;
-
-	for (i = 0; global_settings[i] != NULL; i++) {
-		if (strstr (key, global_settings[i]))
-			return 1;
-	}
-	return 0;
-}
-
-/* Parse a complete line */
-/* Connection type is determined here */
-static void
-init_block_by_line (gchar * buf)
-{
-	gchar **key_value;
-	gchar *pos;
-	gchar *data;
-	gchar *tmp;
-	GHashTable *conn;
-
-	key_value = g_strsplit (buf, "=", 2);
-	if (g_strv_length (key_value) != 2) {
-		nm_log_warn (LOGD_SETTINGS, "Can't handle this line: %s\n", buf);
-		g_strfreev (key_value);
-		return;
-	}
-	pos = g_strrstr (key_value[0], "_");
-	if (pos == NULL || is_global_setting (key_value[0])) {
-		/* global data */
-		data = g_strdup (key_value[1]);
-		tmp = strip_string (data, '"');
-		strip_string (tmp, '\'');
-		nm_log_info (LOGD_SETTINGS, "global:%s-%s\n", key_value[0], tmp);
-		g_hash_table_insert (global_settings_table, g_strdup (key_value[0]), g_strdup (tmp));
-		g_strfreev (key_value);
-		g_free (data);
-		return;
-	}
-	*pos++ = '\0';
-	if ((conn = get_connection_config (pos)) == NULL) {
-		if (g_ascii_strncasecmp (pos, "eth", 3) == 0
-		    && strlen (pos) == 4)
-			/* wired connection */
-			conn = add_new_connection_config ("wired", pos);
-		else if (g_ascii_strncasecmp (pos, "ppp", 3) == 0
-			 && strlen (pos) == 4)
-			/* pppoe connection */
-			conn = add_new_connection_config ("ppp", pos);
-		else if (ignore_connection_name (pos)) {
-			/* ignored connection */
-			conn = add_new_connection_config ("ignore", pos);
-		} else {
-			int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, pos);
-
-			if (ifindex && nm_platform_link_get_type (NM_PLATFORM_GET, ifindex) != NM_LINK_TYPE_WIFI)
-				/* wired connection */
-				conn = add_new_connection_config ("wired", pos);
-			else
-				/* wireless connection */
-				conn = add_new_connection_config ("wireless", pos);
-		}
-	}
-	data = g_strdup (key_value[1]);
-	tmp = strip_string (data, '"');
-	strip_string (tmp, '\'');
-	if (conn)
-		g_hash_table_insert (conn, strip_string (g_strdup (key_value[0]), ' '),
-				     g_strdup (tmp));
-	g_free (data);
-	g_strfreev (key_value);
-}
-
-static void
-destroy_connection_config (GHashTable * conn)
-{
-	gpointer key, value;
-	GHashTableIter iter;
-
-	g_hash_table_iter_init (&iter, conn);
-	while (g_hash_table_iter_next (&iter, &key, &value)) {
-		g_free (key);
-		g_free (value);
-	}
-
-	g_hash_table_destroy (conn);
-}
-
-static void
-strip_function (GIOChannel * channel, gchar * line)
-{
-
-	int counter = 0;
-	gchar *p, *tmp;
-	gboolean begin = FALSE;
-	GString *function_str = g_string_new (line);
-
-	g_string_append (function_str, "\n");
-	while (1) {
-		p = line;
-		while (*p != '\0') {
-			if (*p == '{') {
-				counter++;
-				begin = TRUE;
-			} else if (*p == '}')
-				counter--;
-			p++;
-		}
-		if (begin && counter == 0) {
-			g_free (line);
-			goto done;
-		}
-		while (1) {
-			g_free (line);
-			if (g_io_channel_read_line
-			    (channel, &line, NULL, NULL,
-			     NULL) == G_IO_STATUS_EOF)
-				goto done;
-			g_string_append (function_str, line);
-			tmp = g_strdup (line);
-			g_strstrip (tmp);
-			if (tmp[0] != '#' && tmp[0] != '\0') {
-				g_free (tmp);
-				break;
-			} else
-				g_free (tmp);
-		}
-	}
-done:
-	functions_list =
-	    g_list_append (functions_list, g_strdup (function_str->str));
-	g_string_free (function_str, TRUE);
-}
-
-static gboolean
-is_function (gchar * line)
-{
-	static gchar *func_names[] =
-	    { "preup", "predown", "postup", "postdown", "failup", "faildown",
-		NULL,
-	};
-	int i;
-
-	for (i = 0; func_names[i]; i++) {
-		if (g_str_has_prefix (line, func_names[i])) {
-			nm_log_info (LOGD_SETTINGS, "Ignoring function: %s", func_names[i]);
-			return TRUE;
-		}
-	}
-	return FALSE;
-}
-
-static void
-append_line (GString *buf, gchar* line)
-{
-	gchar *pos = NULL;
-
-	if ((pos = strchr (line, '#')) != NULL)
-		*pos = '\0';
-	g_strstrip (line);
-
-	if (line[0] != '\0')
-		g_string_append_printf (buf, " %s", line);
-	g_free (line);
-}
-
-gboolean
-ifnet_init (gchar * config_file)
-{
-	GIOChannel *channel = NULL;
-	gchar *line;
-
-	/* Handle multiple lines with brackets */
-	gboolean complete = TRUE;
-
-	gboolean openrc_style = TRUE;
-
-	/* line buffer */
-	GString *buf;
-
-	net_parser_data_changed = FALSE;
-
-	conn_table = g_hash_table_new (nm_str_hash, g_str_equal);
-	global_settings_table = g_hash_table_new (nm_str_hash, g_str_equal);
-	functions_list = NULL;
-
-	if (g_file_test (config_file, G_FILE_TEST_IS_REGULAR))
-		channel = g_io_channel_new_file (config_file, "r", NULL);
-	if (channel == NULL) {
-		nm_log_warn (LOGD_SETTINGS, "Can't open %s", config_file);
-		return FALSE;
-	}
-
-	buf = g_string_new (NULL);
-	while (g_io_channel_read_line
-	       (channel, &line, NULL, NULL, NULL) != G_IO_STATUS_EOF) {
-		g_strstrip (line);
-		/* convert multiple lines to a complete line and
-		 * pass it to init_block_by_line() */
-		if (is_function (line)) {
-			strip_function (channel, line);
-			continue;
-		}
-
-		// New openrc style, bash arrays are not allowed. We only care about '"'
-		if (openrc_style && line[0] != '#' && line[0] != '\0'
-				&& !strchr (line, '(') && !strchr (line, ')')) {
-			gchar *tmp = line;
-
-			while ((tmp = strchr (tmp, '"')) != NULL) {
-				complete = !complete;
-				++tmp;
-			}
-
-			append_line (buf, line);
-			// Add "(separator) for routes. It will be easier for later parsing
-			if (strstr (buf->str, "via"))
-				g_string_append_printf (buf, "\"");
-
-			if (!complete)
-				continue;
-
-			strip_string (buf->str, '"');
-
-			init_block_by_line (buf->str);
-			g_string_free (buf, TRUE);
-			buf = g_string_new (NULL);
-		}
-		// Old bash arrays for baselayout-1, to be deleted
-		else if (line[0] != '#' && line[0] != '\0') {
-			if (!complete) {
-				complete =
-				    g_strrstr (line,
-					       ")") == NULL ? FALSE : TRUE;
-
-				append_line (buf, line);
-				if (!complete) {
-					openrc_style = FALSE;
-					continue;
-				}
-				else {
-					openrc_style = TRUE;
-				}
-			} else {
-				complete =
-				    (g_strrstr (line, "(") != NULL
-				     && g_strrstr (line, ")") != NULL)
-				    || g_strrstr (line, "(") == NULL;
-
-				append_line (buf, line);
-				if (!complete)
-				{
-					openrc_style = FALSE;
-					continue;
-				} else {
-					openrc_style = TRUE;
-				}
-			}
-			init_block_by_line (buf->str);
-			g_string_free (buf, TRUE);
-			buf = g_string_new (NULL);
-		} else
-			/* Blank line or comment line */
-			g_free (line);
-	}
-
-	g_string_free (buf, TRUE);
-	g_io_channel_shutdown (channel, FALSE, NULL);
-	g_io_channel_unref (channel);
-	return TRUE;
-}
-
-const char *
-ifnet_get_data (const char *conn_name, const char *key)
-{
-	GHashTable *conn;
-
-	g_return_val_if_fail (conn_name && key, NULL);
-
-	conn = g_hash_table_lookup (conn_table, conn_name);
-
-	if (conn)
-		return g_hash_table_lookup (conn, key);
-	return NULL;
-}
-
-/* format ip values for comparison */
-static gchar*
-format_ip_for_comparison (const gchar * value)
-{
-	gchar **ipset;
-	guint length, i;
-	GString *formated_string = g_string_new (NULL);
-	gchar *formatted = NULL;
-
-	ipset = g_strsplit (value, "\"", 0);
-	length = g_strv_length (ipset);
-
-	for (i = 0; i < length; i++)
-	{
-		strip_string (ipset[i], ' ');
-		if (ipset[i][0] != '\0')
-			g_string_append_printf (formated_string,
-						"%s ", ipset[i]);
-	}
-	formatted = g_strdup (formated_string->str);
-	formatted[formated_string->len - 1] = '\0';
-
-	g_string_free (formated_string, TRUE);
-	g_strfreev (ipset);
-
-	return formatted;
-}
-
-void
-ifnet_set_data (const char *conn_name, const char *key, const char *value)
-{
-	gpointer old_key = NULL, old_value = NULL;
-	GHashTable *conn = g_hash_table_lookup (conn_table, conn_name);
-	gchar * stripped = NULL;
-
-	if (!conn) {
-		nm_log_warn (LOGD_SETTINGS, "%s does not exist!", conn_name);
-		return;
-	}
-	if (value){
-		stripped = g_strdup (value);
-		strip_string (stripped, '"');
-	}
-	/* Remove existing key value pair */
-	if (g_hash_table_lookup_extended (conn, key, &old_key, &old_value)) {
-
-		/* This ugly hack is due to baselayout compatibility. We have to
-		 * deal with different ip format. So sometimes we have the same ips
-		 * but different strings.
-		 */
-		if (stripped &&
-			(!strcmp (key, "config")
-			|| !strcmp (key, "routes")
-			|| !strcmp (key, "pppd")
-			|| !strcmp (key, "chat")))
-		{
-			gchar *old_ips = format_ip_for_comparison (old_value);
-			gchar *new_ips = format_ip_for_comparison (value);
-			if(!strcmp (old_ips, new_ips))
-			{
-				g_free (stripped);
-				g_free (old_ips);
-				g_free (new_ips);
-				return;
-			}
-			g_free (old_ips);
-			g_free (new_ips);
-		}
-
-		if (stripped && !strcmp (old_value, stripped)) {
-			g_free (stripped);
-			return;
-		}
-		g_hash_table_remove (conn, old_key);
-		g_free (old_key);
-		g_free (old_value);
-	} else if (!value)
-		return;
-	if (stripped)
-		g_hash_table_insert (conn, g_strdup (key), stripped);
-	net_parser_data_changed = TRUE;
-}
-
-// Remember to free return value
-const char *
-ifnet_get_global_data (const gchar * key)
-{
-	return g_hash_table_lookup (global_settings_table, key);
-}
-
-// Return names of legal connections
-GList *
-ifnet_get_connection_names (void)
-{
-	GList *names = g_hash_table_get_keys (conn_table);
-	GList *iter, *result = NULL;
-
-	for (iter = names; iter; iter = iter->next) {
-		if (!ignore_connection_name (iter->data))
-			result = g_list_prepend (result, iter->data);
-	}
-
-	g_list_free (names);
-	return g_list_reverse (result);
-}
-
-/* format IP and route for writing */
-static void
-format_ips (gchar * value, gchar ** out_line, gchar * key, gchar * name)
-{
-	gchar **ipset;
-	guint length, i;
-	GString *formated_string = g_string_new (NULL);
-
-	strip_string (value, '(');
-	strip_string (value, ')');
-	strip_string (value, '"');
-	ipset = g_strsplit (value, "\"", 0);
-	length = g_strv_length (ipset);
-
-	//only one line
-	if (length < 2) {
-		*out_line =
-		    g_strdup_printf ("%s_%s=\"%s\"\n", key, name, value);
-		goto done;
-	}
-	// Multiple lines
-	g_string_append_printf (formated_string, "%s_%s=\"\n", key, name);
-	for (i = 0; i < length; i++)
-	{
-		strip_string (ipset[i], ' ');
-		if (ipset[i][0] != '\0')
-			g_string_append_printf (formated_string,
-						"%s\n", ipset[i]);
-	}
-	g_string_append (formated_string, "\"\n");
-	*out_line = g_strdup (formated_string->str);
-done:
-	g_string_free (formated_string, TRUE);
-	g_strfreev (ipset);
-}
-
-gboolean
-ifnet_flush_to_file (const char *config_file, gchar **out_backup)
-{
-	GIOChannel *channel;
-	GError *error = NULL;
-	gpointer key, value, name, network;
-	GHashTableIter iter, iter_network;
-	GList *list_iter;
-	gchar *out_line = NULL;
-	gsize bytes_written;
-	gboolean result = FALSE;
-	gchar *backup;
-
-	if (!net_parser_data_changed)
-		return TRUE;
-	if (!conn_table || !global_settings_table)
-		return FALSE;
-
-	backup = backup_file (config_file);
-
-	channel = g_io_channel_new_file (config_file, "w", NULL);
-	if (!channel) {
-		nm_log_warn (LOGD_SETTINGS, "Can't open file %s for writing", config_file);
-		g_free (backup);
-		return FALSE;
-	}
-	g_hash_table_iter_init (&iter, global_settings_table);
-	nm_log_info (LOGD_SETTINGS, "Writing to %s", config_file);
-	g_io_channel_write_chars (channel,
-				  "#Generated by NetworkManager\n"
-				  "###### Global Configuration ######\n",
-				  -1, &bytes_written, &error);
-	if (error)
-		goto done;
-
-	/* Writing global data */
-	while (g_hash_table_iter_next (&iter, &key, &value)) {
-		out_line =
-		    g_strdup_printf ("%s=\"%s\"\n", (gchar *) key, (gchar *) value);
-		g_io_channel_write_chars (channel, out_line, -1,
-					  &bytes_written, &error);
-		if (bytes_written == 0 || error)
-			goto done;
-		g_free (out_line);
-	}
-
-	/* Writing connection data */
-	g_io_channel_write_chars (channel,
-				  "\n###### Connection Configuration ######\n",
-				  -1, &bytes_written, &error);
-	if (error)
-		goto done;
-
-	g_hash_table_iter_init (&iter, conn_table);
-	while (g_hash_table_iter_next (&iter, &name, &network)) {
-		g_hash_table_iter_init (&iter_network, (GHashTable *) network);
-		g_io_channel_write_chars (channel,
-					  "#----------------------------------\n",
-					  -1, &bytes_written, &error);
-		if (error)
-			goto done;
-
-		while (g_hash_table_iter_next (&iter_network, &key, &value)) {
-			if (!g_str_has_prefix ((gchar *) key, "name")
-			    && !g_str_has_prefix ((gchar *) key, "type")) {
-				/* These keys contain brackets */
-				if (strcmp
-				    ((gchar *) key,
-				     "config") == 0
-				    || strcmp ((gchar *) key,
-					       "routes") == 0
-				    || strcmp ((gchar *) key,
-					       "pppd") == 0
-				    || strcmp ((gchar *) key, "chat") == 0)
-					format_ips (value, &out_line, (gchar *)
-						    key, (gchar *)
-						    name);
-				else
-					out_line =
-					    g_strdup_printf
-					    ("%s_%s=\"%s\"\n",
-					     (gchar *) key,
-					     (gchar *) name, (gchar *) value);
-				g_io_channel_write_chars (channel, out_line, -1, &bytes_written, &error);
-				if (bytes_written == 0 || error)
-					goto done;
-				g_free (out_line);
-			}
-		}
-	}
-
-	/* Writing reserved functions */
-	if (functions_list) {
-		g_io_channel_write_chars (channel,
-					  "\n###### Reserved Functions ######\n",
-					  -1, &bytes_written, &error);
-		if (error)
-			goto done;
-
-		/* Writing functions */
-		for (list_iter = functions_list; list_iter;
-		     list_iter = g_list_next (list_iter)) {
-			out_line =
-			    g_strdup_printf ("%s\n", (gchar *) list_iter->data);
-			g_io_channel_write_chars (channel, out_line, -1,
-						  &bytes_written, &error);
-			if (bytes_written == 0 || error)
-				goto done;
-			g_free (out_line);
-		}
-	}
-
-	g_io_channel_flush (channel, &error);
-	if (error)
-		goto done;
-	result = TRUE;
-	net_parser_data_changed = FALSE;
-
-done:
-	if (error) {
-		nm_log_warn (LOGD_SETTINGS, "Error writing the configuration file: %s", error->message);
-		g_error_free (error);
-	}
-
-	if (result && out_backup)
-		*out_backup = backup;
-	else
-		g_free (backup);
-
-	g_io_channel_shutdown (channel, FALSE, NULL);
-	g_io_channel_unref (channel);
-	return result;
-}
-
-gboolean
-ifnet_delete_network (const char *conn_name)
-{
-	GHashTable *network = NULL;
-
-	g_return_val_if_fail (conn_table != NULL && conn_name != NULL, FALSE);
-	nm_log_info (LOGD_SETTINGS, "Deleting network for %s", conn_name);
-	network = g_hash_table_lookup (conn_table, conn_name);
-	if (!network)
-		return FALSE;
-	g_hash_table_remove (conn_table, conn_name);
-	destroy_connection_config (network);
-	net_parser_data_changed = TRUE;
-	return TRUE;
-}
-
-void
-ifnet_destroy (void)
-{
-	GHashTableIter iter;
-	gpointer key;
-	gpointer value;
-	GList *list_iter;
-
-	/* Destroy connection setting */
-	if (conn_table) {
-		g_hash_table_iter_init (&iter, conn_table);
-		while (g_hash_table_iter_next (&iter, &key, &value)) {
-			destroy_connection_config ((GHashTable *)
-						   value);
-		}
-		g_hash_table_destroy (conn_table);
-		conn_table = NULL;
-	}
-
-	/* Destroy global data */
-	if (global_settings_table) {
-		g_hash_table_iter_init (&iter, global_settings_table);
-		while (g_hash_table_iter_next (&iter, &key, &value)) {
-			g_free (key);
-			g_free (value);
-		}
-		g_hash_table_destroy (global_settings_table);
-		global_settings_table = NULL;
-	}
-	for (list_iter = functions_list; list_iter;
-	     list_iter = g_list_next (list_iter))
-		g_free (list_iter->data);
-	g_list_free (functions_list);
-}
diff --git a/src/settings/plugins/ifnet/nms-ifnet-net-parser.h b/src/settings/plugins/ifnet/nms-ifnet-net-parser.h
deleted file mode 100644
index 31fc9ead..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-net-parser.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#ifndef _NET_PARSER_H
-#define _NET_PARSER_H
-
-#define CONF_NET_FILE SYSCONFDIR "/conf.d/net"
-
-gboolean ifnet_init (gchar * config_file);
-void ifnet_destroy (void);
-
-/* Reader functions */
-GList *ifnet_get_connection_names (void);
-const char *ifnet_get_data (const char *conn_name, const char *key);
-const char *ifnet_get_global_data (const char *key);
-gboolean ifnet_has_network (const char *conn_name);
-
-/* Writer functions */
-gboolean ifnet_flush_to_file (const char *config_file, gchar **out_backup);
-void ifnet_set_data (const char *conn_name, const char *key, const char *value);
-gboolean ifnet_add_network (const char *name, const char *type);
-gboolean ifnet_delete_network (const char *conn_name);
-
-#endif
diff --git a/src/settings/plugins/ifnet/nms-ifnet-net-utils.c b/src/settings/plugins/ifnet/nms-ifnet-net-utils.c
deleted file mode 100644
index 6531a238..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-net-utils.c
+++ /dev/null
@@ -1,830 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ifnet-net-utils.h"
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <errno.h>
-
-#include "nm-utils.h"
-#include "NetworkManagerUtils.h"
-#include "settings/nm-settings-plugin.h"
-#include "nm-config.h"
-#include "dhcp/nm-dhcp-manager.h"
-
-#include "nms-ifnet-wpa-parser.h"
-#include "nms-ifnet-net-parser.h"
-
-/* emit heading and tailing blank space, tab, character t */
-gchar *
-strip_string (gchar * str, gchar t)
-{
-	gchar *ret = str;
-	gint length = 0;
-	guint i = 0;
-
-	while (ret[i] != '\0'
-	       && (ret[i] == '\t' || ret[i] == ' ' || ret[i] == t)) {
-		length++;
-		i++;
-	}
-	i = 0;
-	while (ret[i + length] != '\0') {
-		ret[i] = ret[i + length];
-		i++;
-	}
-	ret[i] = '\0';
-	length = strlen (ret);
-	while ((length - 1) >= 0
-	       && (ret[length - 1] == ' ' || ret[length - 1] == '\n'
-		   || ret[length - 1] == '\t' || ret[length - 1] == t))
-		length--;
-	ret[length] = '\0';
-	return ret;
-}
-
-gboolean
-is_hex (const char *value)
-{
-	const char *p = value;
-
-	if (!p)
-		return FALSE;
-	while (*p) {
-		if (!g_ascii_isxdigit (*p++))
-			return FALSE;
-	}
-	return TRUE;
-}
-
-gboolean
-is_ascii (const char *value)
-{
-	const char *p = value;
-
-	while (*p) {
-		if (!g_ascii_isprint (*p++))
-			return FALSE;
-	}
-	return TRUE;
-
-}
-
-gboolean
-is_true (const char *str)
-{
-	if (!g_ascii_strcasecmp (str, "yes")
-	    || !g_ascii_strcasecmp (str, "true"))
-		return TRUE;
-	return FALSE;
-}
-
-static char *
-find_default_gateway_str (char *str)
-{
-	char *tmp;
-
-	if ((tmp = strstr (str, "default via ")) != NULL) {
-		return tmp + strlen ("default via ");
-	} else if ((tmp = strstr (str, "default gw ")) != NULL) {
-		return tmp + strlen ("default gw ");
-	}
-	return NULL;
-}
-
-static char *
-find_gateway_str (char *str)
-{
-	char *tmp;
-
-	if ((tmp = strstr (str, "via ")) != NULL) {
-		return tmp + strlen ("via ");
-	} else if ((tmp = strstr (str, "gw ")) != NULL) {
-		return tmp + strlen ("gw ");
-	}
-	return NULL;
-}
-
-gboolean
-reload_parsers (void)
-{
-	ifnet_destroy ();
-	wpa_parser_destroy ();
-	if (!ifnet_init (CONF_NET_FILE))
-		return FALSE;
-	wpa_parser_init (WPA_SUPPLICANT_CONF);
-	return TRUE;
-}
-
-gboolean
-is_static_ip4 (const char *conn_name)
-{
-	const char *data = ifnet_get_data (conn_name, "config");
-	const char *dhcp6;
-
-	if (!data)
-		return FALSE;
-	if (!strcmp (data, "shared"))
-		return FALSE;
-	if (!strcmp (data, "autoip"))
-		return FALSE;
-	dhcp6 = strstr (data, "dhcp6");
-	if (dhcp6) {
-		gchar *dhcp4;
-
-		if (strstr (data, "dhcp "))
-			return FALSE;
-		dhcp4 = strstr (data, "dhcp");
-		if (!dhcp4)
-			return TRUE;
-		if (dhcp4[4] == '\0')
-			return FALSE;
-		return TRUE;
-	}
-	return strstr (data, "dhcp") == NULL ? TRUE : FALSE;
-}
-
-gboolean
-is_static_ip6 (const char *conn_name)
-{
-	const char *data = ifnet_get_data (conn_name, "config");
-
-	if (!data)
-		return TRUE;
-	return strstr (data, "dhcp6") == NULL ? TRUE : FALSE;
-}
-
-gboolean
-is_ip4_address (const char *in_address)
-{
-	const char *pattern =
-	    "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.((\\{\\d{1,3}\\.\\.\\d{1,3}\\})|\\d{1,3})$";
-	gchar *address = g_strdup (in_address);
-	gboolean result = FALSE;
-	gchar *tmp;
-	GRegex *regex = g_regex_new (pattern, 0, 0, NULL);
-	GMatchInfo *match_info = NULL;
-
-	if (!address)
-		goto done;
-	g_strstrip (address);
-	if ((tmp = strstr (address, "/")) != NULL)
-		*tmp = '\0';
-	if ((tmp = strstr (address, " ")) != NULL)
-		*tmp = '\0';
-	g_regex_match (regex, address, 0, &match_info);
-	result = g_match_info_matches (match_info);
-done:
-	if (match_info)
-		g_match_info_free (match_info);
-	g_regex_unref (regex);
-	g_free (address);
-	return result;
-}
-
-gboolean
-is_ip6_address (const char *in_address)
-{
-	struct in6_addr tmp_ip6_addr;
-	gchar *tmp, *address;
-	gboolean result = FALSE;
-
-	if (!in_address)
-		return FALSE;
-	address = g_strdup (in_address);
-	g_strstrip (address);
-	if ((tmp = strchr (address, '/')) != NULL)
-		*tmp = '\0';
-	if (inet_pton (AF_INET6, address, &tmp_ip6_addr))
-		result = TRUE;
-	g_free (address);
-	return result;
-
-}
-
-// 'c' is only used for openrc style
-static gchar **
-split_addresses_by_char (const gchar *addresses, const gchar *c)
-{
-	gchar **ipset;
-
-	if (addresses == NULL)
-		return NULL;
-
-	if (strchr (addresses, '(') != NULL) { // old baselayout style
-		gchar *tmp = g_strdup (addresses);
-		strip_string (tmp, '(');
-		strip_string (tmp, ')');
-		strip_string (tmp, '"');
-		strip_string (tmp, '\'');
-		ipset = g_strsplit (tmp, "\" \"", 0);
-		g_free(tmp);
-	} else { // openrc style
-		if (strstr (addresses, "netmask"))
-			// There is only one ip address if "netmask" is specified.
-			// '\n' is not used in config so there will be only one split.
-			ipset = g_strsplit (addresses, "\n", 0);
-		else
-			ipset = g_strsplit (addresses, c, 0);
-	}
-
-	return ipset;
-}
-
-static gchar **
-split_addresses (const gchar* addresses)
-{
-	// " " is only used by openrc style
-	return split_addresses_by_char (addresses, " ");
-}
-
-static gchar **
-split_routes (const gchar* routes)
-{
-	// "\"" is only used by openrc style
-	return split_addresses_by_char (routes, "\"");
-}
-
-gboolean
-has_ip6_address (const char *conn_name)
-{
-	gchar **ipset;
-	guint length;
-	guint i;
-
-	g_return_val_if_fail (conn_name != NULL, FALSE);
-	ipset = split_addresses (ifnet_get_data (conn_name, "config"));
-	length = ipset ? g_strv_length (ipset) : 0;
-	for (i = 0; i < length; i++) {
-		if (!is_ip6_address (ipset[i]))
-			continue;
-		else {
-			g_strfreev (ipset);
-			return TRUE;
-		}
-
-	}
-	g_strfreev (ipset);
-	return FALSE;
-}
-
-gboolean
-has_default_route (const char *conn_name, gboolean (*check_fn) (const char *))
-{
-	char *routes = NULL, *end, *tmp;
-	gboolean success = FALSE;
-
-	g_return_val_if_fail (conn_name != NULL, FALSE);
-
-	routes = g_strdup (ifnet_get_data (conn_name, "routes"));
-	if (!routes)
-		return FALSE;
-	tmp = find_default_gateway_str (routes);
-	if (tmp) {
-		g_strstrip (tmp);
-		if ((end = strstr (tmp, "\"")) != NULL)
-			*end = '\0';
-		if (check_fn (tmp))
-			success = TRUE;
-	}
-
-	g_free (routes);
-	return success;
-}
-
-static ip_block *
-create_ip4_block (gchar * ip)
-{
-	ip_block *iblock = g_slice_new0 (ip_block);
-	guint32 tmp_ip4_addr;
-	int i;
-	guint length;
-	gchar **ip_mask;
-
-	/* prefix format */
-	if (strstr (ip, "/")) {
-		gchar *prefix;
-
-		ip_mask = g_strsplit (ip, "/", 0);
-		length = g_strv_length (ip_mask);
-		if (!nm_utils_ipaddr_valid (AF_INET, ip_mask[0]))
-			goto error;
-		iblock->ip = g_strdup (ip_mask[0]);
-		prefix = ip_mask[1];
-		i = 0;
-		while (i < length && g_ascii_isdigit (prefix[i]))
-			i++;
-		prefix[i] = '\0';
-		iblock->prefix = (guint32) atoi (ip_mask[1]);
-	} else if (strstr (ip, "netmask")) {
-		ip_mask = g_strsplit (ip, " ", 0);
-		length = g_strv_length (ip_mask);
-		if (!nm_utils_ipaddr_valid (AF_INET, ip_mask[0]))
-			goto error;
-		iblock->ip = g_strdup (ip_mask[0]);
-		i = 0;
-		while (i < length && !strstr (ip_mask[++i], "netmask")) ;
-		while (i < length && ip_mask[++i][0] == '\0') ;
-		if (i >= length)
-			goto error;
-		if (!inet_pton (AF_INET, ip_mask[i], &tmp_ip4_addr))
-			goto error;
-		iblock->prefix = nm_utils_ip4_netmask_to_prefix (tmp_ip4_addr);
-	} else {
-		g_slice_free (ip_block, iblock);
-		if (!is_ip6_address (ip) && !strstr (ip, "dhcp"))
-			nm_log_warn (LOGD_SETTINGS, "Can't handle ipv4 address: %s, missing netmask or prefix", ip);
-		return NULL;
-	}
-	if (iblock->prefix == 0 || iblock->prefix > 32) {
-		nm_log_warn (LOGD_SETTINGS, "Can't handle ipv4 address: %s, invalid prefix", ip);
-		goto error;
-	}
-	g_strfreev (ip_mask);
-	return iblock;
-error:
-	if (!is_ip6_address (ip))
-		nm_log_warn (LOGD_SETTINGS, "Can't handle IPv4 address: %s", ip);
-	g_strfreev (ip_mask);
-	g_free (iblock->ip);
-	g_slice_free (ip_block, iblock);
-	return NULL;
-}
-
-static ip_block *
-create_ip_block (gchar * ip)
-{
-	ip_block *iblock = g_slice_new0 (ip_block);
-	gchar *dup_ip = g_strdup (ip);
-	gchar *prefix = NULL;
-
-	if ((prefix = strstr (dup_ip, "/")) != NULL) {
-		*prefix = '\0';
-		prefix++;
-	}
-	if (!nm_utils_ipaddr_valid (AF_INET6, dup_ip))
-		goto error;
-	iblock->ip = dup_ip;
-	if (prefix) {
-		errno = 0;
-		iblock->prefix = strtol (prefix, NULL, 10);
-		if (errno || iblock->prefix <= 0 || iblock->prefix > 128) {
-			goto error;
-		}
-	} else
-		iblock->prefix = 64;
-	return iblock;
-error:
-	if (!is_ip4_address (ip))
-		nm_log_warn (LOGD_SETTINGS, "Can't handle IPv6 address: %s", ip);
-	g_slice_free (ip_block, iblock);
-	g_free (dup_ip);
-	return NULL;
-}
-
-static char *
-get_ip4_gateway (gchar * gateway)
-{
-	gchar *tmp, *split;
-
-	if (!gateway)
-		return NULL;
-	tmp = find_gateway_str (gateway);
-	if (!tmp) {
-		nm_log_warn (LOGD_SETTINGS, "Couldn't obtain gateway in \"%s\"", gateway);
-		return NULL;
-	}
-	tmp = g_strdup (tmp);
-	strip_string (tmp, ' ');
-	strip_string (tmp, '"');
-
-	// Only one gateway is selected
-	if ((split = strstr (tmp, "\"")) != NULL)
-		*split = '\0';
-
-	if (!nm_utils_ipaddr_valid (AF_INET, tmp))
-		goto error;
-	return tmp;
-error:
-	if (!is_ip6_address (tmp))
-		nm_log_warn (LOGD_SETTINGS, "Can't handle IPv4 gateway: %s", tmp);
-	g_free (tmp);
-	return NULL;
-}
-
-static char *
-get_ip6_next_hop (gchar * next_hop)
-{
-	gchar *tmp;
-
-	if (!next_hop)
-		return NULL;
-	tmp = find_gateway_str (next_hop);
-	if (!tmp) {
-		nm_log_warn (LOGD_SETTINGS, "Couldn't obtain next_hop in \"%s\"", next_hop);
-		return NULL;
-	}
-	tmp = g_strdup (tmp);
-	strip_string (tmp, ' ');
-	strip_string (tmp, '"');
-	g_strstrip (tmp);
-	if (!nm_utils_ipaddr_valid (AF_INET6, tmp))
-		goto error;
-	return tmp;
-error:
-	if (!is_ip4_address (tmp))
-		nm_log_warn (LOGD_SETTINGS, "Can't handle IPv6 next_hop: %s", tmp);
-	g_free (tmp);
-
-	return NULL;
-}
-
-ip_block *
-convert_ip4_config_block (const char *conn_name)
-{
-	gchar **ipset;
-	guint length;
-	guint i;
-	gchar *ip;
-	char *def_gateway = NULL;
-	const char *routes;
-	ip_block *start = NULL, *current = NULL, *iblock = NULL;
-
-	g_return_val_if_fail (conn_name != NULL, NULL);
-
-	ipset = split_addresses (ifnet_get_data (conn_name, "config"));
-	length = ipset ? g_strv_length (ipset) : 0;
-
-	routes = ifnet_get_data (conn_name, "routes");
-	if (routes)
-		def_gateway = get_ip4_gateway (strstr (routes, "default"));
-
-	for (i = 0; i < length; i++) {
-		ip = ipset[i];
-		ip = strip_string (ip, '"');
-		iblock = create_ip4_block (ip);
-		if (iblock == NULL)
-			continue;
-		if (!iblock->next_hop && def_gateway != NULL)
-			iblock->next_hop = g_strdup (def_gateway);
-		if (start == NULL)
-			start = current = iblock;
-		else {
-			current->next = iblock;
-			current = iblock;
-		}
-	}
-	g_strfreev (ipset);
-	g_free (def_gateway);
-	return start;
-}
-
-ip_block *
-convert_ip6_config_block (const char *conn_name)
-{
-	gchar **ipset;
-	guint length;
-	guint i;
-	gchar *ip;
-	ip_block *start = NULL, *current = NULL, *iblock = NULL;
-
-	g_return_val_if_fail (conn_name != NULL, NULL);
-	ipset = split_addresses (ifnet_get_data (conn_name, "config"));
-	length = ipset ? g_strv_length (ipset) : 0;
-	for (i = 0; i < length; i++) {
-		ip = ipset[i];
-		ip = strip_string (ip, '"');
-		iblock = create_ip_block (ip);
-		if (iblock == NULL)
-			continue;
-		if (start == NULL)
-			start = current = iblock;
-		else {
-			current->next = iblock;
-			current = iblock;
-		}
-	}
-	g_strfreev (ipset);
-	return start;
-}
-
-ip_block *
-convert_ip4_routes_block (const char *conn_name)
-{
-	gchar **ipset;
-	guint length;
-	guint i;
-	gchar *ip;
-	ip_block *start = NULL, *current = NULL, *iblock = NULL;
-
-	g_return_val_if_fail (conn_name != NULL, NULL);
-
-	ipset = split_routes (ifnet_get_data (conn_name, "routes"));
-	length = ipset ? g_strv_length (ipset) : 0;
-	for (i = 0; i < length; i++) {
-		ip = ipset[i];
-		if (find_default_gateway_str (ip) || strstr (ip, "::")
-		    || !find_gateway_str (ip))
-			continue;
-		ip = strip_string (ip, '"');
-		iblock = create_ip4_block (ip);
-		if (iblock == NULL)
-			continue;
-		iblock->next_hop = get_ip4_gateway (ip);
-		if (start == NULL)
-			start = current = iblock;
-		else {
-			current->next = iblock;
-			current = iblock;
-		}
-	}
-	g_strfreev (ipset);
-	return start;
-}
-
-ip_block *
-convert_ip6_routes_block (const char *conn_name)
-{
-	gchar **ipset;
-	guint length;
-	guint i;
-	gchar *ip, *tmp_addr;
-	ip_block *start = NULL, *current = NULL, *iblock = NULL;
-
-	g_return_val_if_fail (conn_name != NULL, NULL);
-	ipset = split_routes (ifnet_get_data (conn_name, "routes"));
-	length = ipset ? g_strv_length (ipset) : 0;
-	for (i = 0; i < length; i++) {
-		ip = ipset[i];
-		ip = strip_string (ip, '"');
-		if (ip[0] == '\0')
-			continue;
-		if ((tmp_addr = find_default_gateway_str (ip)) != NULL) {
-			if (!is_ip6_address (tmp_addr))
-				continue;
-			else {
-				iblock = g_slice_new0 (ip_block);
-				iblock->ip = g_strdup ("::");
-				iblock->prefix = 128;
-			}
-		} else
-			iblock = create_ip_block (ip);
-		if (iblock == NULL)
-			continue;
-		iblock->next_hop = get_ip6_next_hop (ip);
-		if (iblock->next_hop == NULL) {
-			destroy_ip_block (iblock);
-			continue;
-		}
-		if (start == NULL)
-			start = current = iblock;
-		else {
-			current->next = iblock;
-			current = iblock;
-		}
-	}
-	g_strfreev (ipset);
-	return start;
-}
-
-void
-destroy_ip_block (ip_block * iblock)
-{
-	g_free (iblock->ip);
-	g_free (iblock->next_hop);
-	g_slice_free (ip_block, iblock);
-}
-
-void
-set_ip4_dns_servers (NMSettingIPConfig *s_ip4, const char *conn_name)
-{
-	const char *dns_servers;
-	gchar **server_list, *stripped;
-	guint length, i;
-	guint32 tmp_ip4_addr;
-
-	dns_servers = ifnet_get_data (conn_name, "dns_servers");
-	if (!dns_servers)
-		return;
-	stripped = g_strdup (dns_servers);
-	strip_string (stripped, '"');
-	server_list = g_strsplit (stripped, " ", 0);
-	g_free (stripped);
-
-	length = g_strv_length (server_list);
-	if (length)
-		g_object_set (s_ip4, NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS,
-			      TRUE, NULL);
-	for (i = 0; i < length; i++) {
-		g_strstrip (server_list[i]);
-		if (server_list[i][0] == '\0')
-			continue;
-		if (!inet_pton (AF_INET, server_list[i], &tmp_ip4_addr)) {
-			if (!is_ip6_address (server_list[i]))
-				nm_log_warn (LOGD_SETTINGS, "ignored dns: %s\n", server_list[i]);
-			continue;
-		}
-		if (!nm_setting_ip_config_add_dns (s_ip4, server_list[i]))
-			nm_log_warn (LOGD_SETTINGS, "warning: duplicate DNS server %s", server_list[i]);
-	}
-	g_strfreev (server_list);
-}
-
-void
-set_ip6_dns_servers (NMSettingIPConfig *s_ip6, const char *conn_name)
-{
-	const char *dns_servers;
-	gchar **server_list, *stripped;
-	guint length, i;
-	struct in6_addr tmp_ip6_addr;
-
-	dns_servers = ifnet_get_data (conn_name, "dns_servers");
-	if (!dns_servers)
-		return;
-
-	stripped = g_strdup (dns_servers);
-	strip_string (stripped, '"');
-	server_list = g_strsplit (stripped, " ", 0);
-	g_free (stripped);
-
-	length = g_strv_length (server_list);
-	if (length)
-		g_object_set (s_ip6, NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS,
-			      TRUE, NULL);
-	for (i = 0; i < length; i++) {
-		g_strstrip (server_list[i]);
-		if (server_list[i][0] == '\0')
-			continue;
-		if (!inet_pton (AF_INET6, server_list[i], &tmp_ip6_addr)) {
-			if (is_ip6_address (server_list[i]))
-				nm_log_warn (LOGD_SETTINGS, "ignored dns: %s\n", server_list[i]);
-			continue;
-		}
-		if (!nm_setting_ip_config_add_dns (s_ip6, server_list[i]))
-			nm_log_warn (LOGD_SETTINGS, "warning: duplicate DNS server %s", server_list[i]);
-	}
-	g_strfreev (server_list);
-}
-
-gboolean
-is_managed (const char *conn_name)
-{
-	gchar *config;
-
-	g_return_val_if_fail (conn_name != NULL, FALSE);
-	config = (gchar *) ifnet_get_data (conn_name, "managed");
-	if (!config)
-		return TRUE;
-	if (strcmp (config, "false") == 0)
-		return FALSE;
-	return TRUE;
-}
-
-static char *
-_has_prefix_impl (char *str, const char *prefix, gsize prefix_len)
-{
-	if (!g_str_has_prefix (str, prefix))
-		return NULL;
-	str += prefix_len;
-	if (!g_ascii_isspace (str[0]))
-		return NULL;
-	do {
-		str++;
-	} while (g_ascii_isspace (str[0]));
-	return str;
-}
-#define _has_prefix(STR, PREFIX) _has_prefix_impl (STR, PREFIX, NM_STRLEN (PREFIX))
-
-void
-get_dhcp_hostname_and_client_id (char **hostname, char **client_id)
-{
-	const char *dhcp_client;
-	const gchar *dhcpcd_conf = SYSCONFDIR "/dhcpcd.conf";
-	const gchar *dhclient_conf = SYSCONFDIR "/dhcp/dhclient.conf";
-	gchar *line = NULL, *tmp = NULL, *contents = NULL, *tmp1;
-	gchar **all_lines;
-	guint line_num, i;
-	gboolean use_dhclient = FALSE;
-
-	*hostname = NULL;
-	*client_id = NULL;
-	dhcp_client = nm_dhcp_manager_get_config (nm_dhcp_manager_get ());
-	if (dhcp_client) {
-		if (!strcmp (dhcp_client, "dhclient")) {
-			g_file_get_contents (dhclient_conf, &contents, NULL,
-			                     NULL);
-			use_dhclient = TRUE;
-		} else if (!strcmp (dhcp_client, "dhcpcd")) {
-			g_file_get_contents (dhcpcd_conf, &contents, NULL,
-			                     NULL);
-		}
-	} else {
-		if (g_file_test (dhclient_conf, G_FILE_TEST_IS_REGULAR)) {
-			g_file_get_contents (dhclient_conf, &contents, NULL,
-			                     NULL);
-			use_dhclient = TRUE;
-		} else if (g_file_test (dhcpcd_conf, G_FILE_TEST_IS_REGULAR)) {
-			g_file_get_contents (dhcpcd_conf, &contents, NULL,
-			                     NULL);
-		}
-	}
-	if (!contents)
-		return;
-	all_lines = g_strsplit (contents, "\n", 0);
-	line_num = g_strv_length (all_lines);
-	for (i = 0; i < line_num; i++) {
-		line = all_lines[i];
-		g_strstrip (line);
-		if (line[0] == '#' || line[0] == '\0')
-			continue;
-		if (!use_dhclient) {
-			// dhcpcd.conf
-			if ((tmp = _has_prefix (line, "hostname"))) {
-				if (tmp[0] != '\0') {
-					g_free (*hostname);
-					*hostname = g_strdup (tmp);
-				} else
-					nm_log_info (LOGD_SETTINGS, "dhcpcd hostname not defined, ignoring");
-			} else if ((tmp = _has_prefix (line, "clientid"))) {
-				if (tmp[0] != '\0') {
-					g_free (*client_id);
-					*client_id = g_strdup (tmp);
-				} else
-					nm_log_info (LOGD_SETTINGS, "dhcpcd clientid not defined, ignoring");
-			}
-		} else {
-			// dhclient.conf
-			if ((tmp1 = _has_prefix (line, "send"))) {
-				if ((tmp = _has_prefix (tmp1, "host-name"))) {
-					strip_string (tmp, ';');
-					strip_string (tmp, '"');
-					if (tmp[0] != '\0') {
-						g_free (*hostname);
-						*hostname = g_strdup (tmp);
-					} else
-						nm_log_info (LOGD_SETTINGS, "dhclient hostname not defined, ignoring");
-				} else if ((tmp = _has_prefix (tmp1, "dhcp-client-identifier"))) {
-					strip_string (tmp, ';');
-					if (tmp[0] != '\0') {
-						g_free (*client_id);
-						*client_id = g_strdup (tmp);
-					} else
-						nm_log_info (LOGD_SETTINGS, "dhclient clientid not defined, ignoring");
-				}
-			}
-		}
-	}
-	g_strfreev (all_lines);
-	g_free (contents);
-}
-
-gchar *backup_file (const gchar* target)
-{
-	GFile *source, *backup;
-	gchar* backup_path;
-	GError *error = NULL;
-
-	source = g_file_new_for_path (target);
-
-	if (!g_file_query_exists (source, NULL)) {
-		g_object_unref (source);
-		return NULL;
-	}
-
-	backup_path = g_strdup_printf ("%s.bak", target);
-	backup = g_file_new_for_path (backup_path);
-
-	if (!g_file_copy (source, backup, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error)) {
-		nm_log_warn (LOGD_SETTINGS, "Backup failed: %s", error->message);
-		g_free (backup_path);
-		backup_path = NULL;
-		g_error_free (error);
-	}
-
-	g_object_unref (source);
-	g_object_unref (backup);
-
-	return backup_path;
-}
diff --git a/src/settings/plugins/ifnet/nms-ifnet-net-utils.h b/src/settings/plugins/ifnet/nms-ifnet-net-utils.h
deleted file mode 100644
index cc273aa7..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-net-utils.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#ifndef _IFNET_UTILS_H
-#define _IFNET_UTILS_H
-
-#define IFNET_PLUGIN_NAME "SettingsPlugin-Ifnet"
-
-#include <arpa/inet.h>
-
-#include "nm-setting-ip6-config.h"
-#include "nm-setting-ip4-config.h"
-
-#include "nms-ifnet-net-parser.h"
-
-#define has_default_ip4_route(conn_name) has_default_route((conn_name), &is_ip4_address)
-#define has_default_ip6_route(conn_name) has_default_route((conn_name), &is_ip6_address)
-
-typedef struct _ip_block {
-	char *ip;
-	guint32 prefix;
-	char *next_hop;
-	struct _ip_block *next;
-} ip_block;
-
-gboolean is_static_ip4 (const char *conn_name);
-gboolean is_static_ip6 (const char *conn_name);
-gboolean is_ip4_address (const char *in_address);
-gboolean is_ip6_address (const char *in_address);
-gboolean has_ip6_address (const char *conn_name);
-gboolean has_default_route (const char *conn_name, gboolean (*check_fn) (const char *));
-gboolean reload_parsers (void);
-
-ip_block *convert_ip4_config_block (const char *conn_name);
-ip_block *convert_ip6_config_block (const char *conn_name);
-ip_block *convert_ip4_routes_block (const char *conn_name);
-ip_block *convert_ip6_routes_block (const char *conn_name);
-void destroy_ip_block (ip_block * iblock);
-
-void set_ip4_dns_servers (NMSettingIPConfig * s_ip4, const char *conn_name);
-void set_ip6_dns_servers (NMSettingIPConfig * s_ip6, const char *conn_name);
-
-gchar *strip_string (gchar *str, gchar t);
-gboolean is_managed (const char *conn_name);
-
-gboolean is_hex (const char *value);
-gboolean is_ascii (const char *value);
-gboolean is_true (const char *str);
-
-void get_dhcp_hostname_and_client_id (char **hostname, char **client_id);
-
-gchar *backup_file (const gchar* target);
-#endif
diff --git a/src/settings/plugins/ifnet/nms-ifnet-plugin.c b/src/settings/plugins/ifnet/nms-ifnet-plugin.c
deleted file mode 100644
index 8332358f..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-plugin.c
+++ /dev/null
@@ -1,524 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service (ifnet)
- *
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ifnet-plugin.h"
-
-#include <string.h>
-#include <gmodule.h>
-
-#include "nm-utils.h"
-#include "nm-setting-connection.h"
-#include "nm-dbus-interface.h"
-#include "settings/nm-settings-plugin.h"
-#include "nm-config.h"
-#include "NetworkManagerUtils.h"
-
-#include "nms-ifnet-connection.h"
-#include "nms-ifnet-net-utils.h"
-#include "nms-ifnet-net-parser.h"
-#include "nms-ifnet-wpa-parser.h"
-#include "nms-ifnet-connection-parser.h"
-
-#define IFNET_PLUGIN_NAME_PRINT "ifnet"
-#define IFNET_PLUGIN_INFO "(C) 1999-2010 Gentoo Foundation, Inc. To report bugs please use bugs.gentoo.org with [networkmanager] or [qiaomuf] prefix."
-#define IFNET_MANAGE_WELL_KNOWN_DEFAULT TRUE
-
-/*****************************************************************************/
-
-typedef void (*FileChangedFn) (gpointer user_data);
-
-typedef struct {
-	FileChangedFn callback;
-	gpointer user_data;
-} FileMonitorInfo;
-
-/*****************************************************************************/
-
-typedef struct {
-	GHashTable *connections;  /* uuid::connection */
-	gboolean unmanaged_well_known;
-
-	GFileMonitor *net_monitor;
-	GFileMonitor *wpa_monitor;
-} SettingsPluginIfnetPrivate;
-
-struct _SettingsPluginIfnet {
-	GObject parent;
-	SettingsPluginIfnetPrivate _priv;
-};
-
-struct _SettingsPluginIfnetClass {
-	GObjectClass parent;
-};
-
-static void settings_plugin_interface_init (NMSettingsPluginInterface *plugin_iface);
-
-G_DEFINE_TYPE_EXTENDED (SettingsPluginIfnet, settings_plugin_ifnet, G_TYPE_OBJECT, 0,
-                        G_IMPLEMENT_INTERFACE (NM_TYPE_SETTINGS_PLUGIN,
-                                               settings_plugin_interface_init))
-
-#define SETTINGS_PLUGIN_IFNET_GET_PRIVATE(self) _NM_GET_PRIVATE (self, SettingsPluginIfnet, SETTINGS_IS_PLUGIN_IFNET)
-
-/*****************************************************************************/
-
-static SettingsPluginIfnet *settings_plugin_ifnet_get (void);
-
-NM_DEFINE_SINGLETON_GETTER (SettingsPluginIfnet, settings_plugin_ifnet_get, SETTINGS_TYPE_PLUGIN_IFNET);
-
-/*****************************************************************************/
-
-static void reload_connections (NMSettingsPlugin *config);
-
-/*****************************************************************************/
-
-static gboolean
-is_managed_plugin (void)
-{
-	return nm_config_data_get_value_boolean (NM_CONFIG_GET_DATA_ORIG,
-	                                         NM_CONFIG_KEYFILE_GROUP_IFNET, NM_CONFIG_KEYFILE_KEY_IFNET_MANAGED,
-	                                         IFNET_MANAGE_WELL_KNOWN_DEFAULT);
-}
-
-static void
-file_changed (GFileMonitor * monitor,
-              GFile * file,
-              GFile * other_file,
-              GFileMonitorEvent event_type, gpointer user_data)
-{
-	FileMonitorInfo *info;
-
-	switch (event_type) {
-	case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
-		info = (FileMonitorInfo *) user_data;
-		info->callback (info->user_data);
-		break;
-	default:
-		break;
-	}
-}
-
-static GFileMonitor *
-monitor_file_changes (const char *filename,
-                      FileChangedFn callback, gpointer user_data)
-{
-	GFile *file;
-	GFileMonitor *monitor;
-	FileMonitorInfo *info;
-	GError **error = NULL;
-
-	if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR))
-		return NULL;
-	file = g_file_new_for_path (filename);
-	monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, error);
-	g_object_unref (file);
-
-	if (monitor) {
-		info = g_new0 (FileMonitorInfo, 1);
-		info->callback = callback;
-		info->user_data = user_data;
-		g_object_weak_ref (G_OBJECT (monitor), (GWeakNotify) g_free,
-		                   info);
-		g_signal_connect (monitor, "changed", G_CALLBACK (file_changed),
-		                  info);
-	} else {
-		nm_log_warn (LOGD_SETTINGS, "Monitoring %s failed, error: %s", filename,
-		             error == NULL ? "nothing" : (*error)->message);
-	}
-
-	return monitor;
-}
-
-static void
-setup_monitors (NMIfnetConnection *connection, gpointer user_data)
-{
-	SettingsPluginIfnet *self = SETTINGS_PLUGIN_IFNET (user_data);
-	SettingsPluginIfnetPrivate *priv = SETTINGS_PLUGIN_IFNET_GET_PRIVATE (self);
-
-	if (!nm_config_get_monitor_connection_files (nm_config_get ()))
-		return;
-
-	if (priv->net_monitor || priv->wpa_monitor)
-		return;
-
-	priv->net_monitor = monitor_file_changes (CONF_NET_FILE,
-	                                          (FileChangedFn) reload_connections,
-	                                          user_data);
-	priv->wpa_monitor = monitor_file_changes (WPA_SUPPLICANT_CONF,
-	                                          (FileChangedFn) reload_connections,
-	                                          user_data);
-}
-
-static void
-cancel_monitors (NMIfnetConnection *connection, gpointer user_data)
-{
-	SettingsPluginIfnet *self = SETTINGS_PLUGIN_IFNET (user_data);
-	SettingsPluginIfnetPrivate *priv = SETTINGS_PLUGIN_IFNET_GET_PRIVATE (self);
-
-	if (priv->net_monitor) {
-		g_file_monitor_cancel (priv->net_monitor);
-		g_clear_object (&priv->net_monitor);
-	}
-	if (priv->wpa_monitor) {
-		g_file_monitor_cancel (priv->wpa_monitor);
-		g_clear_object (&priv->wpa_monitor);
-	}
-}
-
-static void
-connection_removed_cb (NMSettingsConnection *obj, gpointer user_data)
-{
-	g_hash_table_remove (SETTINGS_PLUGIN_IFNET_GET_PRIVATE ((SettingsPluginIfnet *) user_data)->connections,
-	                     nm_connection_get_uuid (NM_CONNECTION (obj)));
-}
-
-static void
-track_new_connection (SettingsPluginIfnet *self, NMIfnetConnection *connection)
-{
-	g_hash_table_insert (SETTINGS_PLUGIN_IFNET_GET_PRIVATE (self)->connections,
-	                     g_strdup (nm_connection_get_uuid (NM_CONNECTION (connection))),
-	                     g_object_ref (connection));
-	g_signal_connect (connection, NM_SETTINGS_CONNECTION_REMOVED,
-	                  G_CALLBACK (connection_removed_cb),
-	                  self);
-}
-
-static void
-reload_connections (NMSettingsPlugin *config)
-{
-	SettingsPluginIfnet *self = SETTINGS_PLUGIN_IFNET (config);
-	SettingsPluginIfnetPrivate *priv = SETTINGS_PLUGIN_IFNET_GET_PRIVATE (self);
-	GList *conn_names = NULL, *n_iter = NULL;
-	gboolean auto_refresh;
-	GError *error = NULL;
-
-	/* save names for removing unused connections */
-	GHashTable *new_connections = NULL;
-	GHashTableIter iter;
-	const char *uuid;
-	NMSettingsConnection *candidate;
-
-	if (priv->unmanaged_well_known)
-		return;
-
-	if (!reload_parsers ())
-		return;
-
-	nm_log_info (LOGD_SETTINGS, "Loading connections");
-
-	auto_refresh = nm_config_data_get_value_boolean (NM_CONFIG_GET_DATA_ORIG,
-	                                                 NM_CONFIG_KEYFILE_GROUP_IFNET, NM_CONFIG_KEYFILE_KEY_IFNET_AUTO_REFRESH,
-	                                                 FALSE);
-
-	new_connections = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_object_unref);
-
-	/* Reread on-disk data and refresh in-memory connections from it */
-	conn_names = ifnet_get_connection_names ();
-	for (n_iter = conn_names; n_iter; n_iter = g_list_next (n_iter)) {
-		NMIfnetConnection *new;
-		NMIfnetConnection *old;
-		const char *conn_name = n_iter->data;
-
-		/* read the new connection */
-		new = nm_ifnet_connection_new (NULL, conn_name);
-		if (!new)
-			continue;
-
-		g_signal_connect (G_OBJECT (new), "ifnet_setup_monitors",
-		                  G_CALLBACK (setup_monitors), config);
-		g_signal_connect (G_OBJECT (new), "ifnet_cancel_monitors",
-		                  G_CALLBACK (cancel_monitors), config);
-
-		old = g_hash_table_lookup (priv->connections,
-		                           nm_connection_get_uuid (NM_CONNECTION (new)));
-		if (old && new) {
-			if (auto_refresh) {
-				/* If connection has changed, remove the old one and add the
-				 * new one to force a disconnect/reconnect with new settings
-				 */
-				if (!nm_connection_compare (NM_CONNECTION (old),
-				                            NM_CONNECTION (new),
-				                            NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS |
-				                              NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) {
-					nm_log_info (LOGD_SETTINGS, "Auto refreshing %s", conn_name);
-
-					nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (old));
-					track_new_connection (self, new);
-					if (is_managed_plugin () && is_managed (conn_name))
-						g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_CONNECTION_ADDED, new);
-				}
-			} else {
-				/* Update existing connection with new settings */
-				if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (old),
-				                                    NM_CONNECTION (new),
-				                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
-				                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-				                                    "ifnet-update",
-				                                    &error)) {
-					/* Shouldn't ever get here as 'new' was verified by the reader already
-					 * and the UUID did not change. */
-					g_assert_not_reached ();
-				}
-				g_assert_no_error (error);
-				nm_log_info (LOGD_SETTINGS, "Connection %s updated",
-				             nm_connection_get_id (NM_CONNECTION (new)));
-			}
-			g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED);
-		} else if (new) {
-			track_new_connection (self, new);
-			if (is_managed_plugin () && is_managed (conn_name))
-				g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_CONNECTION_ADDED, new);
-		}
-
-		/* Track all valid connections so we can remove deleted ones later */
-		g_hash_table_insert (new_connections,
-		                     (gpointer) nm_connection_get_uuid (NM_CONNECTION (new)),
-		                     new);
-	}
-
-	/* remove deleted/unused connections */
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, (gpointer) &uuid, (gpointer) &candidate)) {
-		/* only saved connections (which have a conn_name) get removed; unsaved
-		 * ones obviously don't exist in /etc/conf.d/net yet and shouldn't get
-		 * blown away by net file changes.
-		 */
-		if (   nm_ifnet_connection_get_conn_name (NM_IFNET_CONNECTION (candidate))
-		    && !g_hash_table_lookup (new_connections, uuid)) {
-			nm_settings_connection_signal_remove (candidate);
-			g_hash_table_iter_remove (&iter);
-		}
-	}
-	g_hash_table_destroy (new_connections);
-	g_list_free (conn_names);
-}
-
-static NMSettingsConnection *
-add_connection (NMSettingsPlugin *config,
-                NMConnection *source,
-                gboolean save_to_disk,
-                GError **error)
-{
-	SettingsPluginIfnetPrivate *priv = SETTINGS_PLUGIN_IFNET_GET_PRIVATE ((SettingsPluginIfnet *) config);
-	NMIfnetConnection *new = NULL;
-
-	/* Ensure we reject attempts to add the connection long before we're
-	 * asked to write it to disk.
-	 */
-	if (!ifnet_can_write_connection (source, error))
-		goto out;
-
-	if (save_to_disk) {
-		if (!ifnet_add_new_connection (source, CONF_NET_FILE, WPA_SUPPLICANT_CONF, NULL, NULL, error))
-			goto out;
-		reload_connections (config);
-		new = g_hash_table_lookup (priv->connections, nm_connection_get_uuid (source));
-	} else {
-		new = nm_ifnet_connection_new (source, NULL);
-		if (new) {
-			track_new_connection (SETTINGS_PLUGIN_IFNET (config), new);
-			/* track_new_connection refs 'new' */
-			g_object_unref (new);
-		}
-	}
-
-out:
-	if (!new && error && !*error) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		                     "The ifnet plugin cannot add the connection (unknown error).");
-	}
-	return (NMSettingsConnection *) new;
-}
-
-static void
-check_unmanaged (gpointer key, gpointer data, gpointer user_data)
-{
-	NMIfnetConnection *connection = NM_IFNET_CONNECTION (data);
-	GSList **list = (GSList **) user_data;
-	const char *mac, *conn_name;
-	char *unmanaged_spec;
-	GSList *iter;
-
-	conn_name = nm_ifnet_connection_get_conn_name (connection);
-
-	if (!conn_name || is_managed (conn_name))
-		return;
-
-	nm_log_info (LOGD_SETTINGS, "Checking unmanaged: %s", conn_name);
-	mac = ifnet_get_data (conn_name, "mac");
-	if (mac)
-		unmanaged_spec = g_strdup_printf ("mac:%s", mac);
-	else
-		unmanaged_spec = g_strdup_printf ("interface-name:%s", conn_name);
-
-	/* Just return if the unmanaged spec is already in the list */
-	for (iter = *list; iter; iter = g_slist_next (iter)) {
-		if (g_str_equal (iter->data, unmanaged_spec)) {
-			g_free (unmanaged_spec);
-			return;
-		}
-	}
-
-	nm_log_info (LOGD_SETTINGS, "Add unmanaged: %s", unmanaged_spec);
-	*list = g_slist_prepend (*list, unmanaged_spec);
-}
-
-static GSList *
-get_unmanaged_specs (NMSettingsPlugin * config)
-{
-	SettingsPluginIfnetPrivate *priv = SETTINGS_PLUGIN_IFNET_GET_PRIVATE ((SettingsPluginIfnet *) config);
-	GSList *list = NULL;
-
-	nm_log_info (LOGD_SETTINGS, "getting unmanaged specs...");
-	g_hash_table_foreach (priv->connections, check_unmanaged, &list);
-	return list;
-}
-
-static GSList *
-get_connections (NMSettingsPlugin *config)
-{
-	SettingsPluginIfnetPrivate *priv = SETTINGS_PLUGIN_IFNET_GET_PRIVATE ((SettingsPluginIfnet *) config);
-	GSList *connections = NULL;
-	GHashTableIter iter;
-	NMIfnetConnection *connection;
-
-	nm_log_info (LOGD_SETTINGS, "(%p) ... get_connections.", config);
-
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &connection)) {
-		const char *conn_name = nm_ifnet_connection_get_conn_name (connection);
-
-		if (!conn_name || (!priv->unmanaged_well_known && is_managed (conn_name)))
-			connections = g_slist_prepend (connections, connection);
-	}
-	nm_log_info (LOGD_SETTINGS, "(%p) connections count: %d",
-	             config, g_slist_length (connections));
-	return connections;
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject * object, guint prop_id, GValue * value,
-              GParamSpec * pspec)
-{
-	switch (prop_id) {
-	case NM_SETTINGS_PLUGIN_PROP_NAME:
-		g_value_set_string (value, IFNET_PLUGIN_NAME_PRINT);
-		break;
-	case NM_SETTINGS_PLUGIN_PROP_INFO:
-		g_value_set_string (value, IFNET_PLUGIN_INFO);
-		break;
-	case NM_SETTINGS_PLUGIN_PROP_CAPABILITIES:
-		g_value_set_uint (value,
-		                  NM_SETTINGS_PLUGIN_CAP_MODIFY_CONNECTIONS);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*****************************************************************************/
-
-static void
-init (NMSettingsPlugin *config)
-{
-	SettingsPluginIfnet *self = SETTINGS_PLUGIN_IFNET (config);
-	SettingsPluginIfnetPrivate *priv = SETTINGS_PLUGIN_IFNET_GET_PRIVATE (self);
-
-	nm_log_info (LOGD_SETTINGS, "Initializing!");
-
-	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
-	priv->unmanaged_well_known = !is_managed_plugin ();
-	nm_log_info (LOGD_SETTINGS, "management mode: %s",
-	             priv->unmanaged_well_known ? "unmanaged" : "managed");
-
-	setup_monitors (NULL, config);
-	reload_connections (config);
-
-	nm_log_info (LOGD_SETTINGS, "Initialzation complete!");
-}
-
-/*****************************************************************************/
-
-static void
-settings_plugin_ifnet_init (SettingsPluginIfnet * plugin)
-{
-}
-
-static void
-dispose (GObject * object)
-{
-	SettingsPluginIfnet *plugin = SETTINGS_PLUGIN_IFNET (object);
-	SettingsPluginIfnetPrivate *priv = SETTINGS_PLUGIN_IFNET_GET_PRIVATE ((SettingsPluginIfnet *) plugin);
-
-	cancel_monitors (NULL, object);
-	if (priv->connections) {
-		g_hash_table_destroy (priv->connections);
-		priv->connections = NULL;
-	}
-
-	ifnet_destroy ();
-	wpa_parser_destroy ();
-	G_OBJECT_CLASS (settings_plugin_ifnet_parent_class)->dispose (object);
-}
-
-static void
-settings_plugin_ifnet_class_init (SettingsPluginIfnetClass * req_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (req_class);
-
-	object_class->dispose = dispose;
-	object_class->get_property = get_property;
-
-	g_object_class_override_property (object_class,
-	                                  NM_SETTINGS_PLUGIN_PROP_NAME,
-	                                  NM_SETTINGS_PLUGIN_NAME);
-
-	g_object_class_override_property (object_class,
-	                                  NM_SETTINGS_PLUGIN_PROP_INFO,
-	                                  NM_SETTINGS_PLUGIN_INFO);
-
-	g_object_class_override_property (object_class,
-	                                  NM_SETTINGS_PLUGIN_PROP_CAPABILITIES,
-	                                  NM_SETTINGS_PLUGIN_CAPABILITIES);
-}
-
-static void
-settings_plugin_interface_init (NMSettingsPluginInterface *plugin_iface)
-{
-	plugin_iface->init = init;
-	plugin_iface->get_connections = get_connections;
-	plugin_iface->get_unmanaged_specs = get_unmanaged_specs;
-	plugin_iface->add_connection = add_connection;
-	plugin_iface->reload_connections = reload_connections;
-}
-
-/*****************************************************************************/
-
-G_MODULE_EXPORT GObject *
-nm_settings_plugin_factory (void)
-{
-	return G_OBJECT (g_object_ref (settings_plugin_ifnet_get ()));
-}
diff --git a/src/settings/plugins/ifnet/nms-ifnet-plugin.h b/src/settings/plugins/ifnet/nms-ifnet-plugin.h
deleted file mode 100644
index f006e7e4..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-plugin.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service (ifnet)
- *
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#ifndef _PLUGIN_H_
-#define _PLUGIN_H_
-
-#define SETTINGS_TYPE_PLUGIN_IFNET            (settings_plugin_ifnet_get_type ())
-#define SETTINGS_PLUGIN_IFNET(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SETTINGS_TYPE_PLUGIN_IFNET, SettingsPluginIfnet))
-#define SETTINGS_PLUGIN_IFNET_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SETTINGS_TYPE_PLUGIN_IFNET, SettingsPluginIfnetClass))
-#define SETTINGS_IS_PLUGIN_IFNET(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SETTINGS_TYPE_PLUGIN_IFNET))
-#define SETTINGS_IS_PLUGIN_IFNET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SETTINGS_TYPE_PLUGIN_IFNET))
-#define SETTINGS_PLUGIN_IFNET_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SETTINGS_TYPE_PLUGIN_IFNET, SettingsPluginIfnetClass))
-
-typedef struct _SettingsPluginIfnet SettingsPluginIfnet;
-typedef struct _SettingsPluginIfnetClass SettingsPluginIfnetClass;
-
-GType settings_plugin_ifnet_get_type (void);
-
-#endif
diff --git a/src/settings/plugins/ifnet/nms-ifnet-wpa-parser.c b/src/settings/plugins/ifnet/nms-ifnet-wpa-parser.c
deleted file mode 100644
index 2b62e886..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-wpa-parser.c
+++ /dev/null
@@ -1,584 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ifnet-wpa-parser.h"
-
-#include <string.h>
-#include <stdlib.h>
-
-#include "nm-utils/nm-hash-utils.h"
-#include "settings/nm-settings-plugin.h"
-
-#include "nms-ifnet-net-parser.h"
-#include "nms-ifnet-net-utils.h"
-
-/* Security information */
-static GHashTable *wsec_table = NULL;
-
-/* Global information used for writing */
-static GHashTable *wsec_global_table = NULL;
-
-static gboolean wpa_parser_data_changed = FALSE;
-
-static long
-wpa_get_long (GHashTable *table, const char *key)
-{
-	return atol (g_hash_table_lookup (table, key));
-}
-
-static void
-destroy_security (GHashTable * network)
-{
-	gpointer key, value;
-	GHashTableIter iter;
-
-	g_return_if_fail (network);
-	g_hash_table_iter_init (&iter, network);
-	while (g_hash_table_iter_next (&iter, &key, &value)) {
-		g_free (key);
-		g_free (value);
-	}
-
-	g_hash_table_destroy (network);
-}
-
-static GHashTable *
-add_security (GHashTable *security)
-{
-	GHashTable *oldsecurity;
-	const char *ssid, *value;
-	char *ssid_key;
-	gboolean is_hex_ssid;
-
-	/* Every security information should have a ssid */
-	ssid = g_hash_table_lookup (security, "ssid");
-	if (!ssid) {
-		destroy_security (security);
-		return NULL;
-	}
-
-	/* Hex format begins with " */
-	is_hex_ssid = (ssid[0] != '"');
-	if ((value = g_hash_table_lookup (security, "disabled")) != NULL) {
-		if (strcmp (value, "1") == 0) {
-			destroy_security (security);
-			return NULL;
-		}
-	}
-
-	/* Default priority is 1 */
-	if (g_hash_table_lookup (security, "priority") == NULL)
-		g_hash_table_insert (security, g_strdup ("priority"),
-				     g_strdup ("1"));
-
-	oldsecurity = g_hash_table_lookup (wsec_table, ssid);
-	/* Security with lower priority will be ignored */
-	if (oldsecurity != NULL) {
-		if (wpa_get_long (oldsecurity, "priority") >=
-		    wpa_get_long (security, "priority")) {
-			destroy_security (security);
-			return NULL;
-		} else {
-			g_hash_table_remove (wsec_table, ssid);
-			destroy_security (oldsecurity);
-		}
-	}
-
-	/* format ssid */
-	ssid_key =
-	    is_hex_ssid ? g_strdup_printf ("0x%s",
-					   ssid) :
-	    strip_string (g_strdup (ssid), '"');
-	g_hash_table_insert (wsec_table, ssid_key, security);
-	return security;
-}
-
-static void
-add_key_value (GHashTable * network, gchar * line)
-{
-	gpointer orig_key, orig_value;
-	gchar **key_value;
-
-	if (g_str_has_prefix (line, "network={"))
-		line += 9;
-	strip_string (line, '{');
-	strip_string (line, '}');
-	if (line[0] == '\0')
-		return;
-	key_value = g_strsplit (line, "=", 2);
-	if (g_strv_length (key_value) != 2) {
-		g_strfreev (key_value);
-		return;
-	}
-	g_strstrip (key_value[0]);
-	g_strstrip (key_value[1]);
-
-	/* Reserve quotes for psk, wep_key, ssid
-	 * Quotes will determine whether they are hex format */
-	if (strcmp (key_value[0], "psk") != 0
-	    && !g_str_has_prefix (key_value[0], "wep_key")
-	    && strcmp (key_value[0], "ssid") != 0)
-		strip_string (key_value[1], '"');
-
-	/* This sucks */
-	if (g_hash_table_lookup_extended (network, key_value[0], &orig_key, &orig_value)) {
-		g_hash_table_remove (network, orig_key);
-		g_free (orig_key);
-		g_free (orig_value);
-	}
-
-	g_hash_table_insert (network, g_strdup (key_value[0]),
-			     g_strdup (key_value[1]));
-	g_strfreev (key_value);
-}
-
-static void
-add_one_wep_key (GHashTable * table, int key_num, gchar * one_wep_key)
-{
-	if (one_wep_key[0] == 's') {
-		//asc key
-		g_hash_table_insert (table,
-				     g_strdup_printf ("wep_key%d", key_num - 1),
-				     g_strdup_printf ("\"%s\"",
-						      one_wep_key + 2));
-	} else {
-		gchar buf[30];
-		int i = 0, j = 0;
-
-		//hex key
-		while (one_wep_key[i] != '\0') {
-			if (one_wep_key[i] != '-')
-				buf[j++] = one_wep_key[i];
-			i++;
-		}
-		buf[j] = '\0';
-		g_hash_table_insert (table,
-				     g_strdup_printf ("wep_key%d", key_num - 1),
-				     g_strdup (buf));
-
-	}
-}
-
-/* Reading wep security information from /etc/conf.d/net.
- * This should not be used in future, use wpa_supplicant instead. */
-static void
-add_keys_from_net (void)
-{
-	GList *names = ifnet_get_connection_names ();
-	GList *iter = names;
-	gchar *wep_keys = "(\\[([1-4])\\]\\s+(s:\\w{5}|s:\\w{13}|"
-	    "([\\da-fA-F]{4}\\-){2}[\\da-fA-F]{2}|"
-	    "([\\da-fA-F]{4}\\-){6}[\\da-fA-F]{2})\\s+)";
-	gchar *key_method =
-	    "\\s+key\\s+\\[([1-4])\\]\\s+enc\\s+(open|restricted)";
-	GRegex *regex_keys = g_regex_new (wep_keys, 0, 0, NULL);
-	GRegex *regex_method = g_regex_new (key_method, 0, 0, NULL);
-	GMatchInfo *keys_info;
-	GMatchInfo *method_info;
-
-	while (iter) {
-		gchar *conn_name = iter->data;
-		GHashTable *table;
-		const char *key_str;
-
-		if ((key_str = ifnet_get_data (conn_name, "key")) == NULL) {
-			iter = g_list_next (iter);
-			continue;
-		}
-
-		wpa_add_security (conn_name);
-		table = _get_hash_table (conn_name);
-		/* Give lowest priority */
-		wpa_set_data (conn_name, "priority", "0");
-		g_regex_match (regex_keys, key_str, 0, &keys_info);
-		/* add wep keys */
-		while (g_match_info_matches (keys_info)) {
-			gchar *key_num = g_match_info_fetch (keys_info, 2);
-			gchar *one_wep_key = g_match_info_fetch (keys_info, 3);
-
-			add_one_wep_key (table, atoi (key_num), one_wep_key);
-			g_free (key_num);
-			g_free (one_wep_key);
-			g_match_info_next (keys_info, NULL);
-		}
-		g_match_info_free (keys_info);
-
-		g_regex_match (regex_method, key_str, 0, &method_info);
-		/* set default key index and auth alg */
-		if (g_match_info_matches (method_info)) {
-			gchar *default_idx =
-			    g_match_info_fetch (method_info, 1);
-			gchar *method = g_match_info_fetch (method_info, 2);
-
-			default_idx[0]--;
-			g_hash_table_insert (table, g_strdup ("wep_tx_keyidx"),
-					     default_idx);
-			g_hash_table_insert (table, g_strdup ("auth_alg"),
-					     g_ascii_strup (method, -1));
-		}
-		g_match_info_free (method_info);
-		add_security (table);
-		iter = g_list_next (iter);
-	}
-	g_list_free (names);
-	g_regex_unref (regex_keys);
-	g_regex_unref (regex_method);
-}
-
-static void
-add_global_data (gchar * line)
-{
-	gchar **key_value;
-
-	g_strstrip (line);
-	key_value = g_strsplit (line, "=", 2);
-	if (g_strv_length (key_value) != 2) {
-		nm_log_warn (LOGD_SETTINGS, "Can't handle this line: %s\n", line);
-		g_strfreev (key_value);
-		return;
-	}
-	g_hash_table_insert (wsec_global_table,
-			     g_strdup (g_strstrip (key_value[0])),
-			     g_strdup (g_strstrip (key_value[1])));
-	g_strfreev (key_value);
-}
-
-void
-wpa_parser_init (const char *wpa_supplicant_conf)
-{
-	GIOChannel *channel = NULL;
-	gchar *line;
-	gboolean complete = FALSE;
-
-	wpa_parser_data_changed = FALSE;
-	wsec_table = g_hash_table_new (nm_str_hash, g_str_equal);
-	wsec_global_table = g_hash_table_new (nm_str_hash, g_str_equal);
-
-	if (g_file_test (wpa_supplicant_conf, G_FILE_TEST_IS_REGULAR))
-		channel =
-		    g_io_channel_new_file (wpa_supplicant_conf, "r", NULL);
-	if (channel == NULL) {
-		nm_log_warn (LOGD_SETTINGS, "Can't open %s for wireless security",
-		             wpa_supplicant_conf);
-		return;
-	}
-
-	while (g_io_channel_read_line (channel, &line, NULL, NULL, NULL)
-	       != G_IO_STATUS_EOF) {
-		g_strstrip (line);
-		if (line[0] != '#' && line[0] != '\0') {
-			if (strstr (line, "network={") == NULL) {
-				add_global_data (line);
-				g_free (line);
-				continue;
-			} else {
-				GHashTable *network =
-				    g_hash_table_new (nm_str_hash, g_str_equal);
-
-				do {
-					gchar *quote_start, *quote_end = NULL, *comment;
-
-					if (line[0] == '#' || line[0] == '\0') {
-						g_free (line);
-						continue;
-					}
-					/* ignore inline comments unless inside
-					   a double-quoted string */
-					if ((quote_start = strchr (line, '"')) != NULL)
-						quote_end = strrchr (quote_start + 1, '"');
-					if ((comment = strchr ((quote_end != NULL) ?
-					                       quote_end : line, '#')) != NULL)
-						*comment = '\0';
-					if (strstr (line, "}") != NULL)
-						complete = TRUE;
-					add_key_value (network, line);
-					g_free (line);
-				} while (complete == FALSE
-					 &&
-					 g_io_channel_read_line
-					 (channel, &line, NULL,
-					  NULL, NULL) != G_IO_STATUS_EOF);
-				add_security (network);
-				//EOF in inner loop
-				if (complete == FALSE) {
-					g_free (line);
-					break;
-				}
-				complete = FALSE;
-			}
-		} else
-			g_free (line);
-	}
-
-	g_io_channel_shutdown (channel, FALSE, NULL);
-	g_io_channel_unref (channel);
-
-	add_keys_from_net ();
-}
-
-const char *
-wpa_get_value (const char *ssid, const char *key)
-{
-	GHashTable *target = g_hash_table_lookup (wsec_table, ssid);
-
-	if (target)
-		return g_hash_table_lookup (target, key);
-	return NULL;
-}
-
-gboolean
-exist_ssid (const char *ssid)
-{
-	return g_hash_table_lookup (wsec_table, ssid) != NULL;
-}
-
-GHashTable *
-_get_hash_table (const char *ssid)
-{
-	return g_hash_table_lookup (wsec_table, ssid);
-}
-
-static gchar *quoted_keys[] =
-    { "identity", "cert", "private", "phase", "password", NULL };
-
-/* tell whether the key needs quotes when writing is performed */
-static gboolean
-need_quote (gchar * key)
-{
-	int i = 0;
-
-	while (quoted_keys[i] != NULL) {
-		if (strstr (key, quoted_keys[i]))
-			return TRUE;
-		i++;
-	}
-	return FALSE;
-}
-
-gboolean
-wpa_flush_to_file (const char *config_file)
-{
-	GIOChannel *channel;
-	GError *error = NULL;
-	gpointer key, value, ssid, security;
-	GHashTableIter iter, iter_security;
-	gchar *out_line;
-	gsize bytes_written;
-	gboolean result = FALSE;
-
-	if (!wpa_parser_data_changed)
-		return TRUE;
-	if (!wsec_table || !wsec_global_table)
-		return FALSE;
-
-	backup_file (config_file);
-
-	channel = g_io_channel_new_file (config_file, "w", NULL);
-	if (!channel) {
-		nm_log_warn (LOGD_SETTINGS, "Can't open file %s for writing", config_file);
-		return FALSE;
-	}
-	g_hash_table_iter_init (&iter, wsec_global_table);
-	nm_log_info (LOGD_SETTINGS, "Writing to %s", config_file);
-	g_io_channel_write_chars (channel,
-				  "#Generated by NetworkManager\n"
-				  "###### Global Configuration ######\n",
-				  -1, &bytes_written, &error);
-	if (error)
-		goto done;
-
-	/* Writing global information */
-	while (g_hash_table_iter_next (&iter, &key, &value)) {
-		out_line =
-		    g_strdup_printf ("%s=%s\n", (gchar *) key, (gchar *) value);
-		g_io_channel_write_chars (channel, out_line, -1, &bytes_written,
-					  &error);
-		if (bytes_written == 0 || error)
-			break;
-		g_free (out_line);
-	}
-	if (error)
-		goto done;
-	g_io_channel_write_chars (channel,
-				  "\n###### Security Configuration ######\n",
-				  -1, &bytes_written, &error);
-	if (error)
-		goto done;
-
-	g_hash_table_iter_init (&iter, wsec_table);
-	/* Writing security */
-	while (g_hash_table_iter_next (&iter, &ssid, &security)) {
-		g_hash_table_iter_init (&iter_security,
-					(GHashTable *) security);
-		g_io_channel_write_chars (channel, "network={\n", -1,
-					  &bytes_written, &error);
-		if (error)
-			goto done;
-		while (g_hash_table_iter_next (&iter_security, &key, &value)) {
-			out_line =
-			    g_strdup_printf (need_quote ((gchar *) key) ?
-					     "\t%s=\"%s\"\n" : "\t%s=%s\n",
-					     (gchar *) key, (gchar *) value);
-			g_io_channel_write_chars (channel, out_line, -1,
-						  &bytes_written, &error);
-			if (bytes_written == 0 || error)
-				goto done;
-			g_free (out_line);
-		}
-		g_io_channel_write_chars (channel, "}\n\n", -1, &bytes_written, &error);
-
-	}
-	g_io_channel_flush (channel, &error);
-	if (error)
-		goto done;
-
-	wpa_parser_data_changed = FALSE;
-	result = TRUE;
-done:
-	if (error) {
-		nm_log_warn (LOGD_SETTINGS, "Error writing WPA configuration: %s", error->message);
-		g_error_free (error);
-	}
-	g_io_channel_shutdown (channel, FALSE, NULL);
-	g_io_channel_unref (channel);
-	return result;
-}
-
-/* If value is NULL, this method will delete old key value pair */
-void
-wpa_set_data (const char *ssid, const char *key, const char *value)
-{
-	gpointer old_key = NULL, old_value = NULL;
-	GHashTable *security = g_hash_table_lookup (wsec_table, ssid);
-	gchar * stripped = NULL;
-
-	g_return_if_fail (security != NULL);
-
-	if (value){
-		stripped = g_strdup(value);
-		if (strcmp (key, "ssid") != 0 && strcmp (key, "psk") != 0
-			&& !g_str_has_prefix (key, "wep_key"))
-			strip_string (stripped, '"');
-	}
-
-	/* Remove old key value pairs */
-	if (g_hash_table_lookup_extended
-	    (security, key, &old_key, &old_value)) {
-		if (stripped && !strcmp(old_value, stripped)){
-			g_free (stripped);
-			return;
-		}
-		g_hash_table_remove (security, old_key);
-		g_free (old_key);
-		g_free (old_value);
-	} else if (!value)
-		return;
-
-	/* Add new key value */
-	if (stripped)
-		g_hash_table_insert (security, g_strdup (key), stripped);
-	wpa_parser_data_changed = TRUE;
-}
-
-gboolean
-wpa_has_security (const char *ssid)
-{
-	return g_hash_table_lookup (wsec_table, ssid) != NULL;
-}
-
-gboolean
-wpa_add_security (const char *ssid)
-{
-	if (wpa_has_security (ssid))
-		return TRUE;
-	else {
-		GHashTable *security =
-		    g_hash_table_new (nm_str_hash, g_str_equal);
-		gchar *ssid_i;
-
-		nm_log_info (LOGD_SETTINGS, "Adding security for %s", ssid);
-		if (g_str_has_prefix (ssid, "0x")) {
-			/* hex ssid */
-			ssid_i = g_strdup (ssid + 2);
-		} else {
-			/* ascii ssid requires quotes */
-			ssid_i = g_strdup_printf ("\"%s\"", ssid);
-		}
-		g_hash_table_insert (security, strdup ("ssid"), ssid_i);
-		g_hash_table_insert (security, strdup ("priority"),
-				     strdup ("1"));
-		g_hash_table_insert (wsec_table, g_strdup (ssid), security);
-		wpa_parser_data_changed = TRUE;
-		return TRUE;
-	}
-}
-
-gboolean
-wpa_delete_security (const char *ssid)
-{
-	gpointer old_key, old_value;
-
-	g_return_val_if_fail (wsec_table != NULL && ssid != NULL, FALSE);
-	nm_log_info (LOGD_SETTINGS, "Deleting security for %s", ssid);
-	if (!g_hash_table_lookup_extended
-	    (wsec_table, ssid, &old_key, &old_value))
-		return FALSE;
-	g_hash_table_remove (wsec_table, old_key);
-	g_free (old_key);
-	destroy_security ((GHashTable *) old_value);
-	wpa_parser_data_changed = TRUE;
-	return TRUE;
-
-}
-
-void
-wpa_parser_destroy (void)
-{
-	GHashTableIter iter;
-	gpointer key;
-	gpointer value;
-
-	/* Destroy security */
-	if (wsec_table) {
-		g_hash_table_iter_init (&iter, wsec_table);
-		while (g_hash_table_iter_next (&iter, &key, &value)) {
-			destroy_security ((GHashTable *) value);
-			g_free (key);
-		}
-
-		g_hash_table_destroy (wsec_table);
-		wsec_table = NULL;
-	}
-
-	/* Destroy global data */
-	if (wsec_global_table) {
-		g_hash_table_iter_init (&iter, wsec_global_table);
-		while (g_hash_table_iter_next (&iter, &key, &value)) {
-			g_free (key);
-			g_free (value);
-		}
-
-		g_hash_table_destroy (wsec_global_table);
-		wsec_global_table = NULL;
-	}
-}
diff --git a/src/settings/plugins/ifnet/nms-ifnet-wpa-parser.h b/src/settings/plugins/ifnet/nms-ifnet-wpa-parser.h
deleted file mode 100644
index d096f468..00000000
--- a/src/settings/plugins/ifnet/nms-ifnet-wpa-parser.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#ifndef _WPA_PARSER_H
-#define _WPA_PARSER_H
-
-#define WPA_SUPPLICANT_CONF SYSCONFDIR "/wpa_supplicant/wpa_supplicant.conf"
-
-void wpa_parser_init (const char *wpa_supplicant_conf);
-void wpa_parser_destroy (void);
-
-/* reader functions */
-const char *wpa_get_value (const char *ssid, const char *key);
-gboolean exist_ssid (const char *ssid);
-GHashTable *_get_hash_table (const char *ssid);
-gboolean wpa_has_security (const char *ssid);
-
-/* writer functions */
-gboolean wpa_flush_to_file (const char *config_file);
-void wpa_set_data (const char *ssid, const char *key, const char *value);
-gboolean wpa_add_security (const char *ssid);
-gboolean wpa_delete_security (const char *ssid);
-#endif
diff --git a/src/settings/plugins/ifnet/tests/net b/src/settings/plugins/ifnet/tests/net
deleted file mode 100644
index a5ac9ca2..00000000
--- a/src/settings/plugins/ifnet/tests/net
+++ /dev/null
@@ -1,158 +0,0 @@
-# This blank configuration will automatically use DHCP for any net.*
-# scripts in /etc/init.d.  To create a more complete configuration,
-# please review /etc/conf.d/net.example and save your configuration
-# in /etc/conf.d/net (this file :]!).
-
-modules="!wpa_supplicant"
-
-config_eth0=(
-"202.117.16.121    netmask    255.255.255.0 brd 202.117.16.255"
-"192.168.4.121/24"
-"dhcp6"
-)
-routes_eth0=( "default via     202.117.16.1" 
-		"192.168.4.0/24 via 192.168.4.1")
-dns_servers_eth0="202.117.0.20    202.117.0.21"
-dns_search_eth0="p12.edu.cn p13.edu.cn"
-
-config_eth1=(
-	"dhcp"
-)
-enable_ipv6_eth1="true"
-routes_eth1=( "default via 202.117.16.1" )
-dns_servers_eth1="202.117.0.20 202.117.0.21"
-config_eth2=(
-"202.117.16.1211    netmask    255.255.255.0 brd 202.117.16.255"
-"192.168.4.121/24"
-"4321:0:1:2:3:4:567:89ab/64"
-)
-routes_eth2=("default via  4321:0:1:2:3:4:567:89ab")
-enable_ipv6_eth2="true"
-config_eth3=("nufjlsjlll")
-managed_eth4="false"
-routes_eth4=("default via 4321:0:1:2:3:4:567:89ab")
-config_eth5=("dhcp")
-
-config_eth7=( "dhcp" )
-auto_eth7="true"
-
-# missing config_eth8
-auto_eth8="true"
-
-#new openrc style
-config_eth9="202.117.16.10/24 202.117.17.10/24"
-routes_eth9="default via 202.117.16.1
-10.0.0.0/8 via 192.168.0.1
-"
-config_eth10="202.117.16.2 netmask 255.255.255.0"
-routes_eth10="10.0.0.0/8 via 192.168.0.1"
-
-config_myxjtu2=("202.117.16.121/24 brd 202.117.16.255")
-routes_myxjtu2=("default via 202.117.16.1")
-dns_servers_myxjtu2="202.117.0.20 202.117.0.21"
-#key_myxjtu2="[1] s:xjtud key [1] enc restricted"
-#key_eth6="[1] aaaa-4444-3d [2] s:xjtudlc key [1] enc open"
-
-
-username_ppp0='user'
-password_ppp0='password'
-
-config_qiaomuf=("dhcp")
-
-config_1xtest=("dhcp")
-
-config_0xab3ace=("dhcp")
-
-modules=( "iproute2" )
-	config_kvm0=( "null" )
-	config_kvm1=( "null" )
-
-	tuntap_kvm0="tap"
-	tuntap_kvm1="tap"
-	tunctl_kvm0="-u user"
-	tunctl_kvm1="-u user"
-
-bridge_br0="eth0 kvm0 kvm1"
-config_br0=( "192.168.1.10/24" )
-	brctl_br0=( "setfd 0")
-	dhcp_eth1="nosendhost nontp -I"
-
-predown() {
-	# The default in the script is to test for NFS root and disallow
-	# downing interfaces in that case.  Note that if you specify a
-	# predown() function you will override that logic.  Here it is, in
-	# case you still want it...
-	if is_net_fs /; then
-		eerror "root filesystem is network mounted -- can't stop ${IFACE}"
-		return 1
-	fi
-
-	# Remember to return 0 on success
-	return 0
-}
-
-postup() {
-	# This function could be used, for example, to register with a
-	# dynamic DNS service.  Another possibility would be to
-	# send/receive mail once the interface is brought up.
-
-	# Here is an example that allows the use of iproute rules
-	# which have been configured using the rules_eth0 variable.
-	#rules_eth0=" \
-	#	'from 24.80.102.112/32 to 192.168.1.0/24 table localnet priority 100' \
-	#	'from 216.113.223.51/32 to 192.168.1.0/24 table localnet priority 100' \
-	#"
-	eval set -- \$rules_${IFVAR}
-	if [ $# != 0 ]; then
-		einfo "Adding IP policy routing rules"
-		eindent
-		# Ensure that the kernel supports policy routing
-		if ! ip rule list | grep -q "^"; then
-			eerror "You need to enable IP Policy Routing (CONFIG_IP_MULTIPLE_TABLES)"
-			eerror "in your kernel to use ip rules"
-		else
-			for x; do
-				ebegin "${x}"
-				ip rule add ${x}
-				eend $?
-			done
-		fi
-		eoutdent
-		# Flush the cache
-		ip route flush cache dev "${IFACE}"
-	fi
-
-}
-
-postdown() {
-	# Enable Wake-On-LAN for every interface except for lo
-	# Probably a good idea to set ifdown="no" in /etc/conf.d/net
-	# as well ;)
-	[ "${IFACE}" != "lo" ] && ethtool -s "${IFACE}" wol g
-
-	Automatically erase any ip rules created in the example postup above
-	if interface_exists "${IFACE}"; then
-		# Remove any rules for this interface
-		local rule
-		ip rule list | grep " iif ${IFACE}[ ]*" | {
-			while read rule; do
-				rule="${rule#*:}"	
-				ip rule del ${rule}
-			done
-		}
-		# Flush the route cache
-		ip route flush cache dev "${IFACE}"
-	fi
-
-	# Return 0 always
-	return 0
-}
-
-failup() {
-       # This function is mostly here for completeness... I haven't
-       # thought of anything nifty to do with it yet ;-)
-}
-
-faildown() 
-{}
-
diff --git a/src/settings/plugins/ifnet/tests/net.all b/src/settings/plugins/ifnet/tests/net.all
deleted file mode 100644
index 285a4cdf..00000000
--- a/src/settings/plugins/ifnet/tests/net.all
+++ /dev/null
@@ -1,864 +0,0 @@
-##############################################################################
-# QUICK-START
-#
-# The quickest start is if you want to use DHCP.
-# In that case, everything should work out of the box, no configuration
-# necessary, though the startup script will warn you that you haven't
-# specified anything.
-
-# WARNING :- some examples have a mixture of IPv4 (ie 192.168.0.1) and IPv6
-# (ie 4321:0:1:2:3:4:567:89ab) internet addresses. They only work if you have
-# the relevant kernel option enabled. So if you don't have an IPv6 enabled
-# kernel then remove the IPv6 address from your config.
-
-# If you want to use a static address or use DHCP explicitly, jump
-# down to the section labelled INTERFACE HANDLERS.
-#
-# If you want to do anything more fancy, you should take the time to
-# read through the rest of this file.
-
-##############################################################################
-# MODULES
-#
-# We now support modular networking scripts which means we can easily
-# add support for new interface types and modules while keeping
-# compatability with existing ones.
-# 
-# Modules load by default if the package they need is installed.  If
-# you specify a module here that doesn't have it's package installed
-# then you get an error stating which package you need to install.
-# Ideally, you only use the modules setting when you have two or more
-# packages installed that supply the same service.
-#
-# In other words, you probably should DO NOTHING HERE...
-
-# Prefer ifconfig over iproute2
-modules=( "ifconfig" )
-
-# You can also specify other modules for an interface
-# In this case we prefer udhcpc over dhcpcd
-modules_eth0=( "udhcpc" )
-
-# You can also specify which modules not to use - for example you may be
-# using a supplicant or linux-wlan-ng to control wireless configuration but
-# you still want to configure network settings per ESSID associated with.
-modules=( "!iwconfig" "!wpa_supplicant" )
-# IMPORTANT: If you need the above, please disable modules in that order
-
-
-##############################################################################
-# INTERFACE HANDLERS
-# 
-# We provide two interface handlers presently: ifconfig and iproute2.
-# You need one of these to do any kind of network configuration.
-# For ifconfig support, emerge sys-apps/net-tools
-# For iproute2 support, emerge sys-apps/iproute2
-
-# If you don't specify an interface then we prefer iproute2 if it's installed
-# To prefer ifconfig over iproute2
-modules=( "ifconfig" )
-
-# For a static configuration, use something like this
-# (They all do exactly the same thing btw)
-config_eth0=( "192.168.0.2/24" )
-config_eth0=( "192.168.0.2 netmask 255.255.255.0" )
-
-# We can also specify a broadcast
-config_eth0=( "192.168.0.2/24 brd 192.168.0.255" )
-config_eth0=( "192.168.0.2 netmask 255.255.255.0 broadcast 192.168.0.255" )
-
-# If you need more than one address, you can use something like this
-# NOTE: ifconfig creates an aliased device for each extra IPv4 address
-#       (eth0:1, eth0:2, etc)
-#       iproute2 does not do this as there is no need to
-config_eth0=(
-	"192.168.0.2/24"
-	"192.168.0.3/24"
-	"192.168.0.4/24"
-)
-# Or you can use sequence expressions
-config_eth0=( "192.168.0.{2..4}/24" )
-# which does the same as above. Be careful though as if you use this and
-# fallbacks, you have to ensure that both end up with the same number of
-# values otherwise your fallback won't work correctly.
-
-# You can also use IPv6 addresses
-# (you should always specify a prefix length with IPv6 here)
-config_eth0=(
-	"192.168.0.2/24"
-	"4321:0:1:2:3:4:567:89ab/64"
-	"4321:0:1:2:3:4:567:89ac/64"
-)
-
-# If you wish to keep existing addresses + routing and the interface is up,
-# you can specify a noop (no operation). If the interface is down or there
-# are no addresses assigned, then we move onto the next step (default dhcp)
-# This is useful when configuring your interface with a kernel command line
-# or similar
-config_eth0=( "noop" "192.168.0.2/24" )
-
-# If you don't want ANY address (only useful when calling for advanced stuff)
-config_eth0=( "null" )
-
-# Here's how to do routing if you need it
-routes_eth0=(
-	"default via 192.168.0.1"		# IPv4 default route
-	"10.0.0.0/8 via 192.168.0.1"		# IPv4 subnet route
-	"::/0"					# IPv6 unicast
-)
-
-# If a specified module fails (like dhcp - see below), you can specify a
-# fallback like so
-fallback_eth0=( "192.168.0.2 netmask 255.255.255.0" )
-fallback_route_eth0=( "default via 192.168.0.1" )
-
-# NOTE: fallback entry must match the entry location in config_eth0
-# As such you can only have one fallback route.
-
-# Some users may need to alter the MTU - here's how
-mtu_eth0="1500"
-
-# Each module described below can set a default base metric, lower is
-# preferred over higher. This is so we can prefer a wired route over a
-# wireless route automaticaly. You can override this by setting
-metric_eth0="100"
-# or on a global basis
-metric="100"
-# The only downside of the global setting is that you have to ensure that
-# there are no conflicting routes yourself. For users with large routing
-# tables you may have to set a global metric as the due to a simple read of
-# the routing table taking over a minute at a time.
-
-##############################################################################
-# OPTIONAL MODULES
-
-# INTERFACE RENAMING
-# There is no consistent device renaming scheme for Linux.
-# The preferred way of naming devices is via the kernel module directly or
-# by using udev (http://www.reactivated.net/udevrules.php)
-
-# If you are unable to write udev rules, then we do provide a way of renaming
-# the interface based on it's MAC address, but it is not optimal.
-# Here is how to rename an interface whose MAC address is 00:11:22:33:44:55
-# to foo1
-rename_001122334455="foo1"
-
-# You can also do this based on current device name - although this is not
-# recommended. Here we rename eth1 to foo2.
-rename_eth1="foo2"
-
-#-----------------------------------------------------------------------------
-# WIRELESS (802.11 support)
-# Wireless can be provided by iwconfig or wpa_supplicant
-
-# iwconfig
-# emerge net-wireless/wireless-tools
-# Wireless options are held in /etc/conf.d/wireless - but could be here too
-# Consult the sample file /etc/conf.d/wireless.example for instructions
-# iwconfig is the default
-
-# wpa_supplicant
-# emerge net-wireless/wpa-supplicant
-# Wireless options are held in /etc/wpa_supplicant.conf
-# Consult the sample file /etc/wpa_supplicant.conf.example for instructions
-# To choose wpa_supplicant over iwconfig
-modules=( "wpa_supplicant" )
-# To configure wpa_supplicant
-wpa_supplicant_eth0="-Dwext" # For generic wireless
-wpa_supplicant_ath0="-Dmadwifi" # For Atheros based cards
-# Consult wpa_supplicant for more drivers
-# By default don't wait for wpa_suppliant to associate and authenticate.
-# If you would like to, so can specify how long in seconds
-associate_timeout_eth0=60
-# A value of 0 means wait forever.
-
-# GENERIC WIRELESS OPTIONS
-# PLEASE READ THE INSTRUCTIONS IN /etc/conf.d/wireless.example FOR
-# HOW TO USE THIS ESSID VARIABLE
-# You can also override any settings found here per ESSID - which is very
-# handy if you use different networks a lot
-config_ESSID=( "dhcp" )
-dhcpcd_ESSID="-t 5"
-
-# Setting name/domain server causes /etc/resolv.conf to be overwritten
-# Note that if DHCP is used, and you want this to take precedence then
- set dhcp_ESSID="nodns"
-dns_servers_ESSID=( "192.168.0.1" "192.168.0.2" )
-dns_domain_ESSID="some.domain"
-dns_search_ESSID="search.this.domain search.that.domain"
-# Please check the man page for resolv.conf for more information
-# as domain and search are mutually exclusive.
-
-# You can also override any settings found here per MAC address of the AP
-# in case you use Access Points with the same ESSID but need different
-# networking configs. Below is an example - of course you use the same
-# method with other variables
-mac_config_001122334455=( "dhcp" )
-mac_dhcpcd_001122334455="-t 10"
-mac_dns_servers_001122334455=( "192.168.0.1" "192.168.0.2" )
-
-# When an interface has been associated with an Access Point, a global
-# variable called ESSID is set to the Access Point's ESSID for use in the
-# pre/post user functions below (although it's not available in preup as you
-# won't have associated then)
-
-# If you're using anything else to configure wireless on your interface AND
-# you have installed any of the above packages, you need to disable them
-modules=( "!iwconfig" "!wpa_supplicant" )
-
-#-----------------------------------------------------------------------------
-# DHCP
-# DHCP can be provided by dhclient, dhcpcd, pump or udhcpc.
-#
-# dhclient: emerge net-misc/dhcp
-# dhcpcd:   emerge net-misc/dhcpcd
-# pump:     emerge net-misc/pump
-# udhcpc:   emerge net-misc/udhcp
-
-# If you have more than one DHCP client installed, you need to specify which
-# one to use - otherwise we default to dhcpcd if available.
-modules=( "dhclient" ) # to select dhclient over dhcpcd
-#
-# Notes:
-# - All clients send the current hostname to the DHCP server by default
-# - dhcpcd does not daemonize when the lease time is infinite
-# - udhcp-0.9.3-r3 and earlier do not support getting NTP servers 
-# - pump does not support getting NIS servers
-# - DHCP tends to erase any existing device information - so add
-#   static addresses after dhcp if you need them
-# - dhclient and udhcpc can set other resolv.conf options such as "option"
-#   and "sortlist"- see the System module for more details
-
-# Regardless of which DHCP client you prefer, you configure them the
-# same way using one of following depending on which interface modules
-# you're using.
-config_eth0=( "dhcp" )
-
-# For passing custom options to dhcpcd use something like the following.  This
-# example reduces the timeout for retrieving an address from 60 seconds (the
-# default) to 10 seconds.
-dhcpcd_eth0="-t 10"
-
-# dhclient, udhcpc and pump don't have many runtime options
-# You can pass options to them in a similar manner to dhcpcd though
-dhclient_eth0="..."
-udhcpc_eth0="..."
-pump_eth0="..."
-
-# GENERIC DHCP OPTIONS
-# Set generic DHCP options like so
-dhcp_eth0="release nodns nontp nonis nogateway nosendhost"
-
-# This tells the dhcp client to release it's lease when it stops, not to
-# overwrite dns, ntp and nis settings, not to set a default route and not to
-# send the current hostname to the dhcp server and when it starts.
-# You can use any combination of the above options - the default is not to
-# use any of them.
-
-#-----------------------------------------------------------------------------
-# For APIPA support, emerge net-misc/iputils or net-analyzer/arping
-
-# APIPA is a module that tries to find a free address in the range
-# 169.254.0.0-169.254.255.255 by arping a random address in that range on the
-# interface. If no reply is found then we assign that address to the interface
-
-# This is only useful for LANs where there is no DHCP server and you don't
-# connect directly to the internet.
-config_eth0=( "dhcp" )
-fallback_eth0=( "apipa" )
-
-#-----------------------------------------------------------------------------
-# ARPING Gateway configuration 
-# and
-# Automatic Private IP Addressing (APIPA)
-# For arpingnet / apipa support, emerge net-misc/iputils or net-analyzer/arping
-#
-# This is a module that tries to find a gateway IP. If it exists then we use
-# that gateways configuration for our own. For the configuration variables
-# simply ensure that each octet is zero padded and the dots are removed.
-# Below is an example.
-#
-gateways_eth0="192.168.0.1 10.0.0.1"
-config_192168000001=( "192.168.0.2/24" )
-routes_192168000001=( "default via 192.168.0.1" )
-dns_servers_192168000001=( "192.168.0.1" )
-config_010000000001=( "10.0.0.254/8" )
-routes_010000000001=( "default via 10.0.0.1" )
-dns_servers_010000000001=( "10.0.0.1" )
-
-# We can also specify a specific MAC address for each gateway if different
-# networks have the same gateway.
-gateways_eth0="192.168.0.1,00:11:22:AA:BB:CC 10.0.0.1,33:44:55:DD:EE:FF"
-config_192168000001_001122AABBCC=( "192.168.0.2/24" )
-routes_192168000001_001122AABBCC=( "default via 192.168.0.1" )
-dns_servers_192168000001_001122AABBCC=( "192.168.0.1" )
-config_010000000001_334455DDEEFF=( "10.0.0.254/8" )
-routes_010000000001_334455DDEEFF=( "default via 10.0.0.1" )
-dns_servers_010000000001_334455DDEEFF=( "10.0.0.1" )
-
-# If we don't find any gateways (or there are none configured) then we try and
-# use APIPA to find a free address in the range 169.254.0.0-169.254.255.255
-# by arping a random address in that range on the interface. If no reply is
-# found then we assign that address to the interface.
-
-# This is only useful for LANs where there is no DHCP server.
-config_eth0=( "arping" )
-
-# or if no DHCP server can be found
-config_eth0=( "dhcp" )
-fallback_eth0=( "arping" )
-
-# NOTE: We default to sleeping for 1 second the first time we attempt an
-# arping to give the interface time to settle on the LAN. This appears to
-# be a good default for most instances, but if not you can alter it here.
-arping_sleep=5
-arping_sleep_lan=7
-
-# NOTE: We default to waiting 3 seconds to get an arping response. You can
-# change the default wait like so.
-arping_wait=3
-arping_wait_lan=2
-
-#-----------------------------------------------------------------------------
-# VLAN (802.1q support)
-# For VLAN support, emerge net-misc/vconfig
-
-# Specify the VLAN numbers for the interface like so
-# Please ensure your VLAN IDs are NOT zero-padded
-vlans_eth0="1 2"
-
-# You may not want to assign an IP to the physical interface, but we still
-# need it up.
-config_eth0=( "null" )
-
-# You can also configure the VLAN - see for vconfig man page for more details
-vconfig_eth0=( "set_name_type VLAN_PLUS_VID_NO_PAD" )
-vconfig_vlan1=( "set_flag 1" "set_egress_map 2 6" )
-config_vlan1=( "172.16.3.1 netmask 255.255.254.0" )
-config_vlan2=( "172.16.2.1 netmask 255.255.254.0" )
-
-# NOTE: Vlans can be configured with a . in their interface names
-# When configuring vlans with this name type, you need to replace . with a _
-config_eth0.1=( "dhcp" ) - does not work
-config_eth0_1=( "dhcp" ) - does work
-
-# NOTE: Vlans are controlled by their physical interface and not per vlan
-# This means you do not need to create init scripts in /etc/init.d for each
-# vlan, you must need to create one for the physical interface.
-# If you wish to control the configuration of each vlan through a separate
-# script, or wish to rename the vlan interface to something that vconfig
-# cannot then you need to do this.
-vlan_start_eth0="no"
-
-# If you do the above then you may want to depend on eth0 like so
- RC_NEED_vlan1="net.eth0"
-# NOTE: depend functions only work in /etc/conf.d/net
-# and not in profile configs such as /etc/conf.d/net.foo
-
-#-----------------------------------------------------------------------------
-# Bonding
-# For link bonding/trunking emerge net-misc/ifenslave
-
-# To bond interfaces together
-slaves_bond0="eth0 eth1 eth2"
-config_bond0=( "null" ) # You may not want to assign an IP to the bond
-
-# If any of the slaves require extra configuration - for example wireless or
-# ppp devices - we need to depend function on the bonded interfaces
-RC_NEED_bond0="net.eth0 net.eth1"
-
-
-#-----------------------------------------------------------------------------
-# Classical IP over ATM 
-# For CLIP support emerge net-dialup/linux-atm
-
-# Ensure that you have /etc/atmsigd.conf setup correctly
-# Now setup each clip interface like so
-clip_atm0=( "peer_ip [if.]vpi.vci [opts]" ... )
-#  where "peer_ip" is the IP address of a PVC peer (in case of an ATM connection
-#  with your ISP, your only peer is usually the ISP gateway closest to you),
-#  "if" is the number of the ATM interface which will carry the PVC, "vpi.vci"
-#  is the ATM VC address, and "opts" may optionally specify VC parameters like
-#  qos, pcr, and the like (see "atmarp -s" for further reference). Please also
-#  note quoting: it is meant to distinguish the VCs you want to create. You may,
-#  in example, create an atm0 interface to more peers, like this:
-clip_atm0=( "1.1.1.254 0.8.35" "1.1.1.253 1.8.35" )
-
-#  By default, the PVC will use the LLC/SNAP encapsulation. If you rather need a
-#  null encapsulation (aka "VC mode"), please add the keyword "null" to opts.
-
-
-#-----------------------------------------------------------------------------
-# PPP
-# For PPP support, emerge net-dialup/ppp
-# PPP is used for most dialup connections, including ADSL.
-# The older ADSL module is documented below, but you are encouraged to try
-# this module first.
-#
-# You need to create the PPP net script yourself. Make it like so
-#ln -s net.lo /etc/init.d/net.ppp0
-#
-# We have to instruct ppp0 to actually use ppp
-config_ppp0=( "ppp" )
-#
-# Each PPP interface requires an interface to use as a "Link"
-link_ppp0="/dev/ttyS0" 		# Most PPP links will use a serial port
-link_ppp0="eth0"       		# PPPoE requires an ethernet interface
-link_ppp0="[itf.]vpi.vci"		# PPPoA requires the ATM VC's address
-link_ppp0="/dev/null"			# ISDN links should have this
-link_ppp0="pty 'your_link_command'"	# PPP links over ssh, rsh, etc
-#
-# Here you should specify what pppd plugins you want to use
-# Available plugins are: pppoe, pppoa, capi, dhcpc, minconn, radius,
-#			 radattr, radrealms and winbind 
-plugins_ppp0=(
-	"pppoe"				# Required plugin for PPPoE
-	"pppoa vc-encaps"		# Required plugin for PPPoA with an option
-	"capi"				# Required plugin for ISDN
-)
-#
-# PPP requires at least a username. You can optionally set a password here too
-# If you don't, then it will use the password specified in /etc/ppp/*-secrets
-# against the specified username
-username_ppp0='user'
-password_ppp0='password'
-# NOTE: You can set a blank password like so
-password_ppp0=
-#
-# The PPP daemon has many options you can specify - although there are many
-# and may seem daunting, it is recommended that you read the pppd man page
-# before enabling any of them
-pppd_ppp0=(
-	"maxfail 0"	# WARNING: It's not recommended you use this
-			# if you don't specify maxfail then we assume 0
-	"updetach" 	# If not set, "/etc/init.d/net.ppp0 start" will return
-			# immediately,  without waiting the link to come up
-			# for the first time.
-			# Do not use it for dial-on-demand links!
-	"debug" 	# Enables syslog debugging
-	"noauth"	# Do not require the peer to authenticate itself
-	"defaultroute"	# Make this PPP interface the default route
-	"usepeerdns"	# Use the DNS settings provided by PPP
-
-# On demand options
-	"demand"		# Enable dial on demand
-	"idle 30"		# Link goes down after 30 seconds of inactivity
-	"10.112.112.112:10.112.112.113"	# Phony IP addresses
-	"ipcp-accept-remote"	# Accept the peers idea of remote address
-	"ipcp-accept-local"	# Accept the peers idea of local address
-	"holdoff 3" 	# Wait 3 seconds after link dies before re-starting
-
-# Dead peer detection
-	"lcp-echo-interval 15"	# Send a LCP echo every 15 seconds
-	"lcp-echo-failure 3"	# Make peer dead after 3 consective
-				# echo-requests
-	
-# Compression options - use these to completely disable compression
-#	noaccomp noccp nobsdcomp nodeflate nopcomp novj novjccomp
-
-# Dial-up settings
-	"lock"				# Lock serial port
-	"115200"			# Set the serial port baud rate
-	"modem crtscts"			# Enable hardware flow control
-	"192.168.0.1:192.168.0.2"	# Local and remote IP addresses
-)
-#
-# Dial-up PPP users need to specify at least one telephone number
-phone_number_ppp0=( "12345689" ) # Maximum 2 phone numbers are supported
-# They will also need a chat script - here's a good one
-chat_ppp0=(
-#	'ABORT' 'BUSY'
-#	'ABORT' 'ERROR'
-#	'ABORT' 'NO ANSWER'
-#	'ABORT' 'NO CARRIER'
-#	'ABORT' 'NO DIALTONE'
-#	'ABORT' 'Invalid Login'
-#	'ABORT' 'Login incorrect'
-#	'TIMEOUT' '5'
-#	'' 'ATZ'
-#	'OK' 'AT' # Put your modem initialization string here
-#	'OK' 'ATDT\T'
-#	'TIMEOUT' '60'
-#	'CONNECT' ''
-#	'TIMEOUT' '5'
-#	'~--' ''
-)
-
-# If the link require extra configuration - for example wireless or
-# RFC 268 bridge - we need to depend on the bridge so they get
-# configured correctly.
-RC_NEED_ppp0="net.nas0"
-
-#WARNING: if MTU of the PPP interface is less than 1500 and you use this
-#machine as a router, you should add the following rule to your firewall
-#
-#iptables -I FORWARD 1 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
-
-#-----------------------------------------------------------------------------
-# ADSL
-# For ADSL support, emerge net-dialup/rp-pppoe
-# WARNING: This ADSL module is being deprecated in favour of the PPP module
-# above.
-# You should make the following settings and also put your
-# username/password information in /etc/ppp/pap-secrets
-
-# Configure the interface to use ADSL
-config_eth0=( "adsl" )
-
-# You probably won't need to edit /etc/ppp/pppoe.conf if you set this
-adsl_user_eth0="my-adsl-username"
-
-#-----------------------------------------------------------------------------
-# ISDN
-# For ISDN support, emerge net-dialup/isdn4k-utils
-# You should make the following settings and also put your
-# username/password information in /etc/ppp/pap-secrets
-
-# Configure the interface to use ISDN
-config_ippp0=( "dhcp" )
-# It's important to specify dhcp if you need it!
-config_ippp0=( "192.168.0.1/24" )
-# Otherwise, you can use a static IP
-
-# NOTE: The interface name must be either ippp or isdn followed by a number
-
-# You may need this option to set the default route
-ipppd_eth0="defaultroute"
-
-#-----------------------------------------------------------------------------
-# MAC changer
-# To set a specific MAC address
-mac_eth0="00:11:22:33:44:55"
-
-# For changing MAC addresses using the below, emerge net-analyzer/macchanger
-# - to randomize the last 3 bytes only
-mac_eth0="random-ending"
-# - to randomize between the same physical type of connection (e.g. fibre,
-# copper, wireless) , all vendors
-mac_eth0="random-samekind"
-# - to randomize between any physical type of connection (e.g. fibre, copper,
-# wireless) , all vendors
-mac_eth0="random-anykind"
-# - full randomization - WARNING: some MAC addresses generated by this may NOT
-# act as expected
-mac_eth0="random-full"
-# custom - passes all parameters directly to net-analyzer/macchanger
-mac_eth0="some custom set of parameters"
-
-# You can also set other options based on the MAC address of your network card
-# Handy if you use different docking stations with laptops
-config_001122334455=( "dhcp" )
-
-#-----------------------------------------------------------------------------
-# TUN/TAP
-# For TUN/TAP support emerge net-misc/openvpn or sys-apps/usermode-utilities
-#
-# You must specify if we're a tun or tap device. Then you can give it any
-# name you like - such as vpn
-tuntap_vpn="tun"
-config_vpn=( "192.168.0.1/24")
-
-# Or stick wit the generic names - like tap0
-tuntap_tap0="tap"
-config_tap0=( "192.168.0.1/24")
-
-# For passing custom options to tunctl use something like the following.  This
-# example sets the owner to adm
-tunctl_tun1="-u adm"
-# When using openvpn, there are no options
-
-#-----------------------------------------------------------------------------
-# Bridging (802.1d)
-# For bridging support emerge net-misc/bridge-utils
-
-# To add ports to bridge br0
-bridge_br0="eth0 eth1"
-# or dynamically add them when the interface comes up
-bridge_add_eth0="br0"
-bridge_add_eth1="br0"
-
-# You need to configure the ports to null values so dhcp does not get started 
-config_eth0=( "null" )
-config_eth1=( "null" )
-
-# Finally give the bridge an address - dhcp or a static IP
-config_br0=( "dhcp" ) # may not work when adding ports dynamically
-config_br0=( "192.168.0.1/24" )
-
-# If any of the ports require extra configuration - for example wireless or
-# ppp devices - we need to depend on them like so.
-RC_NEED_br0="net.eth0 net.eth1"
-
-# Below is an example of configuring the bridge
-# Consult "man brctl" for more details
-brctl_br0=( "setfd 0" "sethello 0" "stp off" )
-
-#-----------------------------------------------------------------------------
-# RFC 2684 Bridge Support 
-# For RFC 2684 bridge support emerge net-misc/br2684ctl
-
-# Interface names have to be of the form nas0, nas1, nas2, etc.
-# You have to specify a VPI and VCI for the interface like so
-br2684ctl_nas0="-a 0.38" # UK VPI and VCI
-
-# You may want to configure the encapsulation method as well by adding the -e
-# option to the command above (may need to be before the -a command)
-# -e 0 # LLC (default)
-# -e 1 # VC mux
-
-# Then you can configure the interface as normal
-config_nas0=( "192.168.0.1/24" )
-
-#-----------------------------------------------------------------------------
-# Tunnelling
-# WARNING: For tunnelling it is highly recommended that you
-# emerge sys-apps/iproute2
-#
-# For GRE tunnels
-iptunnel_vpn0="mode gre remote 207.170.82.1 key 0xffffffff ttl 255"
-
-# For IPIP tunnels
-iptunnel_vpn0="mode ipip remote 207.170.82.2 ttl 255"
-
-# To configure the interface
-config_vpn0=( "192.168.0.2 pointopoint 192.168.1.2" )	# ifconfig style
-config_vpn0=( "192.168.0.2 peer 192.168.1.1" )		# iproute2 style
-
-# 6to4 Tunnels allow IPv6 to work over IPv4 addresses, provided you
-# have a non-private address configured on an interface.
- link_6to4="eth0"		# Interface to base it's addresses on
- config_6to4=( "ip6to4" )
-# You may want to depend on eth0 like so
-RC_NEED_6to4="net.eth0"
-# To ensure that eth0 is configured before 6to4. Of course, the tunnel could be
-# any name and this also works for any configured interface.
-# NOTE: If you're not using iproute2 then your 6to4 tunnel has to be called
-# sit0 - otherwise use a different name like 6to4 in the example above.
-
-
-#-----------------------------------------------------------------------------
-# System
-# For configuring system specifics such as domain, dns, ntp and nis servers
-# It's rare that you would need todo this, but you can anyway.
-# This is most benefit to wireless users who don't use DHCP so they can change
-# their configs based on ESSID. See wireless.example for more details
-
-# To use dns settings such as these, dns_servers_eth0 must be set!
-# If you omit the _eth0 suffix, then it applies to all interfaces unless
-# overridden by the interface suffix.
-dns_domain_eth0="your.domain"
-dns_servers_eth0="192.168.0.2 192.168.0.3"
-dns_search_eth0="this.domain that.domain"
-dns_options_eth0=( "timeout 1" "rotate" )
-dns_sortlist_eth0="130.155.160.0/255.255.240.0 130.155.0.0"
-# See the man page for resolv.conf for details about the options and sortlist
-# directives
-
-ntp_servers_eth0="192.168.0.2 192.168.0.3"
-
-nis_domain_eth0="domain"
-nis_servers_eth0="192.168.0.2 192.168.0.3"
-
-# NOTE: Setting any of these will stamp on the files in question. So if you
-# don't specify dns_servers but you do specify dns_domain then no nameservers
-# will be listed in /etc/resolv.conf even if there were any there to start
-# with.
-# If this is an issue for you then maybe you should look into a resolv.conf
-# manager like resolvconf-gentoo to manage this file for you. All packages
-# that baselayout supports use resolvconf-gentoo if installed.
-
-#-----------------------------------------------------------------------------
-# Cable in/out detection
-# Sometimes the cable is in, others it's out. Obviously you don't want to
-# restart net.eth0 every time when you plug it in either.
-#
-# netplug is a package that detects this and requires no extra configuration
-# on your part.
-# emerge sys-apps/netplug
-# or
-# emerge sys-apps/ifplugd
-# and you're done :)
-
-# By default we don't wait for netplug/ifplugd to configure the interface.
-# If you would like it to wait so that other services now that network is up
-# then you can specify a timeout here.
-plug_timeout="10"
-# A value of 0 means wait forever.
-
-# If you don't want to use netplug on a specific interface but you have it
-# installed, you can disable it for that interface via the modules statement
-modules_eth0=( "!netplug" )
-# You can do the same for ifplugd
-#
-# You can disable them both with the generic plug
-modules_eth0=( "!plug" )
-
-# To use specific ifplugd options, fex specifying wireless mode
-ifplugd_eth0="--api-mode=wlan"
-# man ifplugd for more options
-
-##############################################################################
-# ADVANCED CONFIGURATION
-# 
-# Four functions can be defined which will be called surrounding the
-# start/stop operations.  The functions are called with the interface
-# name first so that one function can control multiple adapters. An extra two
-# functions can be defined when an interface fails to start or stop.
-#
-# The return values for the preup and predown functions should be 0
-# (success) to indicate that configuration or deconfiguration of the
-# interface can continue.  If preup returns a non-zero value, then
-# interface configuration will be aborted.  If predown returns a
-# non-zero value, then the interface will not be allowed to continue
-# deconfiguration.
-#
-# The return values for the postup, postdown, failup and faildown functions are
-# ignored since there's nothing to do if they indicate failure.
-#
-# ${IFACE} is set to the interface being brought up/down
-# ${IFVAR} is ${IFACE} converted to variable name bash allows
-
-#preup() {
-#	# Test for link on the interface prior to bringing it up.  This
-#	# only works on some network adapters and requires the mii-diag
-#	# package to be installed.
-#	if mii-tool "${IFACE}" 2> /dev/null | grep -q 'no link'; then
-#		ewarn "No link on ${IFACE}, aborting configuration"
-#		return 1
-#	fi 
-#
-#	# Test for link on the interface prior to bringing it up.  This
-#	# only works on some network adapters and requires the ethtool
-#	# package to be installed.
-#	if ethtool "${IFACE}" | grep -q 'Link detected: no'; then
-#		ewarn "No link on ${IFACE}, aborting configuration"
-#		return 1
-#	fi
-#
-#
-#	# Remember to return 0 on success
-#	return 0
-#}
-
-#predown() {
-#	# The default in the script is to test for NFS root and disallow
-#	# downing interfaces in that case.  Note that if you specify a
-#	# predown() function you will override that logic.  Here it is, in
-#	# case you still want it...
-#	if is_net_fs /; then
-#		eerror "root filesystem is network mounted -- can't stop ${IFACE}"
-#		return 1
-#	fi
-#
-#	# Remember to return 0 on success
-#	return 0
-#}
-
-#postup() {
-#	# This function could be used, for example, to register with a
-#	# dynamic DNS service.  Another possibility would be to
-#	# send/receive mail once the interface is brought up.
-
-#	# Here is an example that allows the use of iproute rules
-#	# which have been configured using the rules_eth0 variable.
-#	#rules_eth0=(
-#	#	"from 24.80.102.112/32 to 192.168.1.0/24 table localnet priority 100"
-#	#	"from 216.113.223.51/32 to 192.168.1.0/24 table localnet priority 100"
-#	#)
-#	local x="rules_${IFVAR}[@]"
-#	local -a rules=( "${!x}" )
-#	if [[ -n ${rules} ]] ; then
-#		einfo "Adding IP policy routing rules"
-#		eindent
-#		# Ensure that the kernel supports policy routing
-#		if ! ip rule list | grep -q "^" ; then
-#			eerror "You need to enable IP Policy Routing (CONFIG_IP_MULTIPLE_TABLES)"
-#			eerror "in your kernel to use ip rules"
-#		else
-#			for x in "${rules[@]}" ; do
-#				ebegin "${x}"
-#				ip rule add ${x} dev "${IFACE}"
-#				eend $?
-#			done
-#		fi
-#		eoutdent
-#		# Flush the cache
-#		ip route flush cache dev "${IFACE}"
-#	fi
-
-#}
-
-#postdown() {
-#	# Enable Wake-On-LAN for every interface except for lo
-#	# Probably a good idea to set RC_DOWN_INTERFACE="no" in /etc/conf.d/rc
-#	# as well ;)
-#	[[ ${IFACE} != "lo" ]] && ethtool -s "${IFACE}" wol g
-
-#	Automatically erase any ip rules created in the example postup above
-#	if interface_exists "${IFACE}" ; then
-#		# Remove any rules for this interface
-#		local rule
-#		ip rule list | grep " iif ${IFACE}[ ]*" | {
-#			while read rule ; do
-#				rule="${rule#*:}"	
-#				ip rule del ${rule}
-#			done
-#		}
-#		# Flush the route cache
-#		ip route flush cache dev "${IFACE}"
-#	fi
-
-#	# Return 0 always
-#	return 0
-#}
-
-#failup() {
-#       # This function is mostly here for completeness... I haven't
-#       # thought of anything nifty to do with it yet ;-)
-#}
-
-#faildown() {
-#       # This function is mostly here for completeness... I haven't
-#       # thought of anything nifty to do with it yet ;-)
-#}
-
-##############################################################################
-# FORCING MODULES
-# The Big Fat Warning :- If you use module forcing do not complain to us or
-# file bugs about it not working!
-#
-# Loading modules is a slow affair - we have to check each one for the following
-# 1) Code sanity
-# 2) Has the required package been emerged?
-# 3) Has it modified anything?
-# 4) Have all the dependant modules been loaded?
-
-# Then we have to strip out the conflicting modules based on user preference
-# and default configuration and sort them into the correct order.
-# Finally we check the end result for dependencies.
-
-# This, of course, takes valuable CPU time so we provide module forcing as a
-# means to speed things up. We still do *some* checking but not much.
-
-# It is essential that you force modules in the correct order and supply all
-# the modules you need. You must always supply an interface module - we
-# supply ifconfig or iproute2.
-
-# The Big Fat Warning :- If you use module forcing do not complain to us or
-# file bugs about it not working!
-
-# Now that we've warned you twice, here's how to do it
-modules_force=( "ifconfig" )
-modules_force=( "iproute2" "dhcpcd" )
-
-# We can also apply this to a specific interface
-modules_force_eth1=( "iproute2" )
-
-# The below will not work
-modules_force=( "dhcpcd" )
-# No interface (ifconfig/iproute2)
-modules_force=( "ifconfig" "essidnet" "iwconfig" )
-# Although it will not crash, essidnet will not work as it has to come after
-# iwconfig
-modules_force=( "iproute2" "ifconfig" )
-# The interface will be setup twice which will cause problems
diff --git a/src/settings/plugins/ifnet/tests/nm-system-settings.conf b/src/settings/plugins/ifnet/tests/nm-system-settings.conf
deleted file mode 100644
index 39bc87b8..00000000
--- a/src/settings/plugins/ifnet/tests/nm-system-settings.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-[main]
-plugins=ifnet,keyfile
-
-[ifnet]
-managed=false
diff --git a/src/settings/plugins/ifnet/tests/test-ifnet.c b/src/settings/plugins/ifnet/tests/test-ifnet.c
deleted file mode 100644
index 68f2b928..00000000
--- a/src/settings/plugins/ifnet/tests/test-ifnet.c
+++ /dev/null
@@ -1,396 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service (ifnet)
- *
- * Mu Qiao <qiaomuf@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 1999-2010 Gentoo Foundation, Inc.
- */
-
-#include "nm-default.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <arpa/inet.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include "nm-utils.h"
-
-#include "platform/nm-linux-platform.h"
-#include "dhcp/nm-dhcp-manager.h"
-
-#include "settings/plugins/ifnet/nms-ifnet-net-parser.h"
-#include "settings/plugins/ifnet/nms-ifnet-net-utils.h"
-#include "settings/plugins/ifnet/nms-ifnet-wpa-parser.h"
-#include "settings/plugins/ifnet/nms-ifnet-connection-parser.h"
-
-#include "nm-test-utils-core.h"
-
-/* Fake config handling; the values it returns don't matter, so this
- * is easier than forcing it to read our own config file, etc.
- */
-NMDhcpManager *
-nm_dhcp_manager_get (void)
-{
-	return NULL;
-}
-
-const char *
-nm_dhcp_manager_get_config (NMDhcpManager *dhcp_manager)
-{
-	return "dhclient";
-}
-
-static void
-test_getdata (void)
-{
-	g_assert (ifnet_get_data ("eth1", "config") &&
-	          strcmp (ifnet_get_data ("eth1", "config"), "( \"dhcp\" )") == 0);
-	g_assert (ifnet_get_data ("ppp0", "username") &&
-	          strcmp (ifnet_get_data ("ppp0", "username"), "user") == 0);
-	g_assert (ifnet_get_data ("ppp0", "password") &&
-	          strcmp (ifnet_get_data ("ppp0", "password"), "password") == 0);
-	g_assert (ifnet_get_global_data ("modules") &&
-	          strcmp ("!wpa_supplicant", ifnet_get_global_data ("modules")) == 0);
-}
-
-static void
-test_is_static (void)
-{
-	g_assert (!is_static_ip4 ("eth1"));
-	g_assert (is_static_ip4 ("eth0"));
-	g_assert (!is_static_ip6 ("eth0"));
-}
-
-static void
-test_has_default_route (void)
-{
-	g_assert (has_default_ip4_route ("eth0"));
-	g_assert (has_default_ip6_route ("eth4"));
-	g_assert (!has_default_ip4_route ("eth5") &&
-	          !has_default_ip6_route ("eth5"));
-}
-
-static void
-test_has_ip6_address (void)
-{
-	g_assert (has_ip6_address ("eth2"));
-	g_assert (!has_ip6_address ("eth0"));
-}
-
-static void
-test_is_ip4_address (void)
-{
-	gchar *address1 = "192.168.4.232/24";
-	gchar *address2 = "192.168.100.{1..254}/24";
-	gchar *address3 = "192.168.4.2555/24";
-
-	g_assert (is_ip4_address (address1));
-	g_assert (is_ip4_address (address2));
-	g_assert (!is_ip4_address (address3));
-}
-
-static void
-test_is_ip6_address (void)
-{
-	gchar *address1 = "4321:0:1:2:3:4:567:89ac/24";
-
-	g_assert (is_ip6_address (address1));
-}
-
-static void
-check_ip_block (ip_block * iblock, gchar * ip, guint32 prefix, gchar * gateway)
-{
-	g_assert_cmpstr (ip, ==, iblock->ip);
-	g_assert (prefix == iblock->prefix);
-	g_assert_cmpstr (gateway, ==, iblock->next_hop);
-}
-
-static void
-test_convert_ipv4_config_block (void)
-{
-	ip_block *iblock = convert_ip4_config_block ("eth0");
-	ip_block *tmp = iblock;
-
-	g_assert (iblock != NULL);
-	check_ip_block (iblock, "202.117.16.121", 24, "202.117.16.1");
-	iblock = iblock->next;
-	destroy_ip_block (tmp);
-	g_assert (iblock != NULL);
-	check_ip_block (iblock, "192.168.4.121", 24, "202.117.16.1");
-	destroy_ip_block (iblock);
-
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*Can't handle IPv4 address*202.117.16.1211*");
-	iblock = convert_ip4_config_block ("eth2");
-	g_test_assert_expected_messages ();
-	g_assert (iblock != NULL && iblock->next == NULL);
-	check_ip_block (iblock, "192.168.4.121", 24, NULL);
-	destroy_ip_block (iblock);
-
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*missing netmask or prefix*");
-	iblock = convert_ip4_config_block ("eth3");
-	g_assert (iblock == NULL);
-}
-
-static void
-test_convert_ipv4_routes_block (void)
-{
-	ip_block *iblock = convert_ip4_routes_block ("eth0");
-	ip_block *tmp = iblock;
-
-	g_assert (iblock != NULL);
-	check_ip_block (iblock, "192.168.4.0", 24, "192.168.4.1");
-	iblock = iblock->next;
-	destroy_ip_block (tmp);
-	g_assert (iblock == NULL);
-
-	iblock = convert_ip4_routes_block ("eth9");
-	tmp = iblock;
-
-	g_assert (iblock != NULL);
-	check_ip_block (iblock, "10.0.0.0", 8, "192.168.0.1");
-	iblock = iblock->next;
-	destroy_ip_block (tmp);
-	g_assert (iblock == NULL);
-}
-
-static void
-test_wpa_parser (void)
-{
-	const char *value;
-
-	g_assert (exist_ssid ("example"));
-
-	g_assert (exist_ssid ("static-wep-test"));
-	value = wpa_get_value ("static-wep-test", "key_mgmt");
-	g_assert_cmpstr (value, ==, "NONE");
-	value = wpa_get_value ("static-wep-test", "wep_key0");
-	g_assert_cmpstr (value, ==, "\"abcde\"");
-
-	g_assert (exist_ssid ("leap-example"));
-
-	value = wpa_get_value ("test-with-hash-in-psk", "psk");
-	g_assert_cmpstr (value, ==, "\"xjtudlc3731###asdfasdfasdf\"");
-}
-
-static void
-test_strip_string (void)
-{
-	gchar *str = "( \"default via     202.117.16.1\" )";
-	gchar *result = g_strdup (str);
-	gchar *result_b = result;
-
-	result = strip_string (result, '(');
-	result = strip_string (result, ')');
-	result = strip_string (result, '"');
-	g_assert_cmpstr (result, ==, "default via     202.117.16.1");
-	g_free (result_b);
-}
-
-static void
-test_is_unmanaged (void)
-{
-	g_assert (is_managed ("eth0"));
-	g_assert (!is_managed ("eth4"));
-}
-
-static void
-test_new_connection (void)
-{
-	GError *error = NULL;
-	NMConnection *connection;
-
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*Can't handle IPv4 address*202.117.16.1211*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*Can't handle IPv6 address*202.117.16.1211*");
-	connection = ifnet_update_connection_from_config_block ("eth2", NULL, &error);
-	g_test_assert_expected_messages ();
-	g_assert (connection != NULL);
-	g_object_unref (connection);
-
-	connection = ifnet_update_connection_from_config_block ("qiaomuf", NULL, &error);
-	g_assert (connection != NULL);
-	g_object_unref (connection);
-
-	connection = ifnet_update_connection_from_config_block ("myxjtu2", NULL, &error);
-	g_assert (connection != NULL);
-	g_object_unref (connection);
-
-	connection = ifnet_update_connection_from_config_block ("eth9", NULL, &error);
-	g_assert (connection != NULL);
-	g_object_unref (connection);
-
-	connection = ifnet_update_connection_from_config_block ("eth10", NULL, &error);
-	g_assert (connection != NULL);
-	g_object_unref (connection);
-}
-
-static void
-kill_backup (char **path)
-{
-	if (*path) {
-		unlink (*path);
-		g_free (*path);
-		*path = NULL;
-	}
-}
-
-#define NET_GEN_NAME "net.generate"
-#define SUP_GEN_NAME "wpa_supplicant.conf.generate"
-
-static void
-test_update_connection (void)
-{
-	GError *error = NULL;
-	NMConnection *connection;
-	gboolean success;
-	char *backup = NULL;
-	char *basepath = TEST_IFNET_DIR;
-
-	connection = ifnet_update_connection_from_config_block ("eth0", basepath, &error);
-	g_assert (connection != NULL);
-
-	success = ifnet_update_parsers_by_connection (connection, "eth0",
-	                                              NET_GEN_NAME,
-	                                              SUP_GEN_NAME,
-	                                              NULL,
-	                                              &backup,
-	                                              &error);
-	kill_backup (&backup);
-	g_assert (success);
-	g_object_unref (connection);
-
-	connection = ifnet_update_connection_from_config_block ("0xab3ace", basepath, &error);
-	g_assert (connection != NULL);
-
-	success = ifnet_update_parsers_by_connection (connection, "0xab3ace",
-	                                              NET_GEN_NAME,
-	                                              SUP_GEN_NAME,
-	                                              NULL,
-	                                              &backup,
-	                                              &error);
-	kill_backup (&backup);
-	g_assert (success);
-	g_object_unref (connection);
-
-	unlink (NET_GEN_NAME);
-	unlink (SUP_GEN_NAME);
-}
-
-static void
-test_add_connection (void)
-{
-	NMConnection *connection;
-	char *backup = NULL;
-	const char *basepath = TEST_IFNET_DIR;
-
-	connection = ifnet_update_connection_from_config_block ("eth0", basepath, NULL);
-	g_assert (ifnet_add_new_connection (connection, NET_GEN_NAME, SUP_GEN_NAME, NULL, &backup, NULL));
-	kill_backup (&backup);
-	g_object_unref (connection);
-
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*Can't handle ipv4 address: brd, missing netmask or prefix*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*Can't handle ipv4 address: 202.117.16.255, missing netmask or prefix*");
-	connection = ifnet_update_connection_from_config_block ("myxjtu2", basepath, NULL);
-	g_test_assert_expected_messages ();
-	g_assert (ifnet_add_new_connection (connection, NET_GEN_NAME, SUP_GEN_NAME, NULL, &backup, NULL));
-	kill_backup (&backup);
-	g_object_unref (connection);
-
-	unlink (NET_GEN_NAME);
-	unlink (SUP_GEN_NAME);
-}
-
-static void
-test_delete_connection (void)
-{
-	GError *error = NULL;
-	NMConnection *connection;
-	char *backup = NULL;
-
-	connection = ifnet_update_connection_from_config_block ("eth7", NULL, &error);
-	g_assert (connection != NULL);
-	g_assert (ifnet_delete_connection_in_parsers ("eth7", NET_GEN_NAME, SUP_GEN_NAME, &backup));
-	kill_backup (&backup);
-	g_object_unref (connection);
-
-	connection = ifnet_update_connection_from_config_block ("qiaomuf", NULL, &error);
-	g_assert (connection != NULL);
-	g_assert (ifnet_delete_connection_in_parsers ("qiaomuf", NET_GEN_NAME, SUP_GEN_NAME, &backup));
-	kill_backup (&backup);
-	g_object_unref (connection);
-
-	unlink (NET_GEN_NAME);
-	unlink (SUP_GEN_NAME);
-}
-
-static void
-test_missing_config (void)
-{
-	gs_free_error GError *error = NULL;
-	NMConnection *connection;
-
-	connection = ifnet_update_connection_from_config_block ("eth8", NULL, &error);
-	g_assert_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
-	g_assert (connection == NULL && error != NULL);
-}
-
-NMTST_DEFINE ();
-
-#define TPATH "/settings/plugins/ifnet/"
-
-int
-main (int argc, char **argv)
-{
-	int ret;
-
-	nm_linux_platform_setup ();
-
-	nmtst_init_assert_logging (&argc, &argv, "WARN", "DEFAULT");
-
-	ifnet_init (TEST_IFNET_DIR "/net");
-	wpa_parser_init (TEST_IFNET_DIR "/wpa_supplicant.conf");
-
-	g_test_add_func (TPATH "strip-string", test_strip_string);
-	g_test_add_func (TPATH "is-static", test_is_static);
-	g_test_add_func (TPATH "has-ip6-address", test_has_ip6_address);
-	g_test_add_func (TPATH "has-default-route", test_has_default_route);
-	g_test_add_func (TPATH "get-data", test_getdata);
-	g_test_add_func (TPATH "is-ip4-address", test_is_ip4_address);
-	g_test_add_func (TPATH "is-ip6-address", test_is_ip6_address);
-	g_test_add_func (TPATH "convert-ip4-config", test_convert_ipv4_config_block);
-	g_test_add_func (TPATH "convert-ip4-routes", test_convert_ipv4_routes_block);
-	g_test_add_func (TPATH "is-unmanaged", test_is_unmanaged);
-	g_test_add_func (TPATH "wpa-parser", test_wpa_parser);
-	g_test_add_func (TPATH "new-connection", test_new_connection);
-	g_test_add_func (TPATH "update-connection", test_update_connection);
-	g_test_add_func (TPATH "add-connection", test_add_connection);
-	g_test_add_func (TPATH "delete-connection", test_delete_connection);
-	g_test_add_func (TPATH "missing-config", test_missing_config);
-
-	ret = g_test_run ();
-
-	ifnet_destroy ();
-	wpa_parser_destroy ();
-
-	return ret;
-}
diff --git a/src/settings/plugins/ifnet/tests/test_ca_cert.pem b/src/settings/plugins/ifnet/tests/test_ca_cert.pem
deleted file mode 100644
index ef1be20d..00000000
--- a/src/settings/plugins/ifnet/tests/test_ca_cert.pem
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIEjzCCA3egAwIBAgIJAOvnZPt59yIZMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD
-VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw
-FQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UE
-AxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAzMTAx
-NTEyMTRaFw0xOTAzMDgxNTEyMTRaMIGLMQswCQYDVQQGEwJVUzESMBAGA1UECBMJ
-QmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55
-IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3
-DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
-ggEBAKot9j+/+CX1/gZLgJHIXCRgCItKLGnf7qGbgqB9T2ACBqR0jllKWwDKrcWU
-xjXNIc+GF9Wnv+lX6G0Okn4Zt3/uRNobL+2b/yOF7M3Td3/9W873zdkQQX930YZc
-Rr8uxdRPP5bxiCgtcw632y21sSEbG9mjccAUnV/0jdvfmMNj0i8gN6E0fMBiJ9S3
-FkxX/KFvt9JWE9CtoyL7ki7UIDq+6vj7Gd5N0B3dOa1y+rRHZzKlJPcSXQSEYUS4
-HmKDwiKSVahft8c4tDn7KPi0vex91hlgZVd3usL2E/Vq7o5D9FAZ5kZY0AdFXwdm
-J4lO4Mj7ac7GE4vNERNcXVIX59sCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUuDU3Mr7P
-T3n1e3Sy8hBauoDFahAwgcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDF
-ahChgZGkgY4wgYsxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO
-BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQL
-EwdUZXN0aW5nMQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRl
-c3QuY29tggkA6+dk+3n3IhkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC
-AQEAVRG4aALIvCXCiKfe7K+iJxjBVRDFPEf7JWA9LGgbFOn6pNvbxonrR+0BETdc
-JV1ET4ct2xsE7QNFIkp9GKRC+6J32zCo8qtLCD5+v436r8TUG2/t2JRMkb9I2XVT
-p7RJoot6M0Ltf8KNQUPYh756xmKZ4USfQUwc58MOSDGY8VWEXJOYij9Pf0e0c52t
-qiCEjXH7uXiS8Pgq9TYm7AkWSOrglYhSa83x0f8mtT8Q15nBESIHZ6o8FAS2bBgn
-B0BkrKRjtBUkuJG3vTox+bYINh2Gxi1JZHWSV1tN5z3hd4VFcKqanW5OgQwToBqp
-3nniskIjbH0xjgZf/nVMyLnjxg==
------END CERTIFICATE-----
diff --git a/src/settings/plugins/ifnet/tests/wpa_supplicant.conf b/src/settings/plugins/ifnet/tests/wpa_supplicant.conf
deleted file mode 100644
index 917d495d..00000000
--- a/src/settings/plugins/ifnet/tests/wpa_supplicant.conf
+++ /dev/null
@@ -1,70 +0,0 @@
-# Only WPA-PSK is used. Any valid cipher combination is accepted.
-network={
-	ssid="example"
-	proto=WPA
-	key_mgmt=WPA-PSK
-	pairwise=CCMP TKIP
-	group=CCMP TKIP WEP104 WEP40
-	psk=06b4be19da289f475aa46a33cb793029d4ab3db7a23ee92382eb0106c72ac7bb
-	priority=2
-}
-
-# LEAP with dynamic WEP keys
-network={
-	ssid="leap-example"
-	key_mgmt=IEEE8021X
-	eap=LEAP
-	identity="user"
-	password="foobar"
-}
-
-# Shared WEP key connection (no WPA, no IEEE 802.1X)
-network={
-	ssid="static-wep-test"
-	key_mgmt=NONE
-	wep_key0="abcde"
-	wep_key1=0102030405
-	wep_key2="1234567890123"
-	wep_tx_keyidx=0
-	priority=5
-}
-
-# Wildcard match for SSID (plaintext APs only). This example select any
-# open AP regardless of its SSID.
-network={
-	key_mgmt=NONE
-}
-
-network={
-  ssid="myxjtu2"
-  scan_ssid=1
-  key_mgmt=WPA-PSK
-  psk="xjtudlc3731"
-  disabled=0
-  key_mgmt=NONE
-  wep_key0="12345"
-  wep_key1=1234567890
-  wep_key2="zxcvb"
-  wep_tx_keyidx=1
-  auth_alg=OPEN
-  mode=1
-}
-
-network={
-	ssid=ab3ace
-	key_mgmt=WPA-EAP
-	eap=TTLS
-	identity="user@example.com"
-	anonymous_identity="anonymous@example.com"
-	password="foobar"
-	ca_cert="test_ca_cert.pem"
-	phase2="auth=CHAP"
-	priority=20
-}
-
-network={
-  ssid="test-with-hash-in-psk"
-  key_mgmt=WPA-PSK
-  psk="xjtudlc3731###asdfasdfasdf"
-}
-
diff --git a/src/settings/plugins/ifupdown/meson.build b/src/settings/plugins/ifupdown/meson.build
new file mode 100644
index 00000000..87b5bea2
--- /dev/null
+++ b/src/settings/plugins/ifupdown/meson.build
@@ -0,0 +1,53 @@
+sources = files(
+  'nms-ifupdown-interface-parser.c',
+  'nms-ifupdown-parser.c'
+)
+
+deps = [
+  libudev_dep,
+  nm_dep
+]
+
+cflags = '-DSYSCONFDIR="@0@"'.format(nm_sysconfdir)
+
+libnms_ifupdown_core = static_library(
+  'nms-ifupdown-core',
+  sources: sources,
+  dependencies: deps,
+  c_args: cflags
+)
+
+sources = files(
+  'nms-ifupdown-connection.c',
+  'nms-ifupdown-plugin.c'
+)
+
+libnm_settings_plugin_ifupdown = shared_module(
+  'nm-settings-plugin-ifupdown',
+  sources: sources,
+  dependencies: deps,
+  c_args: cflags,
+  link_with: libnms_ifupdown_core,
+  link_args: ldflags_linker_script_settings,
+  link_depends: linker_script_settings,
+  install: true,
+  install_dir: nm_pkglibdir
+)
+
+core_plugins += libnm_settings_plugin_ifupdown
+
+# FIXME: check_so_symbols replacement
+'''
+run_target(
+  'check-local-symbols-settings-ifupdown',
+  command: [check_so_symbols, libnm_settings_plugin_ifupdown.full_path()],
+  depends: libnm_settings_plugin_ifupdown
+)
+
+check-local-symbols-settings-ifupdown: src/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la
+  $(call check_so_symbols,$(builddir)/src/settings/plugins/ifupdown/.libs/libnm-settings-plugin-ifupdown.so)
+'''
+
+if enable_tests
+  subdir('tests')
+endif
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
index 8421afc2..014998a6 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
@@ -129,8 +129,8 @@ _recursive_ifparser (const char *eni_file, int quiet)
 
 	while (!feof(inp))
 	{
-		char *token[128];	/* 255 chars can only be split into 127 tokens */
-		char value[255];	/* large enough to join previously split tokens */
+		char *token[128]; /* 255 chars can only be split into 127 tokens */
+		char value[255];  /* large enough to join previously split tokens */
 		char *safeptr;
 		int toknum;
 		int len = 0;
@@ -169,7 +169,7 @@ _recursive_ifparser (const char *eni_file, int quiet)
 			continue;
 		}
 
-#define SPACES	" \t"
+#define SPACES " \t"
 		/* tokenize input; */
 		for (toknum = 0, token[toknum] = strtok_r(line, SPACES, &safeptr);
 		     token[toknum] != NULL;
diff --git a/src/settings/plugins/ifupdown/tests/meson.build b/src/settings/plugins/ifupdown/tests/meson.build
new file mode 100644
index 00000000..7f210034
--- /dev/null
+++ b/src/settings/plugins/ifupdown/tests/meson.build
@@ -0,0 +1,17 @@
+test_unit = 'test-ifupdown'
+
+cflags = '-DTEST_ENI_DIR="@0@"'.format(meson.current_source_dir())
+
+exe = executable(
+  test_unit,
+  test_unit + '.c',
+  dependencies: test_nm_dep,
+  c_args: cflags,
+  link_with: libnms_ifupdown_core
+)
+
+test(
+  'ifupdown/' + test_unit,
+  test_script,
+  args: test_args + [exe.full_path()]
+)
diff --git a/src/settings/plugins/ifupdown/tests/test-ifupdown.c b/src/settings/plugins/ifupdown/tests/test-ifupdown.c
index d037b8a6..56515438 100644
--- a/src/settings/plugins/ifupdown/tests/test-ifupdown.c
+++ b/src/settings/plugins/ifupdown/tests/test-ifupdown.c
@@ -168,7 +168,7 @@ dump_blocks (void)
 	for (n = ifparser_getfirst (); n != NULL; n = n->next) {
 		if_data *m;
 
-		// each block start with its type & name 
+		// each block start with its type & name
 		// (single quotes used to show typ & name baoundaries)
 		g_print("'%s' '%s'\n", n->type, n->name);
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index e6299d1e..df2c05b7 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -436,7 +436,7 @@ read_connections (NMSettingsPlugin *config)
 		return;
 	}
 
-	alive_connections = g_hash_table_new (NULL, NULL);
+	alive_connections = g_hash_table_new (nm_direct_hash, NULL);
 
 	filenames = g_ptr_array_new_with_free_func (g_free);
 	while ((item = g_dir_read_name (dir))) {
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection b/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
index 5cb4d726..1e62f4b3 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
+++ b/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
@@ -15,7 +15,7 @@ mtu=1400
 
 [ipv4]
 method=manual
-dns=4.2.2.1;4.2.2.2;
+dns=4.2.2.1;bogus;4.2.2.2;
 addresses1=192.168.0.5;24;192.168.0.1;
 addresses2=1.2.3.4;16;1.2.1.1;
 address=2.3.4.5/24,2.3.4.6
@@ -26,15 +26,21 @@ routes1=1.2.3.0/24,2.3.4.8,99
 route=5.6.7.8/32
 routes2=1.1.1.2/12,
 routes3=1.1.1.3/13,,
+routes7=1.1.1.7/17,0.0.0.0
 routes4=1.1.1.4/14,2.2.2.4
+address30=1.2.3.130/24
 routes5=1.1.1.5/15,2.2.2.5,
 routes6=1.1.1.6/16,2.2.2.6,0
-routes7=1.1.1.7/17,0.0.0.0
 routes8=1.1.1.8/18,0.0.0.0,
 routes9=1.1.1.9/19,0.0.0.0,0
+route10=1.1.1.10/21,,0
 routes10=1.1.1.10/20,,0
 routes11=1.1.1.11/21,,21
 routes11_options=cwnd=10,lock-cwnd=true,mtu=1430,src=7.7.7.7
+address30=1.2.3.30/24
+addresses30=1.2.3.30/25
+addresses31=1.2.3.31/25
+address31=1.2.3.31/24
 ignore-auto-routes=false
 ignore-auto-dns=false
 
diff --git a/src/settings/plugins/keyfile/tests/meson.build b/src/settings/plugins/keyfile/tests/meson.build
new file mode 100644
index 00000000..54b4ee0d
--- /dev/null
+++ b/src/settings/plugins/keyfile/tests/meson.build
@@ -0,0 +1,21 @@
+test_unit = 'test-keyfile'
+
+test_keyfiles_dir = join_paths(meson.current_source_dir(), 'keyfiles')
+
+cflags = [
+  '-DTEST_KEYFILES_DIR="@0@"'.format(test_keyfiles_dir),
+  '-DTEST_SCRATCH_DIR="@0@"'.format(test_keyfiles_dir)
+]
+
+exe = executable(
+  test_unit,
+  test_unit + '.c',
+  dependencies: test_nm_dep,
+  c_args: cflags
+)
+
+test(
+  'keyfile/' + test_unit,
+  test_script,
+  args: test_args + [exe.full_path()]
+)
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile.c b/src/settings/plugins/keyfile/tests/test-keyfile.c
index f27efddd..b46475e8 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile.c
@@ -231,34 +231,21 @@ test_read_valid_wired_connection (void)
 	char expected_mac_address[ETH_ALEN] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 };
 	gboolean success;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv4.addresses:*semicolon at the end*addresses1*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv4.addresses:*semicolon at the end*addresses2*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*missing prefix length*address4*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*missing prefix length*address5*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv4.routes*semicolon at the end*routes2*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv4.routes*semicolon at the end*routes3*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv4.routes*semicolon at the end*routes5*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv4.routes*semicolon at the end*routes8*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*missing prefix length*address4*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv6.address*semicolon at the end*address5*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*missing prefix length*address5*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv6.address*semicolon at the end*address7*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv6.routes*semicolon at the end*routes1*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv6.route*semicolon at the end*route6*");
+	NMTST_EXPECT_NM_INFO ("*ipv4.addresses:*semicolon at the end*addresses1*");
+	NMTST_EXPECT_NM_INFO ("*ipv4.addresses:*semicolon at the end*addresses2*");
+	NMTST_EXPECT_NM_WARN ("*missing prefix length*address4*");
+	NMTST_EXPECT_NM_WARN ("*missing prefix length*address5*");
+	NMTST_EXPECT_NM_WARN ("*ipv4.dns: ignoring invalid DNS server IPv4 address 'bogus'*");
+	NMTST_EXPECT_NM_INFO ("*ipv4.routes*semicolon at the end*routes2*");
+	NMTST_EXPECT_NM_INFO ("*ipv4.routes*semicolon at the end*routes3*");
+	NMTST_EXPECT_NM_INFO ("*ipv4.routes*semicolon at the end*routes5*");
+	NMTST_EXPECT_NM_INFO ("*ipv4.routes*semicolon at the end*routes8*");
+	NMTST_EXPECT_NM_WARN ("*missing prefix length*address4*");
+	NMTST_EXPECT_NM_INFO ("*ipv6.address*semicolon at the end*address5*");
+	NMTST_EXPECT_NM_WARN ("*missing prefix length*address5*");
+	NMTST_EXPECT_NM_INFO ("*ipv6.address*semicolon at the end*address7*");
+	NMTST_EXPECT_NM_INFO ("*ipv6.routes*semicolon at the end*routes1*");
+	NMTST_EXPECT_NM_INFO ("*ipv6.route*semicolon at the end*route6*");
 	connection = nms_keyfile_reader_from_file (TEST_KEYFILES_DIR "/Test_Wired_Connection", &error);
 	g_assert_no_error (error);
 	g_test_assert_expected_messages ();
@@ -294,19 +281,23 @@ test_read_valid_wired_connection (void)
 	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip4, 1), ==, "4.2.2.2");
 
 	/* IPv4 addresses */
-	g_assert_cmpint (nm_setting_ip_config_get_num_addresses (s_ip4), ==, 6);
+	g_assert_cmpint (nm_setting_ip_config_get_num_addresses (s_ip4), ==, 10);
 	check_ip_address (s_ip4, 0, "2.3.4.5", 24);
 	check_ip_address (s_ip4, 1, "192.168.0.5", 24);
 	check_ip_address (s_ip4, 2, "1.2.3.4", 16);
 	check_ip_address (s_ip4, 3, "3.4.5.6", 16);
 	check_ip_address (s_ip4, 4, "4.5.6.7", 24);
 	check_ip_address (s_ip4, 5, "5.6.7.8", 24);
+	check_ip_address (s_ip4, 6, "1.2.3.30", 24);
+	check_ip_address (s_ip4, 7, "1.2.3.30", 25);
+	check_ip_address (s_ip4, 8, "1.2.3.31", 24);
+	check_ip_address (s_ip4, 9, "1.2.3.31", 25);
 
 	/* IPv4 gateway */
 	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, "2.3.4.6");
 
 	/* IPv4 routes */
-	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 12);
+	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 13);
 	check_ip_route (s_ip4, 0, "5.6.7.8", 32, NULL, -1);
 	check_ip_route (s_ip4, 1, "1.2.3.0", 24, "2.3.4.8", 99);
 	check_ip_route (s_ip4, 2, "1.1.1.2", 12, NULL, -1);
@@ -317,11 +308,12 @@ test_read_valid_wired_connection (void)
 	check_ip_route (s_ip4, 7, "1.1.1.7", 17, NULL, -1);
 	check_ip_route (s_ip4, 8, "1.1.1.8", 18, NULL, -1);
 	check_ip_route (s_ip4, 9, "1.1.1.9", 19, NULL, 0);
-	check_ip_route (s_ip4, 10, "1.1.1.10", 20, NULL, 0);
-	check_ip_route (s_ip4, 11, "1.1.1.11", 21, NULL, 21);
+	check_ip_route (s_ip4, 10, "1.1.1.10", 21, NULL, 0);
+	check_ip_route (s_ip4, 11, "1.1.1.10", 20, NULL, 0);
+	check_ip_route (s_ip4, 12, "1.1.1.11", 21, NULL, 21);
 
 	/* Route attributes */
-	route = nm_setting_ip_config_get_route (s_ip4, 11);
+	route = nm_setting_ip_config_get_route (s_ip4, 12);
 	g_assert (route);
 
 	nmtst_assert_route_attribute_uint32  (route, NM_IP_ROUTE_ATTRIBUTE_CWND, 10);
@@ -649,12 +641,9 @@ test_read_wired_mac_case (void)
 	char expected_mac_address[ETH_ALEN] = { 0x00, 0x11, 0xaa, 0xbb, 0xcc, 0x55 };
 	gboolean success;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv4.addresses*semicolon at the end*addresses1*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv4.addresses*semicolon at the end*addresses2*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*ipv6.routes*semicolon at the end*routes1*");
+	NMTST_EXPECT_NM_INFO ("*ipv4.addresses*semicolon at the end*addresses1*");
+	NMTST_EXPECT_NM_INFO ("*ipv4.addresses*semicolon at the end*addresses2*");
+	NMTST_EXPECT_NM_INFO ("*ipv6.routes*semicolon at the end*routes1*");
 	connection = nms_keyfile_reader_from_file (TEST_KEYFILES_DIR "/Test_Wired_Connection_MAC_Case", NULL);
 	g_test_assert_expected_messages ();
 	g_assert (connection);
@@ -1415,10 +1404,8 @@ test_read_wired_8021x_tls_blob_connection (void)
 	gboolean success;
 	GBytes *blob;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*<warn> * keyfile: 802-1x.client-cert: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*<warn> * keyfile: 802-1x.private-key: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+	NMTST_EXPECT_NM_WARN ("keyfile: 802-1x.client-cert: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+	NMTST_EXPECT_NM_WARN ("keyfile: 802-1x.private-key: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
 	connection = nms_keyfile_reader_from_file (TEST_KEYFILES_DIR "/Test_Wired_TLS_Blob", &error);
 	g_assert_no_error (error);
 	g_assert (connection);
@@ -1447,8 +1434,7 @@ test_read_wired_8021x_tls_blob_connection (void)
 	g_assert_cmpint (nm_setting_802_1x_get_ca_cert_scheme (s_8021x), ==, NM_SETTING_802_1X_CK_SCHEME_BLOB);
 
 	/* Make sure it's not a path, since it's a blob */
-	g_test_expect_message ("libnm", G_LOG_LEVEL_CRITICAL,
-	                       NMTST_G_RETURN_MSG (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH));
+	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH));
 	tmp = nm_setting_802_1x_get_ca_cert_path (s_8021x);
 	g_test_assert_expected_messages ();
 	g_assert (tmp == NULL);
@@ -1476,8 +1462,7 @@ test_read_wired_8021x_tls_bad_path_connection (void)
 	char *tmp2;
 	gboolean success;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*does not exist*");
+	NMTST_EXPECT_NM_WARN ("*does not exist*");
 	connection = nms_keyfile_reader_from_file (TEST_KEYFILES_DIR "/Test_Wired_TLS_Path_Missing", &error);
 	g_test_assert_expected_messages ();
 	g_assert_no_error (error);
@@ -1530,12 +1515,9 @@ test_read_wired_8021x_tls_old_connection (void)
 	const char *tmp;
 	gboolean success;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*<warn> * keyfile: 802-1x.ca-cert: certificate or key file '/CASA/dcbw/Desktop/certinfra/CA/eaptest_ca_cert.pem' does not exist*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*<warn> * keyfile: 802-1x.client-cert: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*<warn> * keyfile: 802-1x.private-key: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+	NMTST_EXPECT_NM_WARN ("keyfile: 802-1x.ca-cert: certificate or key file '/CASA/dcbw/Desktop/certinfra/CA/eaptest_ca_cert.pem' does not exist*");
+	NMTST_EXPECT_NM_WARN ("keyfile: 802-1x.client-cert: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+	NMTST_EXPECT_NM_WARN ("keyfile: 802-1x.private-key: certificate or key file '/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
 	connection = nms_keyfile_reader_from_file (TEST_KEYFILES_DIR "/Test_Wired_TLS_Old", &error);
 	g_assert_no_error (error);
 	g_assert (connection);
diff --git a/src/settings/plugins/meson.build b/src/settings/plugins/meson.build
new file mode 100644
index 00000000..a1aa7823
--- /dev/null
+++ b/src/settings/plugins/meson.build
@@ -0,0 +1,15 @@
+if enable_ibft
+  subdir('ibft')
+endif
+
+if enable_ifcfg_rh
+  subdir('ifcfg-rh')
+endif
+
+if enable_ifupdown
+  subdir('ifupdown')
+endif
+
+if enable_tests
+  subdir('keyfile/tests')
+endif
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index 16e7851a..14f5cac8 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -47,6 +47,8 @@ typedef struct {
 	guint32    ap_scan;
 	gboolean   fast_required;
 	gboolean   dispose_has_run;
+	gboolean   support_pmf;
+	gboolean   support_fils;
 } NMSupplicantConfigPrivate;
 
 struct _NMSupplicantConfig {
@@ -65,9 +67,18 @@ G_DEFINE_TYPE (NMSupplicantConfig, nm_supplicant_config, G_TYPE_OBJECT)
 /*****************************************************************************/
 
 NMSupplicantConfig *
-nm_supplicant_config_new (void)
+nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils)
 {
-	return g_object_new (NM_TYPE_SUPPLICANT_CONFIG, NULL);
+	NMSupplicantConfigPrivate *priv;
+	NMSupplicantConfig *self;
+
+	self = g_object_new (NM_TYPE_SUPPLICANT_CONFIG, NULL);
+	priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
+
+	priv->support_pmf = support_pmf;
+	priv->support_fils = support_fils;
+
+	return self;
 }
 
 static void
@@ -369,7 +380,6 @@ nm_supplicant_config_add_setting_macsec (NMSupplicantConfig * self,
                                          NMSettingMacsec * setting,
                                          GError **error)
 {
-	NMSupplicantConfigPrivate *priv;
 	gs_unref_bytes GBytes *bytes = NULL;
 	const char *value;
 	char buf[32];
@@ -379,8 +389,6 @@ nm_supplicant_config_add_setting_macsec (NMSupplicantConfig * self,
 	g_return_val_if_fail (setting != NULL, FALSE);
 	g_return_val_if_fail (!error || !*error, FALSE);
 
-	priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
-
 	if (!nm_supplicant_config_add_option (self, "macsec_policy", "1", -1, NULL, error))
 		return FALSE;
 
@@ -736,8 +744,10 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
                                                     const char *con_uuid,
                                                     guint32 mtu,
                                                     NMSettingWirelessSecurityPmf pmf,
+                                                    NMSettingWirelessSecurityFils fils,
                                                     GError **error)
 {
+	NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
 	const char *key_mgmt, *key_mgmt_conf, *auth_alg;
 	const char *psk;
 
@@ -746,18 +756,37 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	g_return_val_if_fail (con_uuid != NULL, FALSE);
 	g_return_val_if_fail (!error || !*error, FALSE);
 
+	/* Check if we actually support FILS */
+	if (!priv->support_fils) {
+		if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED) {
+			g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
+			                     "Supplicant does not support FILS");
+			return FALSE;
+		} else if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL)
+			fils = NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE;
+	}
+
 	key_mgmt = key_mgmt_conf = nm_setting_wireless_security_get_key_mgmt (setting);
-	if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL) {
-		if (nm_streq (key_mgmt_conf, "wpa-psk"))
+	if (nm_streq (key_mgmt, "wpa-psk")) {
+		if (priv->support_pmf)
 			key_mgmt_conf = "wpa-psk wpa-psk-sha256";
-		else if (nm_streq (key_mgmt_conf, "wpa-eap"))
-			key_mgmt_conf = "wpa-eap wpa-eap-sha256";
-	} else if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
-		if (nm_streq (key_mgmt_conf, "wpa-psk"))
-			key_mgmt_conf = "wpa-psk-sha256";
-		else if (nm_streq (key_mgmt_conf, "wpa-eap"))
-			key_mgmt_conf = "wpa-eap-sha256";
+	} else if (nm_streq (key_mgmt, "wpa-eap")) {
+		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";
+			break;
+		default:
+			if (priv->support_pmf)
+				key_mgmt_conf = "wpa-eap wpa-eap-sha256";
+			break;
+		}
 	}
+
 	if (!add_string_val (self, key_mgmt_conf, "key_mgmt", TRUE, NULL, error))
 		return FALSE;
 
@@ -803,6 +832,20 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 		}
 	}
 
+	/* Don't try to enable PMF on non-WPA networks */
+	if (!NM_IN_STRSET (key_mgmt, "wpa-eap", "wpa-psk"))
+		pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
+
+	/* Check if we actually support PMF */
+	if (!priv->support_pmf) {
+		if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
+			g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
+			                     "Supplicant does not support PMF");
+			return FALSE;
+		} else if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL)
+			pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
+	}
+
 	/* Only WPA-specific things when using WPA */
 	if (   !strcmp (key_mgmt, "wpa-none")
 	    || !strcmp (key_mgmt, "wpa-psk")
diff --git a/src/supplicant/nm-supplicant-config.h b/src/supplicant/nm-supplicant-config.h
index d90d82b8..f6c845a3 100644
--- a/src/supplicant/nm-supplicant-config.h
+++ b/src/supplicant/nm-supplicant-config.h
@@ -40,7 +40,7 @@ typedef struct _NMSupplicantConfigClass NMSupplicantConfigClass;
 
 GType nm_supplicant_config_get_type (void);
 
-NMSupplicantConfig *nm_supplicant_config_new (void);
+NMSupplicantConfig *nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils);
 
 guint32 nm_supplicant_config_get_ap_scan (NMSupplicantConfig *self);
 
@@ -65,6 +65,7 @@ gboolean nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig
                                                              const char *con_uuid,
                                                              guint32 mtu,
                                                              NMSettingWirelessSecurityPmf pmf,
+                                                             NMSettingWirelessSecurityFils fils,
                                                              GError **error);
 
 gboolean nm_supplicant_config_add_no_security (NMSupplicantConfig *self,
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 44f887cb..3511b151 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -93,6 +93,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
 	PROP_FAST_SUPPORT,
 	PROP_AP_SUPPORT,
 	PROP_PMF_SUPPORT,
+	PROP_FILS_SUPPORT,
 );
 
 typedef struct {
@@ -102,6 +103,7 @@ typedef struct {
 	NMSupplicantFeature fast_support;
 	NMSupplicantFeature ap_support;   /* Lightweight AP mode support */
 	NMSupplicantFeature pmf_support;
+	NMSupplicantFeature fils_support;
 	guint32        max_scan_ssids;
 	guint32        ready_count;
 
@@ -565,6 +567,12 @@ nm_supplicant_interface_get_pmf_support (NMSupplicantInterface *self)
 	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->pmf_support;
 }
 
+NMSupplicantFeature
+nm_supplicant_interface_get_fils_support (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->fils_support;
+}
+
 void
 nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                         NMSupplicantFeature ap_support)
@@ -596,6 +604,15 @@ nm_supplicant_interface_set_pmf_support (NMSupplicantInterface *self,
 	priv->pmf_support = pmf_support;
 }
 
+void
+nm_supplicant_interface_set_fils_support (NMSupplicantInterface *self,
+                                          NMSupplicantFeature fils_support)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->fils_support = fils_support;
+}
+
 /*****************************************************************************/
 
 static void
@@ -1057,9 +1074,8 @@ props_changed_cb (GDBusProxy *proxy,
 	}
 
 	if (g_variant_lookup (changed_properties, "CurrentBSS", "&o", &s)) {
-		if (strcmp (s, "/") == 0)
-			s = NULL;
-		if (g_strcmp0 (s, priv->current_bss) != 0) {
+		s = nm_utils_dbus_normalize_object_path (s);
+		if (!nm_streq0 (s, priv->current_bss)) {
 			g_free (priv->current_bss);
 			priv->current_bss = g_strdup (s);
 			_notify (self, PROP_CURRENT_BSS);
@@ -1198,7 +1214,6 @@ static void
 interface_get_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
 	gs_unref_variant GVariant *variant = NULL;
 	gs_free_error GError *error = NULL;
 	const char *path;
@@ -1210,7 +1225,6 @@ interface_get_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 		return;
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
 	if (variant) {
 		g_variant_get (variant, "(&o)", &path);
@@ -1900,6 +1914,10 @@ set_property (GObject *object,
 		/* construct-only */
 		priv->pmf_support = g_value_get_int (value);
 		break;
+	case PROP_FILS_SUPPORT:
+		/* construct-only */
+		priv->fils_support = g_value_get_int (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1920,7 +1938,8 @@ nm_supplicant_interface_new (const char *ifname,
                              NMSupplicantDriver driver,
                              NMSupplicantFeature fast_support,
                              NMSupplicantFeature ap_support,
-                             NMSupplicantFeature pmf_support)
+                             NMSupplicantFeature pmf_support,
+                             NMSupplicantFeature fils_support)
 {
 	g_return_val_if_fail (ifname != NULL, NULL);
 
@@ -1930,6 +1949,7 @@ nm_supplicant_interface_new (const char *ifname,
 	                     NM_SUPPLICANT_INTERFACE_FAST_SUPPORT, (int) fast_support,
 	                     NM_SUPPLICANT_INTERFACE_AP_SUPPORT, (int) ap_support,
 	                     NM_SUPPLICANT_INTERFACE_PMF_SUPPORT, (int) pmf_support,
+	                     NM_SUPPLICANT_INTERFACE_FILS_SUPPORT, (int) fils_support,
 	                     NULL);
 }
 
@@ -1964,7 +1984,7 @@ dispose (GObject *object)
 	nm_clear_g_cancellable (&priv->other_cancellable);
 
 	g_clear_object (&priv->wpas_proxy);
-	g_clear_pointer (&priv->bss_proxies, (GDestroyNotify) g_hash_table_destroy);
+	g_clear_pointer (&priv->bss_proxies, g_hash_table_destroy);
 
 	g_clear_pointer (&priv->net_path, g_free);
 	g_clear_pointer (&priv->dev, g_free);
@@ -2029,6 +2049,14 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                      G_PARAM_WRITABLE |
 	                      G_PARAM_CONSTRUCT_ONLY |
 	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_FILS_SUPPORT] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_FILS_SUPPORT, "", "",
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      NM_SUPPLICANT_FEATURE_YES,
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
 
 	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 567cf96f..f32ad8dd 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/supplicant/nm-supplicant-interface.h
@@ -61,6 +61,7 @@ typedef enum {
 #define NM_SUPPLICANT_INTERFACE_FAST_SUPPORT     "fast-support"
 #define NM_SUPPLICANT_INTERFACE_AP_SUPPORT       "ap-support"
 #define NM_SUPPLICANT_INTERFACE_PMF_SUPPORT      "pmf-support"
+#define NM_SUPPLICANT_INTERFACE_FILS_SUPPORT     "fils-support"
 
 /* Signals */
 #define NM_SUPPLICANT_INTERFACE_STATE            "state"
@@ -79,7 +80,8 @@ NMSupplicantInterface * nm_supplicant_interface_new (const char *ifname,
                                                      NMSupplicantDriver driver,
                                                      NMSupplicantFeature fast_support,
                                                      NMSupplicantFeature ap_support,
-                                                     NMSupplicantFeature pmf_support);
+                                                     NMSupplicantFeature pmf_support,
+                                                     NMSupplicantFeature fils_support);
 
 void nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
                                                        gboolean available);
@@ -123,6 +125,7 @@ gboolean nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self,
 
 NMSupplicantFeature nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self);
 NMSupplicantFeature nm_supplicant_interface_get_pmf_support (NMSupplicantInterface *self);
+NMSupplicantFeature nm_supplicant_interface_get_fils_support (NMSupplicantInterface *self);
 
 void nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                              NMSupplicantFeature apmode);
@@ -133,6 +136,9 @@ void nm_supplicant_interface_set_fast_support (NMSupplicantInterface *self,
 void nm_supplicant_interface_set_pmf_support (NMSupplicantInterface *self,
                                               NMSupplicantFeature pmf_support);
 
+void nm_supplicant_interface_set_fils_support (NMSupplicantInterface *self,
+                                               NMSupplicantFeature fils_support);
+
 void nm_supplicant_interface_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 0f2eb63a..5ab96f88 100644
--- a/src/supplicant/nm-supplicant-manager.c
+++ b/src/supplicant/nm-supplicant-manager.c
@@ -40,6 +40,7 @@ typedef struct {
 	NMSupplicantFeature fast_support;
 	NMSupplicantFeature ap_support;
 	NMSupplicantFeature pmf_support;
+	NMSupplicantFeature fils_support;
 	guint             die_count_reset_id;
 	guint             die_count;
 } NMSupplicantManagerPrivate;
@@ -161,7 +162,8 @@ nm_supplicant_manager_create_interface (NMSupplicantManager *self,
 	                                     driver,
 	                                     priv->fast_support,
 	                                     priv->ap_support,
-	                                     priv->pmf_support);
+	                                     priv->pmf_support,
+	                                     priv->fils_support);
 
 	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
 	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
@@ -196,6 +198,7 @@ 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;
 
 	value = g_dbus_proxy_get_cached_property (priv->proxy, "Capabilities");
 	if (value) {
@@ -203,21 +206,25 @@ update_capabilities (NMSupplicantManager *self)
 			array = g_variant_get_strv (value, NULL);
 			priv->ap_support = NM_SUPPLICANT_FEATURE_NO;
 			priv->pmf_support = NM_SUPPLICANT_FEATURE_NO;
+			priv->fils_support = NM_SUPPLICANT_FEATURE_NO;
 			if (array) {
 				if (g_strv_contains (array, "ap"))
 					priv->ap_support = NM_SUPPLICANT_FEATURE_YES;
 				if (g_strv_contains (array, "pmf"))
 					priv->pmf_support = NM_SUPPLICANT_FEATURE_YES;
+				if (g_strv_contains (array, "fils"))
+					priv->fils_support = NM_SUPPLICANT_FEATURE_YES;
 				g_free (array);
 			}
 		}
 		g_variant_unref (value);
 	}
 
-	/* Tell all interfaces about results of the AP/PMF check */
+	/* Tell all interfaces about results of the AP/PMF/FILS 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);
 	}
 
 	_LOGD ("AP mode is %ssupported",
@@ -226,6 +233,9 @@ update_capabilities (NMSupplicantManager *self)
 	_LOGD ("PMF is %ssupported",
 	       (priv->pmf_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
 	           (priv->pmf_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+	_LOGD ("FILS is %ssupported",
+	       (priv->fils_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
+	           (priv->fils_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
 
 	/* EAP-FAST */
 	priv->fast_support = NM_SUPPLICANT_FEATURE_NO;
@@ -349,6 +359,7 @@ name_owner_cb (GDBusProxy *proxy, GParamSpec *pspec, gpointer user_data)
 		priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 		priv->fast_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 		priv->pmf_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+		priv->fils_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 
 		set_running (self, FALSE);
 	}
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
index 14daf693..5198d75f 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -73,6 +73,7 @@ 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",
+                                    "FILS-SHA256", "FILS-SHA384",
                                     "IEEE8021X", "WPA-NONE",
                                     "NONE", NULL };
 const char * auth_alg_allowed[] = { "OPEN", "SHARED", "LEAP", NULL };
@@ -221,10 +222,10 @@ validate_type_keyword (const struct Opt * opt,
                        const char * value,
                        const guint32 len)
 {
-	char **		allowed;
-	gchar **	candidates = NULL;
-	char **		candidate;
-	gboolean	found = FALSE;
+	char **allowed;
+	gchar **candidates = NULL;
+	char **candidate;
+	gboolean found = FALSE;
 
 	g_return_val_if_fail (opt != NULL, FALSE);
 	g_return_val_if_fail (value != NULL, FALSE);
diff --git a/src/supplicant/nm-supplicant-types.h b/src/supplicant/nm-supplicant-types.h
index f75827ec..747cf152 100644
--- a/src/supplicant/nm-supplicant-types.h
+++ b/src/supplicant/nm-supplicant-types.h
@@ -21,9 +21,9 @@
 #ifndef __NETWORKMANAGER_SUPPLICANT_TYPES_H__
 #define __NETWORKMANAGER_SUPPLICANT_TYPES_H__
 
-#define WPAS_DBUS_SERVICE	"fi.w1.wpa_supplicant1"
-#define WPAS_DBUS_PATH		"/fi/w1/wpa_supplicant1"
-#define WPAS_DBUS_INTERFACE	"fi.w1.wpa_supplicant1"
+#define WPAS_DBUS_SERVICE       "fi.w1.wpa_supplicant1"
+#define WPAS_DBUS_PATH          "/fi/w1/wpa_supplicant1"
+#define WPAS_DBUS_INTERFACE     "fi.w1.wpa_supplicant1"
 
 typedef struct _NMSupplicantManager NMSupplicantManager;
 typedef struct _NMSupplicantInterface NMSupplicantInterface;
diff --git a/src/supplicant/tests/meson.build b/src/supplicant/tests/meson.build
new file mode 100644
index 00000000..e6a86b20
--- /dev/null
+++ b/src/supplicant/tests/meson.build
@@ -0,0 +1,14 @@
+test_unit = 'test-supplicant-config'
+
+exe = executable(
+  test_unit,
+  test_unit + '.c',
+  dependencies: test_nm_dep,
+  c_args: '-DTEST_CERT_DIR="@0@"'.format(join_paths(meson.current_source_dir(), 'certs'))
+)
+
+test(
+  'supplicant/' + test_unit,
+  test_script,
+  args: test_args + [exe.full_path()]
+)
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index 4b4a4935..60ca5258 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -95,7 +95,11 @@ validate_opt (const char *detail,
 }
 
 static GVariant *
-build_supplicant_config (NMConnection *connection, guint mtu, guint fixed_freq)
+build_supplicant_config (NMConnection *connection,
+                         guint mtu,
+                         guint fixed_freq,
+                         gboolean support_pmf,
+                         gboolean support_fils)
 {
 	gs_unref_object NMSupplicantConfig *config = NULL;
 	gs_free_error GError *error = NULL;
@@ -104,7 +108,7 @@ build_supplicant_config (NMConnection *connection, guint mtu, guint fixed_freq)
 	NMSetting8021x *s_8021x;
 	gboolean success;
 
-	config = nm_supplicant_config_new ();
+	config = nm_supplicant_config_new (support_pmf, support_fils);
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wifi);
@@ -118,6 +122,7 @@ build_supplicant_config (NMConnection *connection, guint mtu, guint fixed_freq)
 	s_wsec = nm_connection_get_setting_wireless_security (connection);
 	if (s_wsec) {
 		NMSettingWirelessSecurityPmf pmf = nm_setting_wireless_security_get_pmf (s_wsec);
+		NMSettingWirelessSecurityFils fils = nm_setting_wireless_security_get_fils (s_wsec);
 		s_8021x = nm_connection_get_setting_802_1x (connection);
 		success = nm_supplicant_config_add_setting_wireless_security (config,
 			                                                          s_wsec,
@@ -125,6 +130,7 @@ build_supplicant_config (NMConnection *connection, guint mtu, guint fixed_freq)
 			                                                          nm_connection_get_uuid (connection),
 			                                                          mtu,
 			                                                          pmf,
+			                                                          fils,
 			                                                          &error);
 	} else {
 		success = nm_supplicant_config_add_no_security (config, &error);
@@ -140,7 +146,6 @@ build_supplicant_config (NMConnection *connection, guint mtu, guint fixed_freq)
 	return nm_supplicant_config_to_variant (config);
 }
 
-#define EXPECT(msg) g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, msg)
 
 static NMConnection *
 new_basic_connection (const char *id,
@@ -199,12 +204,12 @@ test_wifi_open (void)
 	g_assert_no_error (error);
 	g_assert (success);
 
-	EXPECT ("*added 'ssid' value 'Test SSID'*");
-	EXPECT ("*added 'scan_ssid' value '1'*");
-	EXPECT ("*added 'bssid' value '11:22:33:44:55:66'*");
-	EXPECT ("*added 'freq_list' value *");
-	EXPECT ("*added 'key_mgmt' value 'NONE'");
-	config_dict = build_supplicant_config (connection, 1500, 0);
+	NMTST_EXPECT_NM_INFO ("Config: added 'ssid' value 'Test SSID'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'scan_ssid' value '1'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'bssid' value '11:22:33:44:55:66'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'freq_list' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'key_mgmt' value 'NONE'");
+	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -249,19 +254,19 @@ test_wifi_wep_key (const char *detail,
 	g_assert_no_error (error);
 	g_assert (success);
 
-	EXPECT ("*added 'ssid' value 'Test SSID'*");
-	EXPECT ("*added 'scan_ssid' value '1'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'ssid' value 'Test SSID'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'scan_ssid' value '1'*");
 	if (test_bssid)
-		EXPECT ("*added 'bssid' value '11:22:33:44:55:66'*");
+		NMTST_EXPECT_NM_INFO ("Config: added 'bssid' value '11:22:33:44:55:66'*");
 
-	EXPECT ("*added 'freq_list' value *");
-	EXPECT ("*added 'key_mgmt' value 'NONE'");
-	EXPECT ("*added 'wep_key0' value *");
-	EXPECT ("*added 'wep_tx_keyidx' value '0'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'freq_list' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'key_mgmt' value 'NONE'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'wep_key0' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'wep_tx_keyidx' value '0'");
 	if (!test_bssid)
-		EXPECT ("*added 'bgscan' value 'simple:30:-80:86400'*");
+		NMTST_EXPECT_NM_INFO ("Config: added 'bgscan' value 'simple:30:-80:86400'*");
 
-	config_dict = build_supplicant_config (connection, 1500, 0);
+	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -308,7 +313,8 @@ test_wifi_wpa_psk (const char *detail,
                    OptType key_type,
                    const char *key_data,
                    const unsigned char *expected,
-                   size_t expected_size)
+                   size_t expected_size,
+                   NMSettingWirelessSecurityPmf pmf)
 {
 	gs_unref_object NMConnection *connection = NULL;
 	gs_unref_variant GVariant *config_dict = NULL;
@@ -328,7 +334,7 @@ test_wifi_wpa_psk (const char *detail,
 	g_object_set (s_wsec,
 	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk",
 	              NM_SETTING_WIRELESS_SECURITY_PSK, key_data,
-	              NM_SETTING_WIRELESS_SECURITY_PMF, (int) NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL,
+	              NM_SETTING_WIRELESS_SECURITY_PMF, (int) pmf,
 	              NULL);
 	nm_setting_wireless_security_add_proto (s_wsec, "wpa");
 	nm_setting_wireless_security_add_proto (s_wsec, "rsn");
@@ -341,17 +347,26 @@ test_wifi_wpa_psk (const char *detail,
 	g_assert_no_error (error);
 	g_assert (success);
 
-	EXPECT ("*added 'ssid' value 'Test SSID'*");
-	EXPECT ("*added 'scan_ssid' value '1'*");
-	EXPECT ("*added 'bssid' value '11:22:33:44:55:66'*");
-	EXPECT ("*added 'freq_list' value *");
-	EXPECT ("*added 'key_mgmt' value 'WPA-PSK WPA-PSK-SHA256'");
-	EXPECT ("*added 'psk' value *");
-	EXPECT ("*added 'proto' value 'WPA RSN'");
-	EXPECT ("*added 'pairwise' value 'TKIP CCMP'");
-	EXPECT ("*added 'group' value 'TKIP CCMP'");
-	EXPECT ("*added 'ieee80211w' value '1'");
-	config_dict = build_supplicant_config (connection, 1500, 0);
+	NMTST_EXPECT_NM_INFO ("Config: added 'ssid' value 'Test SSID'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'scan_ssid' value '1'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'bssid' value '11:22:33:44:55:66'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'freq_list' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'key_mgmt' value 'WPA-PSK WPA-PSK-SHA256'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'psk' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'proto' value 'WPA RSN'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
+	switch (pmf) {
+	case NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL:
+		NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '1'");
+		break;
+	case NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED:
+		NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '2'");
+		break;
+	default:
+		break;
+	}
+	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
 
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
@@ -381,12 +396,16 @@ test_wifi_wpa_psk_types (void)
 	                                        0x6c, 0x2f, 0x11, 0x60, 0x5a, 0x16, 0x08, 0x93 };
 	const char *key2 = "r34lly l33t wp4 p4ssphr4s3 for t3st1ng";
 
-	test_wifi_wpa_psk ("wifi-wpa-psk-hex", TYPE_BYTES, key1, key1_expected, sizeof (key1_expected));
-	test_wifi_wpa_psk ("wifi-wep-psk-passphrase", TYPE_STRING, key2, (gconstpointer) key2, strlen (key2));
+	test_wifi_wpa_psk ("wifi-wpa-psk-hex", TYPE_BYTES, key1, key1_expected,
+	                   sizeof (key1_expected), NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL);
+	test_wifi_wpa_psk ("wifi-wep-psk-passphrase", TYPE_STRING, key2,
+	                   (gconstpointer) key2, strlen (key2), NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED);
+	test_wifi_wpa_psk ("pmf-disabled", TYPE_STRING, key2,
+	                   (gconstpointer) key2, strlen (key2), NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE);
 }
 
 static NMConnection *
-generate_wifi_eap_connection (const char *id, GBytes *ssid, const char *bssid_str)
+generate_wifi_eap_connection (const char *id, GBytes *ssid, const char *bssid_str, NMSettingWirelessSecurityFils fils)
 {
 	NMConnection *connection = NULL;
 	NMSettingWirelessSecurity *s_wsec;
@@ -401,6 +420,7 @@ generate_wifi_eap_connection (const char *id, GBytes *ssid, const char *bssid_st
 	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
 	g_object_set (s_wsec,
 	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap",
+	              NM_SETTING_WIRELESS_SECURITY_FILS, (int) fils,
 	              NULL);
 	nm_setting_wireless_security_add_proto (s_wsec, "wpa");
 	nm_setting_wireless_security_add_proto (s_wsec, "rsn");
@@ -434,22 +454,22 @@ test_wifi_eap_locked_bssid (void)
 	const char *bssid_str = "11:22:33:44:55:66";
 	guint32 mtu = 1100;
 
-	connection = generate_wifi_eap_connection ("Test Wifi EAP-TLS Locked", ssid, bssid_str);
-
-	EXPECT ("*added 'ssid' value 'Test SSID'*");
-	EXPECT ("*added 'scan_ssid' value '1'*");
-	EXPECT ("*added 'bssid' value '11:22:33:44:55:66'*");
-	EXPECT ("*added 'freq_list' value *");
-	EXPECT ("*added 'key_mgmt' value 'WPA-EAP'");
-	EXPECT ("*added 'proto' value 'WPA RSN'");
-	EXPECT ("*added 'pairwise' value 'TKIP CCMP'");
-	EXPECT ("*added 'group' value 'TKIP CCMP'");
-	EXPECT ("*Config: added 'eap' value 'TLS'");
-	EXPECT ("*Config: added 'fragment_size' value '1086'");
-	EXPECT ("* Config: added 'ca_cert' value '*/test-ca-cert.pem'");
-	EXPECT ("* Config: added 'private_key' value '*/test-cert.p12'");
-	EXPECT ("*Config: added 'proactive_key_caching' value '1'");
-	config_dict = build_supplicant_config (connection, mtu, 0);
+	connection = generate_wifi_eap_connection ("Test Wifi EAP-TLS Locked", ssid, bssid_str, NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL);
+
+	NMTST_EXPECT_NM_INFO ("Config: added 'ssid' value 'Test SSID'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'scan_ssid' value '1'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'bssid' value '11:22:33:44:55:66'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'freq_list' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'key_mgmt' value 'WPA-EAP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'proto' value 'WPA RSN'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'eap' value 'TLS'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'fragment_size' value '1086'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'ca_cert' value '*/test-ca-cert.pem'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'private_key' value '*/test-cert.p12'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'proactive_key_caching' value '1'");
+	config_dict = build_supplicant_config (connection, mtu, 0, FALSE, FALSE);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -475,28 +495,69 @@ test_wifi_eap_unlocked_bssid (void)
 	gs_unref_bytes GBytes *bgscan = g_bytes_new (bgscan_data, strlen (bgscan_data));
 	guint32 mtu = 1100;
 
-	connection = generate_wifi_eap_connection ("Test Wifi EAP-TLS Unlocked", ssid, NULL);
-
-	EXPECT ("*added 'ssid' value 'Test SSID'*");
-	EXPECT ("*added 'scan_ssid' value '1'*");
-	EXPECT ("*added 'freq_list' value *");
-	EXPECT ("*added 'key_mgmt' value 'WPA-EAP'");
-	EXPECT ("*added 'proto' value 'WPA RSN'");
-	EXPECT ("*added 'pairwise' value 'TKIP CCMP'");
-	EXPECT ("*added 'group' value 'TKIP CCMP'");
-	EXPECT ("*Config: added 'eap' value 'TLS'");
-	EXPECT ("*Config: added 'fragment_size' value '1086'");
-	EXPECT ("* Config: added 'ca_cert' value '*/test-ca-cert.pem'");
-	EXPECT ("* Config: added 'private_key' value '*/test-cert.p12'");
-	EXPECT ("*Config: added 'proactive_key_caching' value '1'");
-	EXPECT ("*Config: added 'bgscan' value 'simple:30:-65:300'");
-	config_dict = build_supplicant_config (connection, mtu, 0);
+	connection = generate_wifi_eap_connection ("Test Wifi EAP-TLS Unlocked", ssid, NULL, NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED);
+
+	NMTST_EXPECT_NM_INFO ("Config: added 'ssid' value 'Test SSID'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'scan_ssid' value '1'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'freq_list' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'key_mgmt' value 'FILS-SHA256 FILS-SHA384'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'proto' value 'WPA RSN'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'eap' value 'TLS'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'fragment_size' value '1086'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'ca_cert' value '*/test-ca-cert.pem'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'private_key' value '*/test-cert.p12'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'proactive_key_caching' value '1'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'bgscan' value 'simple:30:-65:300'");
+	config_dict = build_supplicant_config (connection, mtu, 0, FALSE, TRUE);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
 	validate_opt ("wifi-eap", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
 	validate_opt ("wifi-eap", config_dict, "ssid", TYPE_BYTES, ssid);
-	validate_opt ("wifi-eap", config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-EAP");
+	validate_opt ("wifi-eap", config_dict, "key_mgmt", TYPE_KEYWORD, "FILS-SHA256 FILS-SHA384");
+	validate_opt ("wifi-eap", config_dict, "eap", TYPE_KEYWORD, "TLS");
+	validate_opt ("wifi-eap", config_dict, "proto", TYPE_KEYWORD, "WPA RSN");
+	validate_opt ("wifi-eap", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "fragment_size", TYPE_INT, GINT_TO_POINTER(mtu-14));
+	validate_opt ("wifi-eap", config_dict, "bgscan", TYPE_BYTES, bgscan);
+}
+
+static void
+test_wifi_eap_fils_disabled (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_variant GVariant *config_dict = NULL;
+	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	gs_unref_bytes GBytes *ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
+	const char *bgscan_data = "simple:30:-65:300";
+	gs_unref_bytes GBytes *bgscan = g_bytes_new (bgscan_data, strlen (bgscan_data));
+	guint32 mtu = 1100;
+
+	connection = generate_wifi_eap_connection ("Test Wifi FILS disabled", ssid, NULL, NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE);
+
+	NMTST_EXPECT_NM_INFO ("Config: added 'ssid' value 'Test SSID'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'scan_ssid' value '1'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'freq_list' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'key_mgmt' value 'WPA-EAP WPA-EAP-SHA256'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'proto' value 'WPA RSN'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'eap' value 'TLS'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'fragment_size' value '1086'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'ca_cert' value '*/test-ca-cert.pem'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'private_key' value '*/test-cert.p12'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'proactive_key_caching' value '1'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'bgscan' value 'simple:30:-65:300'");
+	config_dict = build_supplicant_config (connection, mtu, 0, TRUE, TRUE);
+	g_test_assert_expected_messages ();
+	g_assert (config_dict);
+
+	validate_opt ("wifi-eap", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-eap", config_dict, "ssid", TYPE_BYTES, ssid);
+	validate_opt ("wifi-eap", config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-EAP WPA-EAP-SHA256");
 	validate_opt ("wifi-eap", config_dict, "eap", TYPE_KEYWORD, "TLS");
 	validate_opt ("wifi-eap", config_dict, "proto", TYPE_KEYWORD, "WPA RSN");
 	validate_opt ("wifi-eap", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
@@ -516,6 +577,7 @@ int main (int argc, char **argv)
 	g_test_add_func ("/supplicant-config/wifi-wpa-psk-types", test_wifi_wpa_psk_types);
 	g_test_add_func ("/supplicant-config/wifi-eap/locked-bssid", test_wifi_eap_locked_bssid);
 	g_test_add_func ("/supplicant-config/wifi-eap/unlocked-bssid", test_wifi_eap_unlocked_bssid);
+	g_test_add_func ("/supplicant-config/wifi-eap/fils-disabled", test_wifi_eap_fils_disabled);
 
 	return g_test_run ();
 }
diff --git a/src/systemd/meson.build b/src/systemd/meson.build
new file mode 100644
index 00000000..fe3d88d3
--- /dev/null
+++ b/src/systemd/meson.build
@@ -0,0 +1,70 @@
+sources = files(
+  'sd-adapt/nm-sd-adapt.c',
+  'src/basic/alloc-util.c',
+  'src/basic/escape.c',
+  'src/basic/ether-addr-util.c',
+  'src/basic/extract-word.c',
+  'src/basic/fd-util.c',
+  'src/basic/fileio.c',
+  'src/basic/fs-util.c',
+  'src/basic/hash-funcs.c',
+  'src/basic/hashmap.c',
+  'src/basic/hexdecoct.c',
+  'src/basic/hostname-util.c',
+  'src/basic/in-addr-util.c',
+  'src/basic/io-util.c',
+  'src/basic/mempool.c',
+  'src/basic/parse-util.c',
+  'src/basic/path-util.c',
+  'src/basic/prioq.c',
+  'src/basic/process-util.c',
+  'src/basic/random-util.c',
+  'src/basic/socket-util.c',
+  'src/basic/string-table.c',
+  'src/basic/string-util.c',
+  'src/basic/strv.c',
+  'src/basic/time-util.c',
+  'src/basic/utf8.c',
+  'src/basic/util.c',
+  'src/libsystemd-network/arp-util.c',
+  'src/libsystemd-network/dhcp-identifier.c',
+  'src/libsystemd-network/dhcp-network.c',
+  'src/libsystemd-network/dhcp-option.c',
+  'src/libsystemd-network/dhcp-packet.c',
+  'src/libsystemd-network/dhcp6-network.c',
+  'src/libsystemd-network/dhcp6-option.c',
+  'src/libsystemd-network/lldp-neighbor.c',
+  'src/libsystemd-network/lldp-network.c',
+  'src/libsystemd-network/network-internal.c',
+  'src/libsystemd-network/sd-dhcp-client.c',
+  'src/libsystemd-network/sd-dhcp-lease.c',
+  'src/libsystemd-network/sd-dhcp6-client.c',
+  'src/libsystemd-network/sd-dhcp6-lease.c',
+  'src/libsystemd-network/sd-ipv4acd.c',
+  'src/libsystemd-network/sd-ipv4ll.c',
+  'src/libsystemd-network/sd-lldp.c',
+  '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'
+)
+
+incs = [
+  src_inc,
+  include_directories(
+    'sd-adapt',
+    'src/basic',
+    'src/libsystemd-network',
+    'src/shared',
+    'src/systemd'
+  )
+]
+
+libsystemd_nm = static_library(
+  'systemd-nm',
+  sources: sources,
+  include_directories: incs,
+  dependencies: nm_core_dep,
+  c_args: '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD'
+)
diff --git a/src/systemd/sd-adapt/device-nodes.h b/src/systemd/sd-adapt/device-nodes.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/systemd/sd-adapt/device-nodes.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/sd-adapt/errno-list.h b/src/systemd/sd-adapt/errno-list.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/systemd/sd-adapt/errno-list.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/sd-adapt/locale-util.h b/src/systemd/sd-adapt/locale-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/systemd/sd-adapt/locale-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/sd-adapt/memfd-util.h b/src/systemd/sd-adapt/memfd-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/systemd/sd-adapt/memfd-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/sd-adapt/nm-sd-adapt.h b/src/systemd/sd-adapt/nm-sd-adapt.h
index 0d291e26..3a4125a0 100644
--- a/src/systemd/sd-adapt/nm-sd-adapt.h
+++ b/src/systemd/sd-adapt/nm-sd-adapt.h
@@ -26,12 +26,16 @@
 #include <sys/resource.h>
 #include <time.h>
 
-#define noreturn G_GNUC_NORETURN
-
 #ifndef CLOCK_BOOTTIME
 #define CLOCK_BOOTTIME 7
 #endif
 
+#if defined(HAVE_DECL_REALLOCARRAY) && HAVE_DECL_REALLOCARRAY == 1
+#define HAVE_REALLOCARRAY 1
+#else
+#define HAVE_REALLOCARRAY 0
+#endif
+
 #if defined(HAVE_DECL_EXPLICIT_BZERO) && HAVE_DECL_EXPLICIT_BZERO == 1
 #define HAVE_EXPLICIT_BZERO 1
 #else
@@ -105,7 +109,7 @@ G_STMT_START { \
  * itself.
  *****************************************************************************/
 
-#if (NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_SYSTEMD
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD
 
 #include <netinet/in.h>
 #include <string.h>
@@ -138,6 +142,15 @@ G_STMT_START { \
 #  endif
 #endif
 
+static inline pid_t
+raw_getpid (void) {
+#if defined(__alpha__)
+	return (pid_t) syscall (__NR_getxpid);
+#else
+	return (pid_t) syscall (__NR_getpid);
+#endif
+}
+
 /*****************************************************************************/
 
 /* work around missing uchar.h */
@@ -146,8 +159,6 @@ typedef guint32 char32_t;
 
 /*****************************************************************************/
 
-#define PID_TO_PTR(p) ((void*) ((uintptr_t) p))
-
 static inline int
 sd_notify (int unset_environment, const char *state)
 {
@@ -186,7 +197,7 @@ static inline pid_t gettid(void) {
         return (pid_t) syscall(SYS_gettid);
 }
 
-#endif /* (NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_SYSTEMD */
+#endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */
 
 #endif /* NM_SD_ADAPT_H */
 
diff --git a/src/systemd/sd-adapt/procfs-util.h b/src/systemd/sd-adapt/procfs-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/systemd/sd-adapt/procfs-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/sd-adapt/terminal-util.h b/src/systemd/sd-adapt/terminal-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/systemd/sd-adapt/terminal-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/src/basic/alloc-util.c b/src/systemd/src/basic/alloc-util.c
index 97588312..1a058d2f 100644
--- a/src/systemd/src/basic/alloc-util.c
+++ b/src/systemd/src/basic/alloc-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -39,7 +40,7 @@ void* memdup(const void *p, size_t l) {
         return ret;
 }
 
-void* memdup_suffix0(const void*p, size_t l) {
+void* memdup_suffix0(const void *p, size_t l) {
         void *ret;
 
         assert(l == 0 || p);
diff --git a/src/systemd/src/basic/alloc-util.h b/src/systemd/src/basic/alloc-util.h
index 0a89691b..b1e0edbb 100644
--- a/src/systemd/src/basic/alloc-util.h
+++ b/src/systemd/src/basic/alloc-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -54,7 +55,7 @@ static inline void *mfree(void *memory) {
         })
 
 void* memdup(const void *p, size_t l) _alloc_(2);
-void* memdup_suffix0(const void*p, size_t l) _alloc_(2);
+void* memdup_suffix0(const void *p, size_t l) _alloc_(2);
 
 static inline void freep(void *p) {
         free(*(void**) p);
@@ -73,12 +74,14 @@ _malloc_  _alloc_(1, 2) static inline void *malloc_multiply(size_t size, size_t
         return malloc(size * need);
 }
 
-_alloc_(2, 3) static inline void *realloc_multiply(void *p, size_t size, size_t need) {
+#if !HAVE_REALLOCARRAY
+_alloc_(2, 3) static inline void *reallocarray(void *p, size_t need, size_t size) {
         if (size_multiply_overflow(size, need))
                 return NULL;
 
         return realloc(p, size * need);
 }
+#endif
 
 _alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, size_t need) {
         if (size_multiply_overflow(size, need))
@@ -127,3 +130,12 @@ void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size);
                 _new_ = alloca_align(_size_, (align));                  \
                 (void*)memset(_new_, 0, _size_);                        \
         })
+
+/* Takes inspiration from Rusts's Option::take() method: reads and returns a pointer, but at the same time resets it to
+ * NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */
+#define TAKE_PTR(ptr)                           \
+        ({                                      \
+                typeof(ptr) _ptr_ = (ptr);      \
+                (ptr) = NULL;                   \
+                _ptr_;                          \
+        })
diff --git a/src/systemd/src/basic/async.h b/src/systemd/src/basic/async.h
index 9bd13ff6..01c975bb 100644
--- a/src/systemd/src/basic/async.h
+++ b/src/systemd/src/basic/async.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -21,5 +22,5 @@
 
 int asynchronous_job(void* (*func)(void *p), void *arg);
 
-int asynchronous_sync(void);
+int asynchronous_sync(pid_t *ret_pid);
 int asynchronous_close(int fd);
diff --git a/src/systemd/src/basic/escape.c b/src/systemd/src/basic/escape.c
index 27a20702..fac618dd 100644
--- a/src/systemd/src/basic/escape.c
+++ b/src/systemd/src/basic/escape.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
diff --git a/src/systemd/src/basic/escape.h b/src/systemd/src/basic/escape.h
index e62347af..cd5c49fa 100644
--- a/src/systemd/src/basic/escape.h
+++ b/src/systemd/src/basic/escape.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/basic/ether-addr-util.c b/src/systemd/src/basic/ether-addr-util.c
index a793219c..93922a28 100644
--- a/src/systemd/src/basic/ether-addr-util.c
+++ b/src/systemd/src/basic/ether-addr-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -19,6 +20,7 @@
 
 #include "nm-sd-adapt.h"
 
+#include <errno.h>
 #include <net/ethernet.h>
 #include <stdio.h>
 #include <sys/types.h>
@@ -72,7 +74,7 @@ int ether_addr_from_string(const char *s, struct ether_addr *ret, size_t *offset
                         if (s[pos] == '\0')                     \
                                 break;                          \
                         hexoff = strchr(hex, s[pos]);           \
-                        if (hexoff == NULL)                     \
+                        if (!hexoff)                            \
                                 break;                          \
                         assert(hexoff >= hex);                  \
                         x = hexoff - hex;                       \
@@ -100,7 +102,7 @@ int ether_addr_from_string(const char *s, struct ether_addr *ret, size_t *offset
         sep = s[strspn(s, hex)];
         if (sep == '\n')
                 return -EINVAL;
-        if (strchr(":.-", sep) == NULL)
+        if (!strchr(":.-", sep))
                 return -EINVAL;
 
         if (sep == '.') {
diff --git a/src/systemd/src/basic/ether-addr-util.h b/src/systemd/src/basic/ether-addr-util.h
index 74e125a9..08d05a13 100644
--- a/src/systemd/src/basic/ether-addr-util.h
+++ b/src/systemd/src/basic/ether-addr-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/basic/extract-word.c b/src/systemd/src/basic/extract-word.c
index 69d8c48d..e52c376b 100644
--- a/src/systemd/src/basic/extract-word.c
+++ b/src/systemd/src/basic/extract-word.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -195,8 +196,7 @@ finish:
 
 finish_force_next:
         s[sz] = 0;
-        *ret = s;
-        s = NULL;
+        *ret = TAKE_PTR(s);
 
         return 1;
 }
diff --git a/src/systemd/src/basic/extract-word.h b/src/systemd/src/basic/extract-word.h
index 04746c6d..300c51bb 100644
--- a/src/systemd/src/basic/extract-word.h
+++ b/src/systemd/src/basic/extract-word.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/basic/fd-util.c b/src/systemd/src/basic/fd-util.c
index 1c327d83..ff480f51 100644
--- a/src/systemd/src/basic/fd-util.c
+++ b/src/systemd/src/basic/fd-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -28,8 +29,10 @@
 
 #include "dirent-util.h"
 #include "fd-util.h"
+#include "fileio.h"
 #include "fs-util.h"
 #include "macro.h"
+#include "memfd-util.h"
 #include "missing.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -191,12 +194,6 @@ int fd_cloexec(int fd, bool cloexec) {
 }
 
 #if 0 /* NM_IGNORED */
-void stdio_unset_cloexec(void) {
-        fd_cloexec(STDIN_FILENO, false);
-        fd_cloexec(STDOUT_FILENO, false);
-        fd_cloexec(STDERR_FILENO, false);
-}
-
 _pure_ static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) {
         unsigned i;
 
@@ -227,20 +224,21 @@ int close_all_fds(const int except[], unsigned n_except) {
 
                 assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0);
                 for (fd = 3; fd < (int) rl.rlim_max; fd ++) {
+                        int q;
 
                         if (fd_in_set(fd, except, n_except))
                                 continue;
 
-                        if (close_nointr(fd) < 0)
-                                if (errno != EBADF && r == 0)
-                                        r = -errno;
+                        q = close_nointr(fd);
+                        if (q < 0 && q != -EBADF && r >= 0)
+                                r = q;
                 }
 
                 return r;
         }
 
         FOREACH_DIRENT(de, d, return -errno) {
-                int fd = -1;
+                int fd = -1, q;
 
                 if (safe_atoi(de->d_name, &fd) < 0)
                         /* Let's better ignore this, just in case */
@@ -255,11 +253,9 @@ int close_all_fds(const int except[], unsigned n_except) {
                 if (fd_in_set(fd, except, n_except))
                         continue;
 
-                if (close_nointr(fd) < 0) {
-                        /* Valgrind has its own FD and doesn't want to have it closed */
-                        if (errno != EBADF && r == 0)
-                                r = -errno;
-                }
+                q = close_nointr(fd);
+                if (q < 0 && q != -EBADF && r >= 0) /* Valgrind has its own FD and doesn't want to have it closed */
+                        r = q;
         }
 
         return r;
@@ -369,15 +365,384 @@ bool fdname_is_valid(const char *s) {
 }
 
 int fd_get_path(int fd, char **ret) {
-        char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+        _cleanup_close_ int dir = -1;
+        char fdname[DECIMAL_STR_MAX(int)];
         int r;
 
-        xsprintf(procfs_path, "/proc/self/fd/%i", fd);
+        dir = open("/proc/self/fd/", O_CLOEXEC | O_DIRECTORY | O_PATH);
+        if (dir < 0)
+                /* /proc is not available or not set up properly, we're most likely
+                 * in some chroot environment. */
+                return errno == ENOENT ? -EOPNOTSUPP : -errno;
 
-        r = readlink_malloc(procfs_path, ret);
+        xsprintf(fdname, "%i", fd);
 
-        if (r == -ENOENT) /* If the file doesn't exist the fd is invalid */
+        r = readlinkat_malloc(dir, fdname, ret);
+        if (r == -ENOENT)
+                /* If the file doesn't exist the fd is invalid */
                 return -EBADF;
 
         return r;
 }
+
+#if 0 /* NM_IGNORED */
+int move_fd(int from, int to, int cloexec) {
+        int r;
+
+        /* Move fd 'from' to 'to', make sure FD_CLOEXEC remains equal if requested, and release the old fd. If
+         * 'cloexec' is passed as -1, the original FD_CLOEXEC is inherited for the new fd. If it is 0, it is turned
+         * off, if it is > 0 it is turned on. */
+
+        if (from < 0)
+                return -EBADF;
+        if (to < 0)
+                return -EBADF;
+
+        if (from == to) {
+
+                if (cloexec >= 0) {
+                        r = fd_cloexec(to, cloexec);
+                        if (r < 0)
+                                return r;
+                }
+
+                return to;
+        }
+
+        if (cloexec < 0) {
+                int fl;
+
+                fl = fcntl(from, F_GETFD, 0);
+                if (fl < 0)
+                        return -errno;
+
+                cloexec = !!(fl & FD_CLOEXEC);
+        }
+
+        r = dup3(from, to, cloexec ? O_CLOEXEC : 0);
+        if (r < 0)
+                return -errno;
+
+        assert(r == to);
+
+        safe_close(from);
+
+        return to;
+}
+
+int acquire_data_fd(const void *data, size_t size, unsigned flags) {
+
+        _cleanup_close_pair_ int pipefds[2] = { -1, -1 };
+        char pattern[] = "/dev/shm/data-fd-XXXXXX";
+        _cleanup_close_ int fd = -1;
+        int isz = 0, r;
+        ssize_t n;
+        off_t f;
+
+        assert(data || size == 0);
+
+        /* Acquire a read-only file descriptor that when read from returns the specified data. This is much more
+         * complex than I wish it was. But here's why:
+         *
+         * a) First we try to use memfds. They are the best option, as we can seal them nicely to make them
+         *    read-only. Unfortunately they require kernel 3.17, and – at the time of writing – we still support 3.14.
+         *
+         * b) Then, we try classic pipes. They are the second best options, as we can close the writing side, retaining
+         *    a nicely read-only fd in the reading side. However, they are by default quite small, and unprivileged
+         *    clients can only bump their size to a system-wide limit, which might be quite low.
+         *
+         * c) Then, we try an O_TMPFILE file in /dev/shm (that dir is the only suitable one known to exist from
+         *    earliest boot on). To make it read-only we open the fd a second time with O_RDONLY via
+         *    /proc/self/<fd>. Unfortunately O_TMPFILE is not available on older kernels on tmpfs.
+         *
+         * d) Finally, we try creating a regular file in /dev/shm, which we then delete.
+         *
+         * It sucks a bit that depending on the situation we return very different objects here, but that's Linux I
+         * figure. */
+
+        if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0)) {
+                /* As a special case, return /dev/null if we have been called for an empty data block */
+                r = open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+                if (r < 0)
+                        return -errno;
+
+                return r;
+        }
+
+        if ((flags & ACQUIRE_NO_MEMFD) == 0) {
+                fd = memfd_new("data-fd");
+                if (fd < 0)
+                        goto try_pipe;
+
+                n = write(fd, data, size);
+                if (n < 0)
+                        return -errno;
+                if ((size_t) n != size)
+                        return -EIO;
+
+                f = lseek(fd, 0, SEEK_SET);
+                if (f != 0)
+                        return -errno;
+
+                r = memfd_set_sealed(fd);
+                if (r < 0)
+                        return r;
+
+                return TAKE_FD(fd);
+        }
+
+try_pipe:
+        if ((flags & ACQUIRE_NO_PIPE) == 0) {
+                if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0)
+                        return -errno;
+
+                isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
+                if (isz < 0)
+                        return -errno;
+
+                if ((size_t) isz < size) {
+                        isz = (int) size;
+                        if (isz < 0 || (size_t) isz != size)
+                                return -E2BIG;
+
+                        /* Try to bump the pipe size */
+                        (void) fcntl(pipefds[1], F_SETPIPE_SZ, isz);
+
+                        /* See if that worked */
+                        isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
+                        if (isz < 0)
+                                return -errno;
+
+                        if ((size_t) isz < size)
+                                goto try_dev_shm;
+                }
+
+                n = write(pipefds[1], data, size);
+                if (n < 0)
+                        return -errno;
+                if ((size_t) n != size)
+                        return -EIO;
+
+                (void) fd_nonblock(pipefds[0], false);
+
+                return TAKE_FD(pipefds[0]);
+        }
+
+try_dev_shm:
+        if ((flags & ACQUIRE_NO_TMPFILE) == 0) {
+                fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500);
+                if (fd < 0)
+                        goto try_dev_shm_without_o_tmpfile;
+
+                n = write(fd, data, size);
+                if (n < 0)
+                        return -errno;
+                if ((size_t) n != size)
+                        return -EIO;
+
+                /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */
+                return fd_reopen(fd, O_RDONLY|O_CLOEXEC);
+        }
+
+try_dev_shm_without_o_tmpfile:
+        if ((flags & ACQUIRE_NO_REGULAR) == 0) {
+                fd = mkostemp_safe(pattern);
+                if (fd < 0)
+                        return fd;
+
+                n = write(fd, data, size);
+                if (n < 0) {
+                        r = -errno;
+                        goto unlink_and_return;
+                }
+                if ((size_t) n != size) {
+                        r = -EIO;
+                        goto unlink_and_return;
+                }
+
+                /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */
+                r = open(pattern, O_RDONLY|O_CLOEXEC);
+                if (r < 0)
+                        r = -errno;
+
+        unlink_and_return:
+                (void) unlink(pattern);
+                return r;
+        }
+
+        return -EOPNOTSUPP;
+}
+#endif /* NM_IGNORED */
+
+int fd_move_above_stdio(int fd) {
+        int flags, copy;
+        PROTECT_ERRNO;
+
+        /* Moves the specified file descriptor if possible out of the range [0…2], i.e. the range of
+         * stdin/stdout/stderr. If it can't be moved outside of this range the original file descriptor is
+         * returned. This call is supposed to be used for long-lasting file descriptors we allocate in our code that
+         * might get loaded into foreign code, and where we want ensure our fds are unlikely used accidentally as
+         * stdin/stdout/stderr of unrelated code.
+         *
+         * Note that this doesn't fix any real bugs, it just makes it less likely that our code will be affected by
+         * buggy code from others that mindlessly invokes 'fprintf(stderr, …' or similar in places where stderr has
+         * been closed before.
+         *
+         * This function is written in a "best-effort" and "least-impact" style. This means whenever we encounter an
+         * error we simply return the original file descriptor, and we do not touch errno. */
+
+        if (fd < 0 || fd > 2)
+                return fd;
+
+        flags = fcntl(fd, F_GETFD, 0);
+        if (flags < 0)
+                return fd;
+
+        if (flags & FD_CLOEXEC)
+                copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+        else
+                copy = fcntl(fd, F_DUPFD, 3);
+        if (copy < 0)
+                return fd;
+
+        assert(copy > 2);
+
+        (void) close(fd);
+        return copy;
+}
+
+#if 0 /* NM_IGNORED */
+int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd) {
+
+        int fd[3] = { /* Put together an array of fds we work on */
+                original_input_fd,
+                original_output_fd,
+                original_error_fd
+        };
+
+        int r, i,
+                null_fd = -1,                /* if we open /dev/null, we store the fd to it here */
+                copy_fd[3] = { -1, -1, -1 }; /* This contains all fds we duplicate here temporarily, and hence need to close at the end */
+        bool null_readable, null_writable;
+
+        /* Sets up stdin, stdout, stderr with the three file descriptors passed in. If any of the descriptors is
+         * specified as -1 it will be connected with /dev/null instead. If any of the file descriptors is passed as
+         * itself (e.g. stdin as STDIN_FILENO) it is left unmodified, but the O_CLOEXEC bit is turned off should it be
+         * on.
+         *
+         * Note that if any of the passed file descriptors are > 2 they will be closed — both on success and on
+         * failure! Thus, callers should assume that when this function returns the input fds are invalidated.
+         *
+         * Note that when this function fails stdin/stdout/stderr might remain half set up!
+         *
+         * O_CLOEXEC is turned off for all three file descriptors (which is how it should be for
+         * stdin/stdout/stderr). */
+
+        null_readable = original_input_fd < 0;
+        null_writable = original_output_fd < 0 || original_error_fd < 0;
+
+        /* First step, open /dev/null once, if we need it */
+        if (null_readable || null_writable) {
+
+                /* Let's open this with O_CLOEXEC first, and convert it to non-O_CLOEXEC when we move the fd to the final position. */
+                null_fd = open("/dev/null", (null_readable && null_writable ? O_RDWR :
+                                             null_readable ? O_RDONLY : O_WRONLY) | O_CLOEXEC);
+                if (null_fd < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                /* If this fd is in the 0…2 range, let's move it out of it */
+                if (null_fd < 3) {
+                        int copy;
+
+                        copy = fcntl(null_fd, F_DUPFD_CLOEXEC, 3); /* Duplicate this with O_CLOEXEC set */
+                        if (copy < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        safe_close(null_fd);
+                        null_fd = copy;
+                }
+        }
+
+        /* Let's assemble fd[] with the fds to install in place of stdin/stdout/stderr */
+        for (i = 0; i < 3; i++) {
+
+                if (fd[i] < 0)
+                        fd[i] = null_fd;        /* A negative parameter means: connect this one to /dev/null */
+                else if (fd[i] != i && fd[i] < 3) {
+                        /* This fd is in the 0…2 territory, but not at its intended place, move it out of there, so that we can work there. */
+                        copy_fd[i] = fcntl(fd[i], F_DUPFD_CLOEXEC, 3); /* Duplicate this with O_CLOEXEC set */
+                        if (copy_fd[i] < 0) {
+                                r = -errno;
+                                goto finish;
+                        }
+
+                        fd[i] = copy_fd[i];
+                }
+        }
+
+        /* At this point we now have the fds to use in fd[], and they are all above the stdio range, so that we
+         * have freedom to move them around. If the fds already were at the right places then the specific fds are
+         * -1. Let's now move them to the right places. This is the point of no return. */
+        for (i = 0; i < 3; i++) {
+
+                if (fd[i] == i) {
+
+                        /* fd is already in place, but let's make sure O_CLOEXEC is off */
+                        r = fd_cloexec(i, false);
+                        if (r < 0)
+                                goto finish;
+
+                } else {
+                        assert(fd[i] > 2);
+
+                        if (dup2(fd[i], i) < 0) { /* Turns off O_CLOEXEC on the new fd. */
+                                r = -errno;
+                                goto finish;
+                        }
+                }
+        }
+
+        r = 0;
+
+finish:
+        /* Close the original fds, but only if they were outside of the stdio range. Also, properly check for the same
+         * fd passed in multiple times. */
+        safe_close_above_stdio(original_input_fd);
+        if (original_output_fd != original_input_fd)
+                safe_close_above_stdio(original_output_fd);
+        if (original_error_fd != original_input_fd && original_error_fd != original_output_fd)
+                safe_close_above_stdio(original_error_fd);
+
+        /* Close the copies we moved > 2 */
+        for (i = 0; i < 3; i++)
+                safe_close(copy_fd[i]);
+
+        /* Close our null fd, if it's > 2 */
+        safe_close_above_stdio(null_fd);
+
+        return r;
+}
+
+int fd_reopen(int fd, int flags) {
+        char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+        int new_fd;
+
+        /* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to
+         * turn O_RDWR fds into O_RDONLY fds.
+         *
+         * This doesn't work on sockets (since they cannot be open()ed, ever).
+         *
+         * This implicitly resets the file read index to 0. */
+
+        xsprintf(procfs_path, "/proc/self/fd/%i", fd);
+        new_fd = open(procfs_path, flags);
+        if (new_fd < 0)
+                return -errno;
+
+        return new_fd;
+}
+#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/fd-util.h b/src/systemd/src/basic/fd-util.h
index 34b98d4a..163b096b 100644
--- a/src/systemd/src/basic/fd-util.h
+++ b/src/systemd/src/basic/fd-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -34,6 +35,13 @@ int close_nointr(int fd);
 int safe_close(int fd);
 void safe_close_pair(int p[]);
 
+static inline int safe_close_above_stdio(int fd) {
+        if (fd < 3) /* Don't close stdin/stdout/stderr, but still invalidate the fd by returning -1 */
+                return -1;
+
+        return safe_close(fd);
+}
+
 void close_many(const int fds[], unsigned n_fd);
 
 int fclose_nointr(FILE *f);
@@ -63,7 +71,6 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DIR*, closedir);
 
 int fd_nonblock(int fd, bool nonblock);
 int fd_cloexec(int fd, bool cloexec);
-void stdio_unset_cloexec(void);
 
 int close_all_fds(const int except[], unsigned n_except);
 
@@ -75,6 +82,36 @@ bool fdname_is_valid(const char *s);
 
 int fd_get_path(int fd, char **ret);
 
+int move_fd(int from, int to, int cloexec);
+
+enum {
+        ACQUIRE_NO_DEV_NULL = 1 << 0,
+        ACQUIRE_NO_MEMFD    = 1 << 1,
+        ACQUIRE_NO_PIPE     = 1 << 2,
+        ACQUIRE_NO_TMPFILE  = 1 << 3,
+        ACQUIRE_NO_REGULAR  = 1 << 4,
+};
+
+int acquire_data_fd(const void *data, size_t size, unsigned flags);
+
 /* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */
 #define ERRNO_IS_DISCONNECT(r) \
         IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH)
+
+int fd_move_above_stdio(int fd);
+
+int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd);
+
+static inline int make_null_stdio(void) {
+        return rearrange_stdio(-1, -1, -1);
+}
+
+/* Like TAKE_PTR() but for file descriptors, resetting them to -1 */
+#define TAKE_FD(fd)                             \
+        ({                                      \
+                int _fd_ = (fd);                \
+                (fd) = -1;                      \
+                _fd_;                           \
+        })
+
+int fd_reopen(int fd, int flags);
diff --git a/src/systemd/src/basic/fileio.c b/src/systemd/src/basic/fileio.c
index 51d1c052..c7b4b241 100644
--- a/src/systemd/src/basic/fileio.c
+++ b/src/systemd/src/basic/fileio.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -24,8 +25,10 @@
 #include <limits.h>
 #include <stdarg.h>
 #include <stdint.h>
+#include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -62,12 +65,30 @@ int write_string_stream_ts(
                 WriteStringFileFlags flags,
                 struct timespec *ts) {
 
+        bool needs_nl;
+
         assert(f);
         assert(line);
 
-        fputs(line, f);
-        if (!(flags & WRITE_STRING_FILE_AVOID_NEWLINE) && !endswith(line, "\n"))
-                fputc('\n', f);
+        if (ferror(f))
+                return -EIO;
+
+        needs_nl = !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) && !endswith(line, "\n");
+
+        if (needs_nl && (flags & WRITE_STRING_FILE_DISABLE_BUFFER)) {
+                /* If STDIO buffering was disabled, then let's append the newline character to the string itself, so
+                 * that the write goes out in one go, instead of two */
+
+                line = strjoina(line, "\n");
+                needs_nl = false;
+        }
+
+        if (fputs(line, f) == EOF)
+                return -errno;
+
+        if (needs_nl)
+                if (fputc('\n', f) == EOF)
+                        return -errno;
 
         if (ts) {
                 struct timespec twice[2] = {*ts, *ts};
@@ -99,6 +120,7 @@ static int write_string_file_atomic(
         if (r < 0)
                 return r;
 
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
         (void) fchmod_umask(fileno(f), 0644);
 
         r = write_string_stream_ts(f, line, flags, ts);
@@ -141,7 +163,7 @@ int write_string_file_ts(
 
                 return r;
         } else
-                assert(ts == NULL);
+                assert(!ts);
 
         if (flags & WRITE_STRING_FILE_CREATE) {
                 f = fopen(fn, "we");
@@ -168,6 +190,11 @@ int write_string_file_ts(
                 }
         }
 
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
+        if (flags & WRITE_STRING_FILE_DISABLE_BUFFER)
+                setvbuf(f, NULL, _IONBF, 0);
+
         r = write_string_stream_ts(f, line, flags, ts);
         if (r < 0)
                 goto fail;
@@ -201,10 +228,11 @@ int read_one_line_file(const char *fn, char **line) {
         if (!f)
                 return -errno;
 
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         r = read_line(f, LONG_LINE_MAX, line);
         return r < 0 ? r : 0;
 }
-#endif /* NM_IGNORED */
 
 int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
         _cleanup_fclose_ FILE *f = NULL;
@@ -227,6 +255,8 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
         if (!f)
                 return -errno;
 
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         /* We try to read one byte more than we need, so that we know whether we hit eof */
         errno = 0;
         k = fread(buf, 1, l + accept_extra_nl + 1, f);
@@ -242,6 +272,7 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
 
         return 1;
 }
+#endif /* NM_IGNORED */
 
 int read_full_stream(FILE *f, char **contents, size_t *size) {
         size_t n, l;
@@ -303,8 +334,7 @@ int read_full_stream(FILE *f, char **contents, size_t *size) {
         }
 
         buf[l] = 0;
-        *contents = buf;
-        buf = NULL; /* do not free */
+        *contents = TAKE_PTR(buf);
 
         if (size)
                 *size = l;
@@ -322,6 +352,8 @@ int read_full_file(const char *fn, char **contents, size_t *size) {
         if (!f)
                 return -errno;
 
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         return read_full_stream(f, contents, size);
 }
 
@@ -334,11 +366,11 @@ static int parse_env_file_internal(
                 void *userdata,
                 int *n_pushed) {
 
-        _cleanup_free_ char *contents = NULL, *key = NULL;
         size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_value_whitespace = (size_t) -1, last_key_whitespace = (size_t) -1;
-        char *p, *value = NULL;
-        int r;
+        _cleanup_free_ char *contents = NULL, *key = NULL, *value = NULL;
         unsigned line = 1;
+        char *p;
+        int r;
 
         enum {
                 PRE_KEY,
@@ -375,10 +407,8 @@ static int parse_env_file_internal(
                                 state = KEY;
                                 last_key_whitespace = (size_t) -1;
 
-                                if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
+                                if (!GREEDY_REALLOC(key, key_alloc, n_key+2))
+                                        return -ENOMEM;
 
                                 key[n_key++] = c;
                         }
@@ -398,10 +428,8 @@ static int parse_env_file_internal(
                                 else if (last_key_whitespace == (size_t) -1)
                                          last_key_whitespace = n_key;
 
-                                if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
+                                if (!GREEDY_REALLOC(key, key_alloc, n_key+2))
+                                        return -ENOMEM;
 
                                 key[n_key++] = c;
                         }
@@ -423,7 +451,7 @@ static int parse_env_file_internal(
 
                                 r = push(fname, line, key, value, userdata, n_pushed);
                                 if (r < 0)
-                                        goto fail;
+                                        return r;
 
                                 n_key = 0;
                                 value = NULL;
@@ -438,10 +466,8 @@ static int parse_env_file_internal(
                         else if (!strchr(WHITESPACE, c)) {
                                 state = VALUE;
 
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return  -ENOMEM;
 
                                 value[n_value++] = c;
                         }
@@ -468,7 +494,7 @@ static int parse_env_file_internal(
 
                                 r = push(fname, line, key, value, userdata, n_pushed);
                                 if (r < 0)
-                                        goto fail;
+                                        return r;
 
                                 n_key = 0;
                                 value = NULL;
@@ -483,10 +509,8 @@ static int parse_env_file_internal(
                                 else if (last_value_whitespace == (size_t) -1)
                                         last_value_whitespace = n_value;
 
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return -ENOMEM;
 
                                 value[n_value++] = c;
                         }
@@ -498,10 +522,8 @@ static int parse_env_file_internal(
 
                         if (!strchr(newline, c)) {
                                 /* Escaped newlines we eat up entirely */
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return -ENOMEM;
 
                                 value[n_value++] = c;
                         }
@@ -513,10 +535,8 @@ static int parse_env_file_internal(
                         else if (c == '\\')
                                 state = SINGLE_QUOTE_VALUE_ESCAPE;
                         else {
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return -ENOMEM;
 
                                 value[n_value++] = c;
                         }
@@ -527,10 +547,8 @@ static int parse_env_file_internal(
                         state = SINGLE_QUOTE_VALUE;
 
                         if (!strchr(newline, c)) {
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return -ENOMEM;
 
                                 value[n_value++] = c;
                         }
@@ -542,10 +560,8 @@ static int parse_env_file_internal(
                         else if (c == '\\')
                                 state = DOUBLE_QUOTE_VALUE_ESCAPE;
                         else {
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return -ENOMEM;
 
                                 value[n_value++] = c;
                         }
@@ -556,10 +572,8 @@ static int parse_env_file_internal(
                         state = DOUBLE_QUOTE_VALUE;
 
                         if (!strchr(newline, c)) {
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
+                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
+                                        return -ENOMEM;
 
                                 value[n_value++] = c;
                         }
@@ -604,14 +618,12 @@ static int parse_env_file_internal(
 
                 r = push(fname, line, key, value, userdata, n_pushed);
                 if (r < 0)
-                        goto fail;
+                        return r;
+
+                value = NULL;
         }
 
         return 0;
-
-fail:
-        free(value);
-        return r;
 }
 
 static int check_utf8ness_and_warn(
@@ -879,7 +891,8 @@ int write_env_file(const char *fname, char **l) {
         if (r < 0)
                 return r;
 
-        fchmod_umask(fileno(f), 0644);
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        (void) fchmod_umask(fileno(f), 0644);
 
         STRV_FOREACH(i, l)
                 write_env_var(f, *i);
@@ -897,14 +910,16 @@ int write_env_file(const char *fname, char **l) {
 }
 
 int executable_is_script(const char *path, char **interpreter) {
-        int r;
         _cleanup_free_ char *line = NULL;
-        int len;
+        size_t len;
         char *ans;
+        int r;
 
         assert(path);
 
         r = read_one_line_file(path, &line);
+        if (r == -ENOBUFS) /* First line overly long? if so, then it's not a script */
+                return 0;
         if (r < 0)
                 return r;
 
@@ -1147,6 +1162,7 @@ int fflush_and_check(FILE *f) {
         return 0;
 }
 
+#if 0 /* NM_IGNORED */
 int fflush_sync_and_check(FILE *f) {
         int r;
 
@@ -1159,8 +1175,13 @@ int fflush_sync_and_check(FILE *f) {
         if (fsync(fileno(f)) < 0)
                 return -errno;
 
+        r = fsync_directory_of_file(fileno(f));
+        if (r < 0)
+                return r;
+
         return 0;
 }
+#endif /* NM_IGNORED */
 
 /* This is much like mkostemp() but is subject to umask(). */
 int mkostemp_safe(char *pattern) {
@@ -1197,8 +1218,7 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
         if (!filename_is_valid(fn))
                 return -EINVAL;
 
-        if (extra == NULL)
-                extra = "";
+        extra = strempty(extra);
 
         t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1);
         if (!t)
@@ -1232,8 +1252,7 @@ int tempfn_random(const char *p, const char *extra, char **ret) {
         if (!filename_is_valid(fn))
                 return -EINVAL;
 
-        if (!extra)
-                extra = "";
+        extra = strempty(extra);
 
         t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1);
         if (!t)
@@ -1273,8 +1292,7 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) {
                         return r;
         }
 
-        if (!extra)
-                extra = "";
+        extra = strempty(extra);
 
         t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1);
         if (!t)
@@ -1426,8 +1444,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
         if (fd < 0)
                 return -errno;
 
-        *ret_path = tmp;
-        tmp = NULL;
+        *ret_path = TAKE_PTR(tmp);
 
         return fd;
 }
@@ -1467,7 +1484,7 @@ int link_tmpfile(int fd, const char *path, const char *target) {
                 if (rename_noreplace(AT_FDCWD, path, AT_FDCWD, target) < 0)
                         return -errno;
         } else {
-                char proc_fd_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
+                char proc_fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
 
                 xsprintf(proc_fd_path, "/proc/self/fd/%i", fd);
 
@@ -1513,8 +1530,7 @@ int read_nul_string(FILE *f, char **ret) {
                         return -ENOMEM;
         }
 
-        *ret = x;
-        x = NULL;
+        *ret = TAKE_PTR(x);
 
         return 0;
 }
@@ -1538,9 +1554,7 @@ int mkdtemp_malloc(const char *template, char **ret) {
         return 0;
 }
 
-static inline void funlockfilep(FILE **f) {
-        funlockfile(*f);
-}
+DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile);
 
 int read_line(FILE *f, size_t limit, char **ret) {
         _cleanup_free_ char *buffer = NULL;
@@ -1567,7 +1581,7 @@ int read_line(FILE *f, size_t limit, char **ret) {
         }
 
         {
-                _cleanup_(funlockfilep) FILE *flocked = f;
+                _unused_ _cleanup_(funlockfilep) FILE *flocked = f;
                 flockfile(f);
 
                 for (;;) {
diff --git a/src/systemd/src/basic/fileio.h b/src/systemd/src/basic/fileio.h
index eba05be2..da5d5c66 100644
--- a/src/systemd/src/basic/fileio.h
+++ b/src/systemd/src/basic/fileio.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -29,11 +30,17 @@
 #include "time-util.h"
 
 typedef enum {
-        WRITE_STRING_FILE_CREATE = 1<<0,
-        WRITE_STRING_FILE_ATOMIC = 1<<1,
-        WRITE_STRING_FILE_AVOID_NEWLINE = 1<<2,
+        WRITE_STRING_FILE_CREATE            = 1<<0,
+        WRITE_STRING_FILE_ATOMIC            = 1<<1,
+        WRITE_STRING_FILE_AVOID_NEWLINE     = 1<<2,
         WRITE_STRING_FILE_VERIFY_ON_FAILURE = 1<<3,
-        WRITE_STRING_FILE_SYNC = 1<<4,
+        WRITE_STRING_FILE_SYNC              = 1<<4,
+        WRITE_STRING_FILE_DISABLE_BUFFER    = 1<<5,
+
+        /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one
+           more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file()
+           and friends. */
+
 } WriteStringFileFlags;
 
 int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags, struct timespec *ts);
diff --git a/src/systemd/src/basic/fs-util.c b/src/systemd/src/basic/fs-util.c
index ff4ad5ab..ffd9996a 100644
--- a/src/systemd/src/basic/fs-util.c
+++ b/src/systemd/src/basic/fs-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -40,6 +41,7 @@
 #include "mkdir.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "process-util.h"
 #include "stat-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
@@ -107,7 +109,6 @@ int rmdir_parents(const char *path, const char *stop) {
         return 0;
 }
 
-
 int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
         struct stat buf;
         int ret;
@@ -229,49 +230,6 @@ int readlink_and_make_absolute(const char *p, char **r) {
         return 0;
 }
 
-int readlink_and_canonicalize(const char *p, const char *root, char **ret) {
-        char *t, *s;
-        int r;
-
-        assert(p);
-        assert(ret);
-
-        r = readlink_and_make_absolute(p, &t);
-        if (r < 0)
-                return r;
-
-        r = chase_symlinks(t, root, 0, &s);
-        if (r < 0)
-                /* If we can't follow up, then let's return the original string, slightly cleaned up. */
-                *ret = path_kill_slashes(t);
-        else {
-                *ret = s;
-                free(t);
-        }
-
-        return 0;
-}
-
-int readlink_and_make_absolute_root(const char *root, const char *path, char **ret) {
-        _cleanup_free_ char *target = NULL, *t = NULL;
-        const char *full;
-        int r;
-
-        full = prefix_roota(root, path);
-        r = readlink_malloc(full, &target);
-        if (r < 0)
-                return r;
-
-        t = file_in_same_dir(path, target);
-        if (!t)
-                return -ENOMEM;
-
-        *ret = t;
-        t = NULL;
-
-        return 0;
-}
-
 int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
         assert(path);
 
@@ -322,43 +280,60 @@ int fd_warn_permissions(const char *path, int fd) {
 }
 
 int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
-        _cleanup_close_ int fd;
-        int r;
+        char fdpath[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+        _cleanup_close_ int fd = -1;
+        int r, ret = 0;
 
         assert(path);
 
-        if (parents)
-                mkdir_parents(path, 0755);
-
-        fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY,
-                  IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode);
-        if (fd < 0)
-                return -errno;
+        /* Note that touch_file() does not follow symlinks: if invoked on an existing symlink, then it is the symlink
+         * itself which is updated, not its target
+         *
+         * Returns the first error we encounter, but tries to apply as much as possible. */
 
-        if (mode != MODE_INVALID) {
-                r = fchmod(fd, mode);
-                if (r < 0)
+        if (parents)
+                (void) mkdir_parents(path, 0755);
+
+        /* Initially, we try to open the node with O_PATH, so that we get a reference to the node. This is useful in
+         * case the path refers to an existing device or socket node, as we can open it successfully in all cases, and
+         * won't trigger any driver magic or so. */
+        fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
+        if (fd < 0) {
+                if (errno != ENOENT)
                         return -errno;
-        }
 
-        if (uid != UID_INVALID || gid != GID_INVALID) {
-                r = fchown(fd, uid, gid);
-                if (r < 0)
+                /* if the node doesn't exist yet, we create it, but with O_EXCL, so that we only create a regular file
+                 * here, and nothing else */
+                fd = open(path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode);
+                if (fd < 0)
                         return -errno;
         }
 
+        /* Let's make a path from the fd, and operate on that. With this logic, we can adjust the access mode,
+         * ownership and time of the file node in all cases, even if the fd refers to an O_PATH object — which is
+         * something fchown(), fchmod(), futimensat() don't allow. */
+        xsprintf(fdpath, "/proc/self/fd/%i", fd);
+
+        if (mode != MODE_INVALID)
+                if (chmod(fdpath, mode) < 0)
+                        ret = -errno;
+
+        if (uid_is_valid(uid) || gid_is_valid(gid))
+                if (chown(fdpath, uid, gid) < 0 && ret >= 0)
+                        ret = -errno;
+
         if (stamp != USEC_INFINITY) {
                 struct timespec ts[2];
 
                 timespec_store(&ts[0], stamp);
                 ts[1] = ts[0];
-                r = futimens(fd, ts);
+                r = utimensat(AT_FDCWD, fdpath, ts, 0);
         } else
-                r = futimens(fd, NULL);
-        if (r < 0)
+                r = utimensat(AT_FDCWD, fdpath, NULL, 0);
+        if (r < 0 && ret >= 0)
                 return -errno;
 
-        return 0;
+        return ret;
 }
 
 int touch(const char *path) {
@@ -490,10 +465,8 @@ int get_files_in_directory(const char *path, char ***list) {
                         n++;
         }
 
-        if (list) {
-                *list = l;
-                l = NULL; /* avoid freeing */
-        }
+        if (list)
+                *list = TAKE_PTR(l);
 
         return n;
 }
@@ -516,7 +489,7 @@ static int getenv_tmp_dir(const char **ret_path) {
                         r = -ENOTDIR;
                         goto next;
                 }
-                if (!path_is_safe(e)) {
+                if (!path_is_normalized(e)) {
                         r = -EPERM;
                         goto next;
                 }
@@ -586,8 +559,19 @@ int tmp_dir(const char **ret) {
         return tmp_dir_internal("/tmp", ret);
 }
 
+int unlink_or_warn(const char *filename) {
+        if (unlink(filename) < 0 && errno != ENOENT)
+                /* If the file doesn't exist and the fs simply was read-only (in which
+                 * case unlink() returns EROFS even if the file doesn't exist), don't
+                 * complain */
+                if (errno != EROFS || access(filename, F_OK) >= 0)
+                        return log_error_errno(errno, "Failed to remove \"%s\": %m", filename);
+
+        return 0;
+}
+
 int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
-        char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
+        char path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
         int r;
 
         /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */
@@ -600,16 +584,39 @@ int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
         return r;
 }
 
+static bool noop_root(const char *root) {
+        return isempty(root) || path_equal(root, "/");
+}
+
+static bool safe_transition(const struct stat *a, const struct stat *b) {
+        /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
+         * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files
+         * making us believe we read something safe even though it isn't safe in the specific context we open it in. */
+
+        if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */
+                return true;
+
+        return a->st_uid == b->st_uid; /* Otherwise we need to stay within the same UID */
+}
+
 int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) {
         _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
         _cleanup_close_ int fd = -1;
         unsigned max_follow = 32; /* how many symlinks to follow before giving up and returning ELOOP */
+        struct stat previous_stat;
         bool exists = true;
         char *todo;
         int r;
 
         assert(path);
 
+        /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
+        if ((flags & (CHASE_NONEXISTENT|CHASE_OPEN)) == (CHASE_NONEXISTENT|CHASE_OPEN))
+                return -EINVAL;
+
+        if (isempty(path))
+                return -EINVAL;
+
         /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following
          * symlinks relative to a root directory, instead of the root of the host.
          *
@@ -628,18 +635,35 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
          * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got
          * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this
          * function what to do when encountering a symlink with an absolute path as directory: prefix it by the
-         * specified path.
-         *
-         * Note: there's also chase_symlinks_prefix() (see below), which as first step prefixes the passed path by the
-         * passed root. */
+         * specified path. */
+
+        /* A root directory of "/" or "" is identical to none */
+        if (noop_root(original_root))
+                original_root = NULL;
+
+        if (!original_root && !ret && (flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_OPEN)) == CHASE_OPEN) {
+                /* Shortcut the CHASE_OPEN case if the caller isn't interested in the actual path and has no root set
+                 * and doesn't care about any of the other special features we provide either. */
+                r = open(path, O_PATH|O_CLOEXEC);
+                if (r < 0)
+                        return -errno;
+
+                return r;
+        }
 
         if (original_root) {
                 r = path_make_absolute_cwd(original_root, &root);
                 if (r < 0)
                         return r;
 
-                if (flags & CHASE_PREFIX_ROOT)
+                if (flags & CHASE_PREFIX_ROOT) {
+
+                        /* We don't support relative paths in combination with a root directory */
+                        if (!path_is_absolute(path))
+                                return -EINVAL;
+
                         path = prefix_roota(root, path);
+                }
         }
 
         r = path_make_absolute_cwd(path, &buffer);
@@ -650,6 +674,11 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
         if (fd < 0)
                 return -errno;
 
+        if (flags & CHASE_SAFE) {
+                if (fstat(fd, &previous_stat) < 0)
+                        return -errno;
+        }
+
         todo = buffer;
         for (;;) {
                 _cleanup_free_ char *first = NULL;
@@ -668,9 +697,20 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
 
                 todo += m;
 
+                /* Empty? Then we reached the end. */
+                if (isempty(first))
+                        break;
+
                 /* Just a single slash? Then we reached the end. */
-                if (isempty(first) || path_equal(first, "/"))
+                if (path_equal(first, "/")) {
+                        /* Preserve the trailing slash */
+
+                        if (flags & CHASE_TRAIL_SLASH)
+                                if (!strextend(&done, "/", NULL))
+                                        return -ENOMEM;
+
                         break;
+                }
 
                 /* Just a dot? Then let's eat this up. */
                 if (path_equal(first, "/."))
@@ -679,7 +719,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                 /* Two dots? Then chop off the last bit of what we already found out. */
                 if (path_equal(first, "/..")) {
                         _cleanup_free_ char *parent = NULL;
-                        int fd_parent = -1;
+                        _cleanup_close_ int fd_parent = -1;
 
                         /* If we already are at the top, then going up will not change anything. This is in-line with
                          * how the kernel handles this. */
@@ -702,8 +742,18 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                         if (fd_parent < 0)
                                 return -errno;
 
+                        if (flags & CHASE_SAFE) {
+                                if (fstat(fd_parent, &st) < 0)
+                                        return -errno;
+
+                                if (!safe_transition(&previous_stat, &st))
+                                        return -EPERM;
+
+                                previous_stat = st;
+                        }
+
                         safe_close(fd);
-                        fd = fd_parent;
+                        fd = TAKE_FD(fd_parent);
 
                         continue;
                 }
@@ -714,12 +764,16 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
 
                         if (errno == ENOENT &&
                             (flags & CHASE_NONEXISTENT) &&
-                            (isempty(todo) || path_is_safe(todo))) {
+                            (isempty(todo) || path_is_normalized(todo))) {
 
                                 /* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return
                                  * what we got so far. But don't allow this if the remaining path contains "../ or "./"
                                  * or something else weird. */
 
+                                /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
+                                if (streq_ptr(done, "/"))
+                                        *done = '\0';
+
                                 if (!strextend(&done, first, todo, NULL))
                                         return -ENOMEM;
 
@@ -732,8 +786,14 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
 
                 if (fstat(child, &st) < 0)
                         return -errno;
+                if ((flags & CHASE_SAFE) &&
+                    !safe_transition(&previous_stat, &st))
+                        return -EPERM;
+
+                previous_stat = st;
+
                 if ((flags & CHASE_NO_AUTOFS) &&
-                    fd_check_fstype(child, AUTOFS_SUPER_MAGIC) > 0)
+                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
                         return -EREMOTE;
 
                 if (S_ISLNK(st.st_mode)) {
@@ -762,6 +822,16 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                                 if (fd < 0)
                                         return -errno;
 
+                                if (flags & CHASE_SAFE) {
+                                        if (fstat(fd, &st) < 0)
+                                                return -errno;
+
+                                        if (!safe_transition(&previous_stat, &st))
+                                                return -EPERM;
+
+                                        previous_stat = st;
+                                }
+
                                 free(done);
 
                                 /* Note that we do not revalidate the root, we take it as is. */
@@ -773,12 +843,11 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                                                 return -ENOMEM;
                                 }
 
-                        }
-
-                        /* Prefix what's left to do with what we just read, and start the loop again,
-                         * but remain in the current directory. */
-
-                        joined = strjoin("/", destination, todo);
+                                /* Prefix what's left to do with what we just read, and start the loop again, but
+                                 * remain in the current directory. */
+                                joined = strjoin(destination, todo);
+                        } else
+                                joined = strjoin("/", destination, todo);
                         if (!joined)
                                 return -ENOMEM;
 
@@ -789,18 +858,20 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                 }
 
                 /* If this is not a symlink, then let's just add the name we read to what we already verified. */
-                if (!done) {
-                        done = first;
-                        first = NULL;
-                } else {
+                if (!done)
+                        done = TAKE_PTR(first);
+                else {
+                        /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
+                        if (streq(done, "/"))
+                                *done = '\0';
+
                         if (!strextend(&done, first, NULL))
                                 return -ENOMEM;
                 }
 
                 /* And iterate again, but go one directory further down. */
                 safe_close(fd);
-                fd = child;
-                child = -1;
+                fd = TAKE_FD(child);
         }
 
         if (!done) {
@@ -810,11 +881,221 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                         return -ENOMEM;
         }
 
-        if (ret) {
-                *ret = done;
-                done = NULL;
+        if (ret)
+                *ret = TAKE_PTR(done);
+
+        if (flags & CHASE_OPEN) {
+                /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a proper fd by
+                 * opening /proc/self/fd/xyz. */
+
+                assert(fd >= 0);
+                return TAKE_FD(fd);
         }
 
         return exists;
 }
+
+int chase_symlinks_and_open(
+                const char *path,
+                const char *root,
+                unsigned chase_flags,
+                int open_flags,
+                char **ret_path) {
+
+        _cleanup_close_ int path_fd = -1;
+        _cleanup_free_ char *p = NULL;
+        int r;
+
+        if (chase_flags & CHASE_NONEXISTENT)
+                return -EINVAL;
+
+        if (noop_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) {
+                /* Shortcut this call if none of the special features of this call are requested */
+                r = open(path, open_flags);
+                if (r < 0)
+                        return -errno;
+
+                return r;
+        }
+
+        path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
+        if (path_fd < 0)
+                return path_fd;
+
+        r = fd_reopen(path_fd, open_flags);
+        if (r < 0)
+                return r;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
+
+        return r;
+}
+
+int chase_symlinks_and_opendir(
+                const char *path,
+                const char *root,
+                unsigned chase_flags,
+                char **ret_path,
+                DIR **ret_dir) {
+
+        char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+        _cleanup_close_ int path_fd = -1;
+        _cleanup_free_ char *p = NULL;
+        DIR *d;
+
+        if (!ret_dir)
+                return -EINVAL;
+        if (chase_flags & CHASE_NONEXISTENT)
+                return -EINVAL;
+
+        if (noop_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) {
+                /* Shortcut this call if none of the special features of this call are requested */
+                d = opendir(path);
+                if (!d)
+                        return -errno;
+
+                *ret_dir = d;
+                return 0;
+        }
+
+        path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
+        if (path_fd < 0)
+                return path_fd;
+
+        xsprintf(procfs_path, "/proc/self/fd/%i", path_fd);
+        d = opendir(procfs_path);
+        if (!d)
+                return -errno;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(p);
+
+        *ret_dir = d;
+        return 0;
+}
+
+int access_fd(int fd, int mode) {
+        char p[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
+        int r;
+
+        /* Like access() but operates on an already open fd */
+
+        xsprintf(p, "/proc/self/fd/%i", fd);
+        r = access(p, mode);
+        if (r < 0)
+                return -errno;
+
+        return r;
+}
+
+int unlinkat_deallocate(int fd, const char *name, int flags) {
+        _cleanup_close_ int truncate_fd = -1;
+        struct stat st;
+        off_t l, bs;
+
+        /* Operates like unlinkat() but also deallocates the file contents if it is a regular file and there's no other
+         * link to it. This is useful to ensure that other processes that might have the file open for reading won't be
+         * able to keep the data pinned on disk forever. This call is particular useful whenever we execute clean-up
+         * jobs ("vacuuming"), where we want to make sure the data is really gone and the disk space released and
+         * returned to the free pool.
+         *
+         * Deallocation is preferably done by FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE (👊) if supported, which means
+         * the file won't change size. That's a good thing since we shouldn't needlessly trigger SIGBUS in other
+         * programs that have mmap()ed the file. (The assumption here is that changing file contents to all zeroes
+         * underneath those programs is the better choice than simply triggering SIGBUS in them which truncation does.)
+         * However if hole punching is not implemented in the kernel or file system we'll fall back to normal file
+         * truncation (🔪), as our goal of deallocating the data space trumps our goal of being nice to readers (💐).
+         *
+         * Note that we attempt deallocation, but failure to succeed with that is not considered fatal, as long as the
+         * primary job – to delete the file – is accomplished. */
+
+        if ((flags & AT_REMOVEDIR) == 0) {
+                truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
+                if (truncate_fd < 0) {
+
+                        /* If this failed because the file doesn't exist propagate the error right-away. Also,
+                         * AT_REMOVEDIR wasn't set, and we tried to open the file for writing, which means EISDIR is
+                         * returned when this is a directory but we are not supposed to delete those, hence propagate
+                         * the error right-away too. */
+                        if (IN_SET(errno, ENOENT, EISDIR))
+                                return -errno;
+
+                        if (errno != ELOOP) /* don't complain if this is a symlink */
+                                log_debug_errno(errno, "Failed to open file '%s' for deallocation, ignoring: %m", name);
+                }
+        }
+
+        if (unlinkat(fd, name, flags) < 0)
+                return -errno;
+
+        if (truncate_fd < 0) /* Don't have a file handle, can't do more ☹️ */
+                return 0;
+
+        if (fstat(truncate_fd, &st) < 0) {
+                log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring.", name);
+                return 0;
+        }
+
+        if (!S_ISREG(st.st_mode) || st.st_blocks == 0 || st.st_nlink > 0)
+                return 0;
+
+        /* If this is a regular file, it actually took up space on disk and there are no other links it's time to
+         * punch-hole/truncate this to release the disk space. */
+
+        bs = MAX(st.st_blksize, 512);
+        l = DIV_ROUND_UP(st.st_size, bs) * bs; /* Round up to next block size */
+
+        if (fallocate(truncate_fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, 0, l) >= 0)
+                return 0; /* Successfully punched a hole! 😊 */
+
+        /* Fall back to truncation */
+        if (ftruncate(truncate_fd, 0) < 0) {
+                log_debug_errno(errno, "Failed to truncate file to 0, ignoring: %m");
+                return 0;
+        }
+
+        return 0;
+}
+
+int fsync_directory_of_file(int fd) {
+        _cleanup_free_ char *path = NULL, *dn = NULL;
+        _cleanup_close_ int dfd = -1;
+        int r;
+
+        r = fd_verify_regular(fd);
+        if (r < 0)
+                return r;
+
+        r = fd_get_path(fd, &path);
+        if (r < 0) {
+                log_debug("Failed to query /proc/self/fd/%d%s: %m",
+                          fd,
+                          r == -EOPNOTSUPP ? ", ignoring" : "");
+
+                if (r == -EOPNOTSUPP)
+                        /* If /proc is not available, we're most likely running in some
+                         * chroot environment, and syncing the directory is not very
+                         * important in that case. Let's just silently do nothing. */
+                        return 0;
+
+                return r;
+        }
+
+        if (!path_is_absolute(path))
+                return -EINVAL;
+
+        dn = dirname_malloc(path);
+        if (!dn)
+                return -ENOMEM;
+
+        dfd = open(dn, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+        if (dfd < 0)
+                return -errno;
+
+        if (fsync(dfd) < 0)
+                return -errno;
+
+        return 0;
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/fs-util.h b/src/systemd/src/basic/fs-util.h
index d3342d5c..2225b7e7 100644
--- a/src/systemd/src/basic/fs-util.h
+++ b/src/systemd/src/basic/fs-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -19,6 +20,7 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <dirent.h>
 #include <fcntl.h>
 #include <limits.h>
 #include <stdbool.h>
@@ -28,6 +30,7 @@
 #include <unistd.h>
 
 #include "time-util.h"
+#include "util.h"
 
 int unlink_noerrno(const char *path);
 
@@ -39,8 +42,6 @@ int readlinkat_malloc(int fd, const char *p, char **ret);
 int readlink_malloc(const char *p, char **r);
 int readlink_value(const char *p, char **ret);
 int readlink_and_make_absolute(const char *p, char **r);
-int readlink_and_canonicalize(const char *p, const char *root, char **r);
-int readlink_and_make_absolute_root(const char *root, const char *path, char **ret);
 
 int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
 
@@ -64,6 +65,8 @@ int get_files_in_directory(const char *path, char ***list);
 int tmp_dir(const char **ret);
 int var_tmp_dir(const char **ret);
 
+int unlink_or_warn(const char *filename);
+
 #define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1)
 
 #define FOREACH_INOTIFY_EVENT(e, buffer, sz) \
@@ -79,22 +82,35 @@ union inotify_event_buffer {
 int inotify_add_watch_fd(int fd, int what, uint32_t mask);
 
 enum {
-        CHASE_PREFIX_ROOT = 1,   /* If set, the specified path will be prefixed by the specified root before beginning the iteration */
-        CHASE_NONEXISTENT = 2,   /* If set, it's OK if the path doesn't actually exist. */
-        CHASE_NO_AUTOFS = 4,     /* If set, return -EREMOTE if autofs mount point found */
+        CHASE_PREFIX_ROOT = 1U << 0,   /* If set, the specified path will be prefixed by the specified root before beginning the iteration */
+        CHASE_NONEXISTENT = 1U << 1,   /* If set, it's OK if the path doesn't actually exist. */
+        CHASE_NO_AUTOFS   = 1U << 2,   /* If set, return -EREMOTE if autofs mount point found */
+        CHASE_SAFE        = 1U << 3,   /* If set, return EPERM if we ever traverse from unprivileged to privileged files or directories */
+        CHASE_OPEN        = 1U << 4,   /* If set, return an O_PATH object to the final component */
+        CHASE_TRAIL_SLASH = 1U << 5,   /* If set, any trailing slash will be preserved */
 };
 
 int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret);
 
+int chase_symlinks_and_open(const char *path, const char *root, unsigned chase_flags, int open_flags, char **ret_path);
+int chase_symlinks_and_opendir(const char *path, const char *root, unsigned chase_flags, char **ret_path, DIR **ret_dir);
+
 /* Useful for usage with _cleanup_(), removes a directory and frees the pointer */
 static inline void rmdir_and_free(char *p) {
+        PROTECT_ERRNO;
         (void) rmdir(p);
         free(p);
 }
 DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rmdir_and_free);
 
 static inline void unlink_and_free(char *p) {
-        (void) unlink(p);
+        (void) unlink_noerrno(p);
         free(p);
 }
 DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free);
+
+int access_fd(int fd, int mode);
+
+int unlinkat_deallocate(int fd, const char *name, int flags);
+
+int fsync_directory_of_file(int fd);
diff --git a/src/systemd/src/basic/hash-funcs.c b/src/systemd/src/basic/hash-funcs.c
index 4a29f43c..234565ed 100644
--- a/src/systemd/src/basic/hash-funcs.c
+++ b/src/systemd/src/basic/hash-funcs.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -20,12 +21,16 @@
 
 #include "nm-sd-adapt.h"
 
+#include <string.h>
+
 #include "hash-funcs.h"
+#include "path-util.h"
 
 void string_hash_func(const void *p, struct siphash *state) {
         siphash24_compress(p, strlen(p) + 1, state);
 }
 
+#if 0 /* NM_IGNORED */
 int string_compare_func(const void *a, const void *b) {
         return strcmp(a, b);
 }
@@ -35,6 +40,56 @@ const struct hash_ops string_hash_ops = {
         .compare = string_compare_func
 };
 
+
+void path_hash_func(const void *p, struct siphash *state) {
+        const char *q = p;
+        size_t n;
+
+        assert(q);
+        assert(state);
+
+        /* Calculates a hash for a path in a way this duplicate inner slashes don't make a differences, and also
+         * whether there's a trailing slash or not. This fits well with the semantics of path_compare(), which does
+         * similar checks and also doesn't care for trailing slashes. Note that relative and absolute paths (i.e. those
+         * which begin in a slash or not) will hash differently though. */
+
+        n = strspn(q, "/");
+        if (n > 0) { /* Eat up initial slashes, and add one "/" to the hash for all of them */
+                siphash24_compress(q, 1, state);
+                q += n;
+        }
+
+        for (;;) {
+                /* Determine length of next component */
+                n = strcspn(q, "/");
+                if (n == 0) /* Reached the end? */
+                        break;
+
+                /* Add this component to the hash and skip over it */
+                siphash24_compress(q, n, state);
+                q += n;
+
+                /* How many slashes follow this component? */
+                n = strspn(q, "/");
+                if (q[n] == 0) /* Is this a trailing slash? If so, we are at the end, and don't care about the slashes anymore */
+                        break;
+
+                /* We are not add the end yet. Hash exactly one slash for all of the ones we just encountered. */
+                siphash24_compress(q, 1, state);
+                q += n;
+        }
+}
+
+int path_compare_func(const void *a, const void *b) {
+        return path_compare(a, b);
+}
+
+const struct hash_ops path_hash_ops = {
+        .hash = path_hash_func,
+        .compare = path_compare_func
+};
+#endif /* NM_IGNORED */
+
 void trivial_hash_func(const void *p, struct siphash *state) {
         siphash24_compress(&p, sizeof(p), state);
 }
@@ -64,6 +119,7 @@ const struct hash_ops uint64_hash_ops = {
         .compare = uint64_compare_func
 };
 
+#if 0 /* NM_IGNORED */
 #if SIZEOF_DEV_T != 8
 void devt_hash_func(const void *p, struct siphash *state) {
         siphash24_compress(p, sizeof(dev_t), state);
@@ -81,3 +137,4 @@ const struct hash_ops devt_hash_ops = {
         .compare = devt_compare_func
 };
 #endif
+#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/hash-funcs.h b/src/systemd/src/basic/hash-funcs.h
index 299189d1..945b4c25 100644
--- a/src/systemd/src/basic/hash-funcs.h
+++ b/src/systemd/src/basic/hash-funcs.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -35,29 +36,28 @@ void string_hash_func(const void *p, struct siphash *state);
 int string_compare_func(const void *a, const void *b) _pure_;
 extern const struct hash_ops string_hash_ops;
 
-/* This will compare the passed pointers directly, and will not
- * dereference them. This is hence not useful for strings or
- * suchlike. */
+void path_hash_func(const void *p, struct siphash *state);
+int path_compare_func(const void *a, const void *b) _pure_;
+extern const struct hash_ops path_hash_ops;
+
+/* This will compare the passed pointers directly, and will not dereference them. This is hence not useful for strings
+ * or suchlike. */
 void trivial_hash_func(const void *p, struct siphash *state);
 int trivial_compare_func(const void *a, const void *b) _const_;
 extern const struct hash_ops trivial_hash_ops;
 
-/* 32bit values we can always just embed in the pointer itself, but
- * in order to support 32bit archs we need store 64bit values
- * indirectly, since they don't fit in a pointer. */
+/* 32bit values we can always just embed in the pointer itself, but in order to support 32bit archs we need store 64bit
+ * values indirectly, since they don't fit in a pointer. */
 void uint64_hash_func(const void *p, struct siphash *state);
 int uint64_compare_func(const void *a, const void *b) _pure_;
 extern const struct hash_ops uint64_hash_ops;
 
-/* On some archs dev_t is 32bit, and on others 64bit. And sometimes
- * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */
+/* On some archs dev_t is 32bit, and on others 64bit. And sometimes it's 64bit on 32bit archs, and sometimes 32bit on
+ * 64bit archs. Yuck! */
 #if SIZEOF_DEV_T != 8
 void devt_hash_func(const void *p, struct siphash *state) _pure_;
 int devt_compare_func(const void *a, const void *b) _pure_;
-extern const struct hash_ops devt_hash_ops = {
-        .hash = devt_hash_func,
-        .compare = devt_compare_func
-};
+extern const struct hash_ops devt_hash_ops;
 #else
 #define devt_hash_func uint64_hash_func
 #define devt_compare_func uint64_compare_func
diff --git a/src/systemd/src/basic/hashmap.c b/src/systemd/src/basic/hashmap.c
index c95d4991..a76ec8e8 100644
--- a/src/systemd/src/basic/hashmap.c
+++ b/src/systemd/src/basic/hashmap.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -27,12 +28,14 @@
 
 #include "alloc-util.h"
 #include "hashmap.h"
+#include "fileio.h"
 #include "macro.h"
 #include "mempool.h"
 #include "process-util.h"
 #include "random-util.h"
 #include "set.h"
 #include "siphash24.h"
+#include "string-util.h"
 #include "strv.h"
 #include "util.h"
 
@@ -228,6 +231,8 @@ struct HashmapBase {
         unsigned n_direct_entries:3; /* Number of entries in direct storage.
                                       * Only valid if !has_indirect. */
         bool from_pool:1;            /* whether was allocated from mempool */
+        bool dirty:1;                /* whether dirtied since last iterated_cache_get() */
+        bool cached:1;               /* whether this hashmap is being cached */
         HASHMAP_DEBUG_FIELDS         /* optional hashmap_debug_info */
 };
 
@@ -247,6 +252,17 @@ struct Set {
         struct HashmapBase b;
 };
 
+typedef struct CacheMem {
+        const void **ptr;
+        size_t n_populated, n_allocated;
+        bool active:1;
+} CacheMem;
+
+struct IteratedCache {
+        HashmapBase *hashmap;
+        CacheMem keys, values;
+};
+
 DEFINE_MEMPOOL(hashmap_pool,         Hashmap,        8);
 DEFINE_MEMPOOL(ordered_hashmap_pool, OrderedHashmap, 8);
 /* No need for a separate Set pool */
@@ -280,6 +296,28 @@ static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = {
         },
 };
 
+#ifdef VALGRIND
+__attribute__((destructor)) static void cleanup_pools(void) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        /* Be nice to valgrind */
+
+        /* The pool is only allocated by the main thread, but the memory can
+         * be passed to other threads. Let's clean up if we are the main thread
+         * and no other threads are live. */
+        if (!is_main_thread())
+                return;
+
+        r = get_proc_field("/proc/self/status", "Threads", WHITESPACE, &t);
+        if (r < 0 || !streq(t, "1"))
+                return;
+
+        mempool_drop(&hashmap_pool);
+        mempool_drop(&ordered_hashmap_pool);
+}
+#endif
+
 static unsigned n_buckets(HashmapBase *h) {
         return h->has_indirect ? h->indirect.n_buckets
                                : hashmap_type_info[h->type].n_direct_buckets;
@@ -328,6 +366,11 @@ static unsigned base_bucket_hash(HashmapBase *h, const void *p) {
 }
 #define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p)
 
+static inline void base_set_dirty(HashmapBase *h) {
+        h->dirty = true;
+}
+#define hashmap_set_dirty(h) base_set_dirty(HASHMAP_BASE(h))
+
 static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) {
         static uint8_t current[HASH_KEY_SIZE];
         static bool current_initialized = false;
@@ -545,6 +588,7 @@ static void base_remove_entry(HashmapBase *h, unsigned idx) {
 
         bucket_mark_free(h, prev);
         n_entries_dec(h);
+        base_set_dirty(h);
 }
 #define remove_entry(h, idx) base_remove_entry(HASHMAP_BASE(h), idx)
 
@@ -714,6 +758,25 @@ bool set_iterate(Set *s, Iterator *i, void **value) {
              (idx != IDX_NIL); \
              (idx) = hashmap_iterate_entry((h), &(i)))
 
+IteratedCache *internal_hashmap_iterated_cache_new(HashmapBase *h) {
+        IteratedCache *cache;
+
+        assert(h);
+        assert(!h->cached);
+
+        if (h->cached)
+                return NULL;
+
+        cache = new0(IteratedCache, 1);
+        if (!cache)
+                return NULL;
+
+        cache->hashmap = h;
+        h->cached = true;
+
+        return cache;
+}
+
 static void reset_direct_storage(HashmapBase *h) {
         const struct hashmap_type_info *hi = &hashmap_type_info[h->type];
         void *p;
@@ -874,6 +937,8 @@ void internal_hashmap_clear(HashmapBase *h) {
                 OrderedHashmap *lh = (OrderedHashmap*) h;
                 lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL;
         }
+
+        base_set_dirty(h);
 }
 
 void internal_hashmap_clear_free(HashmapBase *h) {
@@ -1018,6 +1083,8 @@ static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx,
         h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h));
 #endif
 
+        base_set_dirty(h);
+
         return 1;
 }
 #define hashmap_put_boldly(h, idx, swap, may_resize) \
@@ -1254,6 +1321,8 @@ int hashmap_replace(Hashmap *h, const void *key, void *value) {
 #endif
                 e->b.key = key;
                 e->value = value;
+                hashmap_set_dirty(h);
+
                 return 0;
         }
 
@@ -1276,6 +1345,8 @@ int hashmap_update(Hashmap *h, const void *key, void *value) {
 
         e = plain_bucket_at(h, idx);
         e->value = value;
+        hashmap_set_dirty(h);
+
         return 0;
 }
 
@@ -1828,3 +1899,95 @@ int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags
                         return r;
         }
 }
+
+/* expand the cachemem if needed, return true if newly (re)activated. */
+static int cachemem_maintain(CacheMem *mem, unsigned size) {
+        assert(mem);
+
+        if (!GREEDY_REALLOC(mem->ptr, mem->n_allocated, size)) {
+                if (size > 0)
+                        return -ENOMEM;
+        }
+
+        if (!mem->active) {
+                mem->active = true;
+                return true;
+        }
+
+        return false;
+}
+
+int iterated_cache_get(IteratedCache *cache, const void ***res_keys, const void ***res_values, unsigned *res_n_entries) {
+        bool sync_keys = false, sync_values = false;
+        unsigned size;
+        int r;
+
+        assert(cache);
+        assert(cache->hashmap);
+
+        size = n_entries(cache->hashmap);
+
+        if (res_keys) {
+                r = cachemem_maintain(&cache->keys, size);
+                if (r < 0)
+                        return r;
+
+                sync_keys = r;
+        } else
+                cache->keys.active = false;
+
+        if (res_values) {
+                r = cachemem_maintain(&cache->values, size);
+                if (r < 0)
+                        return r;
+
+                sync_values = r;
+        } else
+                cache->values.active = false;
+
+        if (cache->hashmap->dirty) {
+                if (cache->keys.active)
+                        sync_keys = true;
+                if (cache->values.active)
+                        sync_values = true;
+
+                cache->hashmap->dirty = false;
+        }
+
+        if (sync_keys || sync_values) {
+                unsigned i, idx;
+                Iterator iter;
+
+                i = 0;
+                HASHMAP_FOREACH_IDX(idx, cache->hashmap, iter) {
+                        struct hashmap_base_entry *e;
+
+                        e = bucket_at(cache->hashmap, idx);
+
+                        if (sync_keys)
+                                cache->keys.ptr[i] = e->key;
+                        if (sync_values)
+                                cache->values.ptr[i] = entry_value(cache->hashmap, e);
+                        i++;
+                }
+        }
+
+        if (res_keys)
+                *res_keys = cache->keys.ptr;
+        if (res_values)
+                *res_values = cache->values.ptr;
+        if (res_n_entries)
+                *res_n_entries = size;
+
+        return 0;
+}
+
+IteratedCache *iterated_cache_free(IteratedCache *cache) {
+        if (cache) {
+                free(cache->keys.ptr);
+                free(cache->values.ptr);
+                free(cache);
+        }
+
+        return NULL;
+}
diff --git a/src/systemd/src/basic/hashmap.h b/src/systemd/src/basic/hashmap.h
index c1089652..b6749103 100644
--- a/src/systemd/src/basic/hashmap.h
+++ b/src/systemd/src/basic/hashmap.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -52,6 +53,8 @@ typedef struct Hashmap Hashmap;               /* Maps keys to values */
 typedef struct OrderedHashmap OrderedHashmap; /* Like Hashmap, but also remembers entry insertion order */
 typedef struct Set Set;                       /* Stores just keys */
 
+typedef struct IteratedCache IteratedCache;   /* Caches the iterated order of one of the above */
+
 /* Ideally the Iterator would be an opaque struct, but it is instantiated
  * by hashmap users, so the definition has to be here. Do not use its fields
  * directly. */
@@ -125,6 +128,9 @@ static inline OrderedHashmap *ordered_hashmap_free_free_free(OrderedHashmap *h)
         return (void*)hashmap_free_free_free(PLAIN_HASHMAP(h));
 }
 
+IteratedCache *iterated_cache_free(IteratedCache *cache);
+int iterated_cache_get(IteratedCache *cache, const void ***res_keys, const void ***res_values, unsigned *res_n_entries);
+
 HashmapBase *internal_hashmap_copy(HashmapBase *h);
 static inline Hashmap *hashmap_copy(Hashmap *h) {
         return (Hashmap*) internal_hashmap_copy(HASHMAP_BASE(h));
@@ -138,6 +144,14 @@ int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct h
 #define hashmap_ensure_allocated(h, ops) internal_hashmap_ensure_allocated(h, ops  HASHMAP_DEBUG_SRC_ARGS)
 #define ordered_hashmap_ensure_allocated(h, ops) internal_ordered_hashmap_ensure_allocated(h, ops  HASHMAP_DEBUG_SRC_ARGS)
 
+IteratedCache *internal_hashmap_iterated_cache_new(HashmapBase *h);
+static inline IteratedCache *hashmap_iterated_cache_new(Hashmap *h) {
+        return (IteratedCache*) internal_hashmap_iterated_cache_new(HASHMAP_BASE(h));
+}
+static inline IteratedCache *ordered_hashmap_iterated_cache_new(OrderedHashmap *h) {
+        return (IteratedCache*) internal_hashmap_iterated_cache_new(HASHMAP_BASE(h));
+}
+
 int hashmap_put(Hashmap *h, const void *key, void *value);
 static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *value) {
         return hashmap_put(PLAIN_HASHMAP(h), key, value);
@@ -328,6 +342,29 @@ static inline void *ordered_hashmap_first(OrderedHashmap *h) {
         return internal_hashmap_first(HASHMAP_BASE(h));
 }
 
+#define hashmap_clear_with_destructor(_s, _f)                   \
+        ({                                                      \
+                void *_item;                                    \
+                while ((_item = hashmap_steal_first(_s)))       \
+                        _f(_item);                              \
+        })
+#define hashmap_free_with_destructor(_s, _f)                    \
+        ({                                                      \
+                hashmap_clear_with_destructor(_s, _f);          \
+                hashmap_free(_s);                               \
+        })
+#define ordered_hashmap_clear_with_destructor(_s, _f)                   \
+        ({                                                              \
+                void *_item;                                            \
+                while ((_item = ordered_hashmap_steal_first(_s)))       \
+                        _f(_item);                                      \
+        })
+#define ordered_hashmap_free_with_destructor(_s, _f)                    \
+        ({                                                              \
+                ordered_hashmap_clear_with_destructor(_s, _f);          \
+                ordered_hashmap_free(_s);                               \
+        })
+
 /* no hashmap_next */
 void *ordered_hashmap_next(OrderedHashmap *h, const void *key);
 
@@ -370,3 +407,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free);
 #define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep)
 #define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep)
 #define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep)
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(IteratedCache*, iterated_cache_free);
+
+#define _cleanup_iterated_cache_free_ _cleanup_(iterated_cache_freep)
diff --git a/src/systemd/src/basic/hexdecoct.c b/src/systemd/src/basic/hexdecoct.c
index e0ae83c1..e8e82b5f 100644
--- a/src/systemd/src/basic/hexdecoct.c
+++ b/src/systemd/src/basic/hexdecoct.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -98,7 +99,10 @@ int unhexmem(const char *p, size_t l, void **mem, size_t *len) {
 
         assert(mem);
         assert(len);
-        assert(p);
+        assert(p || l == 0);
+
+        if (l == (size_t) -1)
+                l = strlen(p);
 
         if (l % 2 != 0)
                 return -EINVAL;
@@ -130,6 +134,7 @@ int unhexmem(const char *p, size_t l, void **mem, size_t *len) {
         return 0;
 }
 
+#if 0 /* NM_IGNORED */
 /* https://tools.ietf.org/html/rfc4648#section-6
  * Notice that base32hex differs from base32 in the alphabet it uses.
  * The distinction is that the base32hex representation preserves the
@@ -162,6 +167,8 @@ char *base32hexmem(const void *p, size_t l, bool padding) {
         const uint8_t *x;
         size_t len;
 
+        assert(p || l == 0);
+
         if (padding)
                 /* five input bytes makes eight output bytes, padding is added so we must round up */
                 len = 8 * (l + 4) / 5;
@@ -271,7 +278,12 @@ int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_l
         size_t len;
         unsigned pad = 0;
 
-        assert(p);
+        assert(p || l == 0);
+        assert(mem);
+        assert(_len);
+
+        if (l == (size_t) -1)
+                l = strlen(p);
 
         /* padding ensures any base32hex input has input divisible by 8 */
         if (padding && l % 8 != 0)
@@ -521,6 +533,9 @@ ssize_t base64mem(const void *p, size_t l, char **out) {
         char *r, *z;
         const uint8_t *x;
 
+        assert(p || l == 0);
+        assert(out);
+
         /* three input bytes makes four output bytes, padding is added so we must round up */
         z = r = malloc(4 * (l + 2) / 3 + 1);
         if (!r)
@@ -556,10 +571,11 @@ ssize_t base64mem(const void *p, size_t l, char **out) {
         return z - r;
 }
 
-static int base64_append_width(char **prefix, int plen,
-                               const char *sep, int indent,
-                               const void *p, size_t l,
-                               int width) {
+static int base64_append_width(
+                char **prefix, int plen,
+                const char *sep, int indent,
+                const void *p, size_t l,
+                int width) {
 
         _cleanup_free_ char *x = NULL;
         char *t, *s;
@@ -570,7 +586,7 @@ static int base64_append_width(char **prefix, int plen,
         if (len <= 0)
                 return len;
 
-        lines = (len + width - 1) / width;
+        lines = DIV_ROUND_UP(len, width);
 
         slen = strlen_ptr(sep);
         t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines);
@@ -598,118 +614,148 @@ static int base64_append_width(char **prefix, int plen,
         return 0;
 }
 
-int base64_append(char **prefix, int plen,
-                  const void *p, size_t l,
-                  int indent, int width) {
+int base64_append(
+                char **prefix, int plen,
+                const void *p, size_t l,
+                int indent, int width) {
+
         if (plen > width / 2 || plen + indent > width)
                 /* leave indent on the left, keep last column free */
                 return base64_append_width(prefix, plen, "\n", indent, p, l, width - indent - 1);
         else
                 /* leave plen on the left, keep last column free */
                 return base64_append_width(prefix, plen, NULL, plen, p, l, width - plen - 1);
-};
+}
 
-
-int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) {
-        _cleanup_free_ uint8_t *r = NULL;
-        int a, b, c, d;
-        uint8_t *z;
-        const char *x;
-        size_t len;
+static int unbase64_next(const char **p, size_t *l) {
+        int ret;
 
         assert(p);
+        assert(l);
 
-        /* padding ensures any base63 input has input divisible by 4 */
-        if (l % 4 != 0)
-                return -EINVAL;
-
-        /* strip the padding */
-        if (l > 0 && p[l - 1] == '=')
-                l--;
-        if (l > 0 && p[l - 1] == '=')
-                l--;
+        /* Find the next non-whitespace character, and decode it. If we find padding, we return it as INT_MAX. We
+         * greedily skip all preceeding and all following whitespace. */
 
-        /* a group of four input bytes needs three output bytes, in case of
-           padding we need to add two or three extra bytes */
-        len = (l / 4) * 3 + (l % 4 ? (l % 4) - 1 : 0);
+        for (;;) {
+                if (*l == 0)
+                        return -EPIPE;
 
-        z = r = malloc(len + 1);
-        if (!r)
-                return -ENOMEM;
+                if (!strchr(WHITESPACE, **p))
+                        break;
 
-        for (x = p; x < p + (l / 4) * 4; x += 4) {
-                /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */
-                a = unbase64char(x[0]);
-                if (a < 0)
-                        return -EINVAL;
+                /* Skip leading whitespace */
+                (*p)++, (*l)--;
+        }
 
-                b = unbase64char(x[1]);
-                if (b < 0)
-                        return -EINVAL;
+        if (**p == '=')
+                ret = INT_MAX; /* return padding as INT_MAX */
+        else {
+                ret = unbase64char(**p);
+                if (ret < 0)
+                        return ret;
+        }
 
-                c = unbase64char(x[2]);
-                if (c < 0)
-                        return -EINVAL;
+        for (;;) {
+                (*p)++, (*l)--;
 
-                d = unbase64char(x[3]);
-                if (d < 0)
-                        return -EINVAL;
+                if (*l == 0)
+                        break;
+                if (!strchr(WHITESPACE, **p))
+                        break;
 
-                *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
-                *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
-                *(z++) = (uint8_t) c << 6 | (uint8_t) d;      /* ZZWWWWWW */
+                /* Skip following whitespace */
         }
 
-        switch (l % 4) {
-        case 3:
-                a = unbase64char(x[0]);
+        return ret;
+}
+
+int unbase64mem(const char *p, size_t l, void **ret, size_t *ret_size) {
+        _cleanup_free_ uint8_t *buf = NULL;
+        const char *x;
+        uint8_t *z;
+        size_t len;
+
+        assert(p || l == 0);
+        assert(ret);
+        assert(ret_size);
+
+        if (l == (size_t) -1)
+                l = strlen(p);
+
+        /* A group of four input bytes needs three output bytes, in case of padding we need to add two or three extra
+           bytes. Note that this calculation is an upper boundary, as we ignore whitespace while decoding */
+        len = (l / 4) * 3 + (l % 4 != 0 ? (l % 4) - 1 : 0);
+
+        buf = malloc(len + 1);
+        if (!buf)
+                return -ENOMEM;
+
+        for (x = p, z = buf;;) {
+                int a, b, c, d; /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */
+
+                a = unbase64_next(&x, &l);
+                if (a == -EPIPE) /* End of string */
+                        break;
                 if (a < 0)
+                        return a;
+                if (a == INT_MAX) /* Padding is not allowed at the beginning of a 4ch block */
                         return -EINVAL;
 
-                b = unbase64char(x[1]);
+                b = unbase64_next(&x, &l);
                 if (b < 0)
+                        return b;
+                if (b == INT_MAX) /* Padding is not allowed at the second character of a 4ch block either */
                         return -EINVAL;
 
-                c = unbase64char(x[2]);
+                c = unbase64_next(&x, &l);
                 if (c < 0)
-                        return -EINVAL;
+                        return c;
 
-                /* c == 00ZZZZ00 */
-                if (c & 3)
-                        return -EINVAL;
+                d = unbase64_next(&x, &l);
+                if (d < 0)
+                        return d;
 
-                *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
-                *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
+                if (c == INT_MAX) { /* Padding at the third character */
 
-                break;
-        case 2:
-                a = unbase64char(x[0]);
-                if (a < 0)
-                        return -EINVAL;
+                        if (d != INT_MAX) /* If the third character is padding, the fourth must be too */
+                                return -EINVAL;
 
-                b = unbase64char(x[1]);
-                if (b < 0)
-                        return -EINVAL;
+                        /* b == 00YY0000 */
+                        if (b & 15)
+                                return -EINVAL;
 
-                /* b == 00YY0000 */
-                if (b & 15)
-                        return -EINVAL;
+                        if (l > 0) /* Trailing rubbish? */
+                                return -ENAMETOOLONG;
 
-                *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
+                        *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
+                        break;
+                }
 
-                break;
-        case 0:
+                if (d == INT_MAX) {
+                        /* c == 00ZZZZ00 */
+                        if (c & 3)
+                                return -EINVAL;
 
-                break;
-        default:
-                return -EINVAL;
+                        if (l > 0) /* Trailing rubbish? */
+                                return -ENAMETOOLONG;
+
+                        *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
+                        *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
+                        break;
+                }
+
+                *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
+                *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
+                *(z++) = (uint8_t) c << 6 | (uint8_t) d;      /* ZZWWWWWW */
         }
 
         *z = 0;
 
-        *mem = r;
-        r = NULL;
-        *_len = len;
+        if (ret_size)
+                *ret_size = (size_t) (z - buf);
+
+        *ret = buf;
+        buf = NULL;
 
         return 0;
 }
@@ -718,7 +764,10 @@ void hexdump(FILE *f, const void *p, size_t s) {
         const uint8_t *b = p;
         unsigned n = 0;
 
-        assert(s == 0 || b);
+        assert(b || s == 0);
+
+        if (!f)
+                f = stdout;
 
         while (s > 0) {
                 size_t i;
@@ -756,3 +805,4 @@ void hexdump(FILE *f, const void *p, size_t s) {
                 s -= 16;
         }
 }
+#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/hexdecoct.h b/src/systemd/src/basic/hexdecoct.h
index 1ba2f69e..08d0a522 100644
--- a/src/systemd/src/basic/hexdecoct.h
+++ b/src/systemd/src/basic/hexdecoct.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/basic/hostname-util.c b/src/systemd/src/basic/hostname-util.c
index be6e9e58..66b80727 100644
--- a/src/systemd/src/basic/hostname-util.c
+++ b/src/systemd/src/basic/hostname-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -26,12 +27,15 @@
 #include <sys/utsname.h>
 #include <unistd.h>
 
+#include "alloc-util.h"
+#include "def.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "hostname-util.h"
 #include "macro.h"
 #include "string-util.h"
 
+#if 0 /* NM_IGNORED */
 bool hostname_is_set(void) {
         struct utsname u;
 
@@ -47,7 +51,6 @@ bool hostname_is_set(void) {
         return true;
 }
 
-#if 0 /* NM_IGNORED */
 char* gethostname_malloc(void) {
         struct utsname u;
 
@@ -223,36 +226,88 @@ int sethostname_idempotent(const char *s) {
         return 1;
 }
 
-int read_hostname_config(const char *path, char **hostname) {
-        _cleanup_fclose_ FILE *f = NULL;
-        char l[LINE_MAX];
-        char *name = NULL;
+int shorten_overlong(const char *s, char **ret) {
+        char *h, *p;
 
-        assert(path);
-        assert(hostname);
+        /* Shorten an overlong name to HOST_NAME_MAX or to the first dot,
+         * whatever comes earlier. */
 
-        f = fopen(path, "re");
-        if (!f)
-                return -errno;
+        assert(s);
+
+        h = strdup(s);
+        if (!h)
+                return -ENOMEM;
+
+        if (hostname_is_valid(h, false)) {
+                *ret = h;
+                return 0;
+        }
+
+        p = strchr(h, '.');
+        if (p)
+                *p = 0;
+
+        strshorten(h, HOST_NAME_MAX);
+
+        if (!hostname_is_valid(h, false)) {
+                free(h);
+                return -EDOM;
+        }
+
+        *ret = h;
+        return 1;
+}
 
-        /* may have comments, ignore them */
-        FOREACH_LINE(l, f, return -errno) {
-                truncate_nl(l);
-                if (!IN_SET(l[0], '\0', '#')) {
-                        /* found line with value */
-                        name = hostname_cleanup(l);
-                        name = strdup(name);
-                        if (!name)
+int read_etc_hostname_stream(FILE *f, char **ret) {
+        int r;
+
+        assert(f);
+        assert(ret);
+
+        for (;;) {
+                _cleanup_free_ char *line = NULL;
+                char *p;
+
+                r = read_line(f, LONG_LINE_MAX, &line);
+                if (r < 0)
+                        return r;
+                if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */
+                        return -ENOENT;
+
+                p = strstrip(line);
+
+                /* File may have empty lines or comments, ignore them */
+                if (!IN_SET(*p, '\0', '#')) {
+                        char *copy;
+
+                        hostname_cleanup(p); /* normalize the hostname */
+
+                        if (!hostname_is_valid(p, true)) /* check that the hostname we return is valid */
+                                return -EBADMSG;
+
+                        copy = strdup(p);
+                        if (!copy)
                                 return -ENOMEM;
-                        break;
+
+                        *ret = copy;
+                        return 0;
                 }
         }
+}
 
-        if (!name)
-                /* no non-empty line found */
-                return -ENOENT;
+int read_etc_hostname(const char *path, char **ret) {
+        _cleanup_fclose_ FILE *f = NULL;
+
+        assert(ret);
+
+        if (!path)
+                path = "/etc/hostname";
+
+        f = fopen(path, "re");
+        if (!f)
+                return -errno;
+
+        return read_etc_hostname_stream(f, ret);
 
-        *hostname = name;
-        return 0;
 }
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/hostname-util.h b/src/systemd/src/basic/hostname-util.h
index 7af4e6c7..edae52e3 100644
--- a/src/systemd/src/basic/hostname-util.h
+++ b/src/systemd/src/basic/hostname-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -20,6 +21,7 @@
 ***/
 
 #include <stdbool.h>
+#include <stdio.h>
 
 #include "macro.h"
 
@@ -38,4 +40,7 @@ bool is_gateway_hostname(const char *hostname);
 
 int sethostname_idempotent(const char *s);
 
-int read_hostname_config(const char *path, char **hostname);
+int shorten_overlong(const char *s, char **ret);
+
+int read_etc_hostname_stream(FILE *f, char **ret);
+int read_etc_hostname(const char *path, char **ret);
diff --git a/src/systemd/src/basic/in-addr-util.c b/src/systemd/src/basic/in-addr-util.c
index 2a02d90b..b2b68089 100644
--- a/src/systemd/src/basic/in-addr-util.c
+++ b/src/systemd/src/basic/in-addr-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -332,6 +333,7 @@ int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union
         return -EINVAL;
 }
 
+#if 0 /* NM_IGNORED */
 int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex) {
         const char *suffix;
         int r, ifi = 0;
@@ -372,6 +374,7 @@ int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_u
 
         return r;
 }
+#endif /* NM_IGNORED */
 
 unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr) {
         assert(addr);
diff --git a/src/systemd/src/basic/in-addr-util.h b/src/systemd/src/basic/in-addr-util.h
index 59f8eb7e..acaae6d2 100644
--- a/src/systemd/src/basic/in-addr-util.h
+++ b/src/systemd/src/basic/in-addr-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/basic/io-util.c b/src/systemd/src/basic/io-util.c
index 61b667f0..0f10ad7f 100644
--- a/src/systemd/src/basic/io-util.c
+++ b/src/systemd/src/basic/io-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -34,6 +35,7 @@ int flush_fd(int fd) {
                 .fd = fd,
                 .events = POLLIN,
         };
+        int count = 0;
 
         /* Read from the specified file descriptor, until POLLIN is not set anymore, throwing away everything
          * read. Note that some file descriptors (notable IP sockets) will trigger POLLIN even when no data can be read
@@ -53,7 +55,7 @@ int flush_fd(int fd) {
                         return -errno;
 
                 } else if (r == 0)
-                        return 0;
+                        return count;
 
                 l = read(fd, buf, sizeof(buf));
                 if (l < 0) {
@@ -62,11 +64,13 @@ int flush_fd(int fd) {
                                 continue;
 
                         if (errno == EAGAIN)
-                                return 0;
+                                return count;
 
                         return -errno;
                 } else if (l == 0)
-                        return 0;
+                        return count;
+
+                count += (int) l;
         }
 }
 
@@ -135,7 +139,7 @@ int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
         assert(fd >= 0);
         assert(buf);
 
-        if (nbytes > (size_t) SSIZE_MAX)
+        if (_unlikely_(nbytes > (size_t) SSIZE_MAX))
                 return -EINVAL;
 
         do {
@@ -201,7 +205,6 @@ int fd_wait_for_event(int fd, int event, usec_t t) {
         r = ppoll(&pollfd, 1, t == USEC_INFINITY ? NULL : timespec_store(&ts, t), NULL);
         if (r < 0)
                 return -errno;
-
         if (r == 0)
                 return 0;
 
diff --git a/src/systemd/src/basic/io-util.h b/src/systemd/src/basic/io-util.h
index d9b69add..d81610ad 100644
--- a/src/systemd/src/basic/io-util.h
+++ b/src/systemd/src/basic/io-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/basic/list.h b/src/systemd/src/basic/list.h
index c3771a17..7006c3e2 100644
--- a/src/systemd/src/basic/list.h
+++ b/src/systemd/src/basic/list.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -182,3 +183,6 @@
         for ((i) = (p)->name##_next ? (p)->name##_next : (head);        \
              (i) != (p);                                                \
              (i) = (i)->name##_next ? (i)->name##_next : (head))
+
+#define LIST_IS_EMPTY(head)                                             \
+        (!(head))
diff --git a/src/systemd/src/basic/log.h b/src/systemd/src/basic/log.h
index 67fda3f9..b0e963b0 100644
--- a/src/systemd/src/basic/log.h
+++ b/src/systemd/src/basic/log.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -19,18 +20,16 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include <errno.h>
 #include <stdarg.h>
 #include <stdbool.h>
 #include <stdlib.h>
-#include <sys/signalfd.h>
-#include <sys/socket.h>
 #include <syslog.h>
 
-#include "sd-id128.h"
-
 #include "macro.h"
-#include "process-util.h"
+
+/* Some structures we reference but don't want to pull in headers for */
+struct iovec;
+struct signalfd_siginfo;
 
 typedef enum LogRealm {
         LOG_REALM_SYSTEMD,
@@ -51,7 +50,6 @@ typedef enum LogTarget{
         LOG_TARGET_SYSLOG,
         LOG_TARGET_SYSLOG_OR_KMSG,
         LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */
-        LOG_TARGET_SAFE, /* console if stderr is tty, KMSG otherwise */
         LOG_TARGET_NULL,
         _LOG_TARGET_MAX,
         _LOG_TARGET_INVALID = -1
@@ -98,11 +96,6 @@ int log_open(void);
 void log_close(void);
 void log_forget_fds(void);
 
-void log_close_syslog(void);
-void log_close_journal(void);
-void log_close_kmsg(void);
-void log_close_console(void);
-
 void log_parse_environment_realm(LogRealm realm);
 #define log_parse_environment() \
         log_parse_environment_realm(LOG_REALM)
@@ -156,19 +149,6 @@ int log_object_internal(
                 const char *extra,
                 const char *format, ...) _printf_(10,11);
 
-int log_object_internalv(
-                int level,
-                int error,
-                const char *file,
-                int line,
-                const char *func,
-                const char *object_field,
-                const char *object,
-                const char *extra_field,
-                const char *extra,
-                const char *format,
-                va_list ap) _printf_(10,0);
-
 int log_struct_internal(
                 int level,
                 int error,
@@ -185,8 +165,8 @@ int log_oom_internal(
 
 int log_format_iovec(
                 struct iovec *iovec,
-                unsigned iovec_len,
-                unsigned *n,
+                size_t iovec_len,
+                size_t *n,
                 bool newline_separator,
                 int error,
                 const char *format,
@@ -198,7 +178,7 @@ int log_struct_iovec_internal(
                 const char *file,
                 int line,
                 const char *func,
-                const struct iovec input_iovec[],
+                const struct iovec *input_iovec,
                 size_t n_input_iovec);
 
 /* This modifies the buffer passed! */
@@ -211,7 +191,7 @@ int log_dump_internal(
                 char *buffer);
 
 /* Logging for various assertions */
-noreturn void log_assert_failed_realm(
+_noreturn_ void log_assert_failed_realm(
                 LogRealm realm,
                 const char *text,
                 const char *file,
@@ -220,7 +200,7 @@ noreturn void log_assert_failed_realm(
 #define log_assert_failed(text, ...) \
         log_assert_failed_realm(LOG_REALM, (text), __VA_ARGS__)
 
-noreturn void log_assert_failed_unreachable_realm(
+_noreturn_ void log_assert_failed_unreachable_realm(
                 LogRealm realm,
                 const char *text,
                 const char *file,
@@ -245,9 +225,9 @@ void log_assert_failed_return_realm(
 /* Logging with level */
 #define log_full_errno_realm(realm, level, error, ...)                  \
         ({                                                              \
-                int _level = (level), _e = (error);                     \
-                (log_get_max_level_realm((realm)) >= LOG_PRI(_level))   \
-                        ? log_internal_realm(LOG_REALM_PLUS_LEVEL((realm), _level), _e, \
+                int _level = (level), _e = (error), _realm = (realm);   \
+                (log_get_max_level_realm(_realm) >= LOG_PRI(_level))   \
+                        ? log_internal_realm(LOG_REALM_PLUS_LEVEL(_realm, _level), _e, \
                                              __FILE__, __LINE__, __func__, __VA_ARGS__) \
                         : -abs(_e);                                     \
         })
@@ -257,13 +237,15 @@ void log_assert_failed_return_realm(
 
 #define log_full(level, ...) log_full_errno((level), 0, __VA_ARGS__)
 
+int log_emergency_level(void);
+
 /* Normal logging */
 #define log_debug(...)     log_full(LOG_DEBUG,   __VA_ARGS__)
 #define log_info(...)      log_full(LOG_INFO,    __VA_ARGS__)
 #define log_notice(...)    log_full(LOG_NOTICE,  __VA_ARGS__)
 #define log_warning(...)   log_full(LOG_WARNING, __VA_ARGS__)
 #define log_error(...)     log_full(LOG_ERR,     __VA_ARGS__)
-#define log_emergency(...) log_full(getpid_cached() == 1 ? LOG_EMERG : LOG_ERR, __VA_ARGS__)
+#define log_emergency(...) log_full(log_emergency_level(), __VA_ARGS__)
 
 /* Logging triggered by an errno-like error */
 #define log_debug_errno(error, ...)     log_full_errno(LOG_DEBUG,   error, __VA_ARGS__)
@@ -271,7 +253,7 @@ void log_assert_failed_return_realm(
 #define log_notice_errno(error, ...)    log_full_errno(LOG_NOTICE,  error, __VA_ARGS__)
 #define log_warning_errno(error, ...)   log_full_errno(LOG_WARNING, error, __VA_ARGS__)
 #define log_error_errno(error, ...)     log_full_errno(LOG_ERR,     error, __VA_ARGS__)
-#define log_emergency_errno(error, ...) log_full_errno(getpid_cached() == 1 ? LOG_EMERG : LOG_ERR, error, __VA_ARGS__)
+#define log_emergency_errno(error, ...) log_full_errno(log_emergency_level(), error, __VA_ARGS__)
 
 #ifdef LOG_TRACE
 #  define log_trace(...) log_debug(__VA_ARGS__)
@@ -307,10 +289,20 @@ LogTarget log_target_from_string(const char *s) _pure_;
 
 void log_received_signal(int level, const struct signalfd_siginfo *si);
 
+/* If turned on, any requests for a log target involving "syslog" will be implicitly upgraded to the equivalent journal target */
 void log_set_upgrade_syslog_to_journal(bool b);
+
+/* If turned on, and log_open() is called, we'll not use STDERR_FILENO for logging ever, but rather open /dev/console */
 void log_set_always_reopen_console(bool b);
+
+/* If turned on, we'll open the log stream implicitly if needed on each individual log call. This is normally not
+ * desired as we want to reuse our logging streams. It is useful however  */
 void log_set_open_when_needed(bool b);
 
+/* If turned on, then we'll never use IPC-based logging, i.e. never log to syslog or the journal. We'll only log to
+ * stderr, the console or kmsg */
+void log_set_prohibit_ipc(bool b);
+
 int log_syntax_internal(
                 const char *unit,
                 int level,
@@ -322,6 +314,16 @@ int log_syntax_internal(
                 const char *func,
                 const char *format, ...) _printf_(9, 10);
 
+int log_syntax_invalid_utf8_internal(
+                const char *unit,
+                int level,
+                const char *config_file,
+                unsigned config_line,
+                const char *file,
+                int line,
+                const char *func,
+                const char *rvalue);
+
 #define log_syntax(unit, level, config_file, config_line, error, ...)   \
         ({                                                              \
                 int _level = (level), _e = (error);                     \
@@ -333,10 +335,9 @@ int log_syntax_internal(
 #define log_syntax_invalid_utf8(unit, level, config_file, config_line, rvalue) \
         ({                                                              \
                 int _level = (level);                                   \
-                if (log_get_max_level() >= LOG_PRI(_level)) {           \
-                        _cleanup_free_ char *_p = NULL;                 \
-                        _p = utf8_escape_invalid(rvalue);               \
-                        log_syntax_internal(unit, _level, config_file, config_line, 0, __FILE__, __LINE__, __func__, \
-                                            "String is not UTF-8 clean, ignoring assignment: %s", strna(_p)); \
-                }                                                       \
+                (log_get_max_level() >= LOG_PRI(_level))                \
+                        ? log_syntax_invalid_utf8_internal(unit, _level, config_file, config_line, __FILE__, __LINE__, __func__, rvalue) \
+                        : -EINVAL;                                      \
         })
+
+#define DEBUG_LOGGING _unlikely_(log_get_max_level() >= LOG_DEBUG)
diff --git a/src/systemd/src/basic/macro.h b/src/systemd/src/basic/macro.h
index afcde459..025a5db7 100644
--- a/src/systemd/src/basic/macro.h
+++ b/src/systemd/src/basic/macro.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -47,6 +48,31 @@
 #define _weakref_(x) __attribute__((weakref(#x)))
 #define _alignas_(x) __attribute__((aligned(__alignof(x))))
 #define _cleanup_(x) __attribute__((cleanup(x)))
+#if __GNUC__ >= 7
+#define _fallthrough_ __attribute__((fallthrough))
+#else
+#define _fallthrough_
+#endif
+/* Define C11 noreturn without <stdnoreturn.h> and even on older gcc
+ * compiler versions */
+#ifndef _noreturn_
+#if __STDC_VERSION__ >= 201112L
+#define _noreturn_ _Noreturn
+#else
+#define _noreturn_ __attribute__((noreturn))
+#endif
+#endif
+
+#if !defined(HAS_FEATURE_MEMORY_SANITIZER)
+#  if defined(__has_feature)
+#    if __has_feature(memory_sanitizer)
+#      define HAS_FEATURE_MEMORY_SANITIZER 1
+#    endif
+#  endif
+#  if !defined(HAS_FEATURE_MEMORY_SANITIZER)
+#    define HAS_FEATURE_MEMORY_SANITIZER 0
+#  endif
+#endif
 
 #if (defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) || defined (__clang__)
 /* Temporarily disable some warnings */
@@ -142,11 +168,25 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
         return 1UL << (sizeof(u) * 8 - __builtin_clzl(u - 1UL));
 }
 
+#ifndef __COVERITY__
+#  define VOID_0 ((void)0)
+#else
+#  define VOID_0 ((void*)0)
+#endif
+
 #define ELEMENTSOF(x)                                                    \
         __extension__ (__builtin_choose_expr(                            \
                 !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \
                 sizeof(x)/sizeof((x)[0]),                                \
-                (void)0))
+                VOID_0))
+
+/*
+ * STRLEN - return the length of a string literal, minus the trailing NUL byte.
+ *          Contrary to strlen(), this is a constant expression.
+ * @x: a string literal.
+ */
+#define STRLEN(x) (sizeof(""x"") - 1)
+
 /*
  * container_of - cast a member of a structure out to the containing structure
  * @ptr: the pointer to the member.
@@ -176,7 +216,7 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
                 __builtin_constant_p(_B) &&                             \
                 __builtin_types_compatible_p(typeof(_A), typeof(_B)),   \
                 ((_A) > (_B)) ? (_A) : (_B),                            \
-                (void)0))
+                VOID_0))
 
 /* takes two types and returns the size of the larger one */
 #define MAXSIZE(A, B) (sizeof(union _packed_ { typeof(A) a; typeof(B) b; }))
@@ -403,21 +443,10 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
 #endif
 #endif
 
-/* Define C11 noreturn without <stdnoreturn.h> and even on older gcc
- * compiler versions */
-#ifndef noreturn
-#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
-#define noreturn _Noreturn
-#else
-#define noreturn __attribute__((noreturn))
-#endif
-#endif
-
 #define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func)                 \
         static inline void func##p(type *p) {                   \
                 if (*p)                                         \
                         func(*p);                               \
-        }                                                       \
-        struct __useless_struct_to_allow_trailing_semicolon__
+        }
 
 #include "log.h"
diff --git a/src/systemd/src/basic/mempool.c b/src/systemd/src/basic/mempool.c
index c1635964..43ae59c9 100644
--- a/src/systemd/src/basic/mempool.c
+++ b/src/systemd/src/basic/mempool.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
diff --git a/src/systemd/src/basic/mempool.h b/src/systemd/src/basic/mempool.h
index 0618b8dd..c9235c83 100644
--- a/src/systemd/src/basic/mempool.h
+++ b/src/systemd/src/basic/mempool.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/basic/parse-util.c b/src/systemd/src/basic/parse-util.c
index 6d978e93..44fae932 100644
--- a/src/systemd/src/basic/parse-util.c
+++ b/src/systemd/src/basic/parse-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -27,7 +28,9 @@
 #include <string.h>
 
 #include "alloc-util.h"
+#include "errno-list.h"
 #include "extract-word.h"
+#include "locale-util.h"
 #include "macro.h"
 #include "parse-util.h"
 #include "process-util.h"
@@ -68,7 +71,6 @@ int parse_pid(const char *s, pid_t* ret_pid) {
         *ret_pid = pid;
         return 0;
 }
-#endif /* NM_IGNORED */
 
 int parse_mode(const char *s, mode_t *ret) {
         char *x;
@@ -85,7 +87,7 @@ int parse_mode(const char *s, mode_t *ret) {
         l = strtol(s, &x, 8);
         if (errno > 0)
                 return -errno;
-        if (!x || x == s || *x)
+        if (!x || x == s || *x != 0)
                 return -EINVAL;
         if (l < 0 || l  > 07777)
                 return -ERANGE;
@@ -235,7 +237,6 @@ int parse_size(const char *t, uint64_t base, uint64_t *size) {
         return 0;
 }
 
-#if 0 /* NM_IGNORED */
 int parse_range(const char *t, unsigned *lower, unsigned *upper) {
         _cleanup_free_ char *word = NULL;
         unsigned l, u;
@@ -272,7 +273,64 @@ int parse_range(const char *t, unsigned *lower, unsigned *upper) {
         *upper = u;
         return 0;
 }
-#endif /* NM_IGNORED */
+
+int parse_errno(const char *t) {
+        int r, e;
+
+        assert(t);
+
+        r = errno_from_name(t);
+        if (r > 0)
+                return r;
+
+        r = safe_atoi(t, &e);
+        if (r < 0)
+                return r;
+
+        /* 0 is also allowed here */
+        if (!errno_is_valid(e) && e != 0)
+                return -ERANGE;
+
+        return e;
+}
+
+int parse_syscall_and_errno(const char *in, char **name, int *error) {
+        _cleanup_free_ char *n = NULL;
+        char *p;
+        int e = -1;
+
+        assert(in);
+        assert(name);
+        assert(error);
+
+        /*
+         * This parse "syscall:errno" like "uname:EILSEQ", "@sync:255".
+         * If errno is omitted, then error is set to -1.
+         * Empty syscall name is not allowed.
+         * Here, we do not check that the syscall name is valid or not.
+         */
+
+        p = strchr(in, ':');
+        if (p) {
+                e = parse_errno(p + 1);
+                if (e < 0)
+                        return e;
+
+                n = strndup(in, p - in);
+        } else
+                n = strdup(in);
+
+        if (!n)
+                return -ENOMEM;
+
+        if (isempty(n))
+                return -EINVAL;
+
+        *error = e;
+        *name = TAKE_PTR(n);
+
+        return 0;
+}
 
 char *format_bytes(char *buf, size_t l, uint64_t t) {
         unsigned i;
@@ -314,13 +372,15 @@ finish:
         return buf;
 
 }
+#endif /* NM_IGNORED */
 
-int safe_atou(const char *s, unsigned *ret_u) {
+int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
         char *x = NULL;
         unsigned long l;
 
         assert(s);
         assert(ret_u);
+        assert(base <= 16);
 
         /* strtoul() is happy to parse negative values, and silently
          * converts them to unsigned values without generating an
@@ -333,10 +393,10 @@ int safe_atou(const char *s, unsigned *ret_u) {
         s += strspn(s, WHITESPACE);
 
         errno = 0;
-        l = strtoul(s, &x, 0);
+        l = strtoul(s, &x, base);
         if (errno > 0)
                 return -errno;
-        if (!x || x == s || *x)
+        if (!x || x == s || *x != 0)
                 return -EINVAL;
         if (s[0] == '-')
                 return -ERANGE;
@@ -358,7 +418,7 @@ int safe_atoi(const char *s, int *ret_i) {
         l = strtol(s, &x, 0);
         if (errno > 0)
                 return -errno;
-        if (!x || x == s || *x)
+        if (!x || x == s || *x != 0)
                 return -EINVAL;
         if ((long) (int) l != l)
                 return -ERANGE;
@@ -380,7 +440,7 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) {
         l = strtoull(s, &x, 0);
         if (errno > 0)
                 return -errno;
-        if (!x || x == s || *x)
+        if (!x || x == s || *x != 0)
                 return -EINVAL;
         if (*s == '-')
                 return -ERANGE;
@@ -400,7 +460,7 @@ int safe_atolli(const char *s, long long int *ret_lli) {
         l = strtoll(s, &x, 0);
         if (errno > 0)
                 return -errno;
-        if (!x || x == s || *x)
+        if (!x || x == s || *x != 0)
                 return -EINVAL;
 
         *ret_lli = l;
@@ -420,7 +480,7 @@ int safe_atou8(const char *s, uint8_t *ret) {
         l = strtoul(s, &x, 0);
         if (errno > 0)
                 return -errno;
-        if (!x || x == s || *x)
+        if (!x || x == s || *x != 0)
                 return -EINVAL;
         if (s[0] == '-')
                 return -ERANGE;
@@ -431,20 +491,21 @@ int safe_atou8(const char *s, uint8_t *ret) {
         return 0;
 }
 
-int safe_atou16(const char *s, uint16_t *ret) {
+int safe_atou16_full(const char *s, unsigned base, uint16_t *ret) {
         char *x = NULL;
         unsigned long l;
 
         assert(s);
         assert(ret);
+        assert(base <= 16);
 
         s += strspn(s, WHITESPACE);
 
         errno = 0;
-        l = strtoul(s, &x, 0);
+        l = strtoul(s, &x, base);
         if (errno > 0)
                 return -errno;
-        if (!x || x == s || *x)
+        if (!x || x == s || *x != 0)
                 return -EINVAL;
         if (s[0] == '-')
                 return -ERANGE;
@@ -466,7 +527,7 @@ int safe_atoi16(const char *s, int16_t *ret) {
         l = strtol(s, &x, 0);
         if (errno > 0)
                 return -errno;
-        if (!x || x == s || *x)
+        if (!x || x == s || *x != 0)
                 return -EINVAL;
         if ((long) (int16_t) l != l)
                 return -ERANGE;
@@ -475,10 +536,11 @@ int safe_atoi16(const char *s, int16_t *ret) {
         return 0;
 }
 
+#if 0 /* NM_IGNORED */
 int safe_atod(const char *s, double *ret_d) {
+        _cleanup_(freelocalep) locale_t loc = (locale_t) 0;
         char *x = NULL;
         double d = 0;
-        locale_t loc;
 
         assert(s);
         assert(ret_d);
@@ -489,16 +551,11 @@ int safe_atod(const char *s, double *ret_d) {
 
         errno = 0;
         d = strtod_l(s, &x, loc);
-        if (errno > 0) {
-                freelocale(loc);
+        if (errno > 0)
                 return -errno;
-        }
-        if (!x || x == s || *x) {
-                freelocale(loc);
+        if (!x || x == s || *x != 0)
                 return -EINVAL;
-        }
 
-        freelocale(loc);
         *ret_d = (double) d;
         return 0;
 }
@@ -541,19 +598,20 @@ int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) {
 
 int parse_percent_unbounded(const char *p) {
         const char *pc, *n;
-        unsigned v;
-        int r;
+        int r, v;
 
         pc = endswith(p, "%");
         if (!pc)
                 return -EINVAL;
 
         n = strndupa(p, pc - p);
-        r = safe_atou(n, &v);
+        r = safe_atoi(n, &v);
         if (r < 0)
                 return r;
+        if (v < 0)
+                return -ERANGE;
 
-        return (int) v;
+        return v;
 }
 
 int parse_percent(const char *p) {
@@ -566,7 +624,6 @@ int parse_percent(const char *p) {
         return v;
 }
 
-#if 0 /* NM_IGNORED */
 int parse_nice(const char *p, int *ret) {
         int n, r;
 
diff --git a/src/systemd/src/basic/parse-util.h b/src/systemd/src/basic/parse-util.h
index dc09782c..1605cc4f 100644
--- a/src/systemd/src/basic/parse-util.h
+++ b/src/systemd/src/basic/parse-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -37,18 +38,34 @@ int parse_ifindex(const char *s, int *ret);
 
 int parse_size(const char *t, uint64_t base, uint64_t *size);
 int parse_range(const char *t, unsigned *lower, unsigned *upper);
+int parse_errno(const char *t);
+int parse_syscall_and_errno(const char *in, char **name, int *error);
 
 #define FORMAT_BYTES_MAX 8
 char *format_bytes(char *buf, size_t l, uint64_t t);
 
-int safe_atou(const char *s, unsigned *ret_u);
+int safe_atou_full(const char *s, unsigned base, unsigned *ret_u);
+
+static inline int safe_atou(const char *s, unsigned *ret_u) {
+        return safe_atou_full(s, 0, ret_u);
+}
+
 int safe_atoi(const char *s, int *ret_i);
 int safe_atollu(const char *s, unsigned long long *ret_u);
 int safe_atolli(const char *s, long long int *ret_i);
 
 int safe_atou8(const char *s, uint8_t *ret);
 
-int safe_atou16(const char *s, uint16_t *ret);
+int safe_atou16_full(const char *s, unsigned base, uint16_t *ret);
+
+static inline int safe_atou16(const char *s, uint16_t *ret) {
+        return safe_atou16_full(s, 0, ret);
+}
+
+static inline int safe_atoux16(const char *s, uint16_t *ret) {
+        return safe_atou16_full(s, 16, ret);
+}
+
 int safe_atoi16(const char *s, int16_t *ret);
 
 static inline int safe_atou32(const char *s, uint32_t *ret_u) {
diff --git a/src/systemd/src/basic/path-util.c b/src/systemd/src/basic/path-util.c
index 1bdcc653..23827c19 100644
--- a/src/systemd/src/basic/path-util.c
+++ b/src/systemd/src/basic/path-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -83,14 +84,36 @@ char *path_make_absolute(const char *p, const char *prefix) {
         /* Makes every item in the list an absolute path by prepending
          * the prefix, if specified and necessary */
 
-        if (path_is_absolute(p) || !prefix)
+        if (path_is_absolute(p) || isempty(prefix))
                 return strdup(p);
 
-        return strjoin(prefix, "/", p);
+        if (endswith(prefix, "/"))
+                return strjoin(prefix, p);
+        else
+                return strjoin(prefix, "/", p);
+}
+
+int safe_getcwd(char **ret) {
+        char *cwd;
+
+        cwd = get_current_dir_name();
+        if (!cwd)
+                return negative_errno();
+
+        /* Let's make sure the directory is really absolute, to protect us from the logic behind
+         * CVE-2018-1000001 */
+        if (cwd[0] != '/') {
+                free(cwd);
+                return -ENOMEDIUM;
+        }
+
+        *ret = cwd;
+        return 0;
 }
 
 int path_make_absolute_cwd(const char *p, char **ret) {
         char *c;
+        int r;
 
         assert(p);
         assert(ret);
@@ -103,11 +126,14 @@ int path_make_absolute_cwd(const char *p, char **ret) {
         else {
                 _cleanup_free_ char *cwd = NULL;
 
-                cwd = get_current_dir_name();
-                if (!cwd)
-                        return negative_errno();
+                r = safe_getcwd(&cwd);
+                if (r < 0)
+                        return r;
 
-                c = strjoin(cwd, "/", p);
+                if (endswith(cwd, "/"))
+                        c = strjoin(cwd, p);
+                else
+                        c = strjoin(cwd, "/", p);
         }
         if (!c)
                 return -ENOMEM;
@@ -225,8 +251,8 @@ int path_strv_make_absolute_cwd(char **l) {
                 if (r < 0)
                         return r;
 
-                free(*s);
-                *s = t;
+                path_kill_slashes(t);
+                free_and_replace(*s, t);
         }
 
         return 0;
@@ -267,8 +293,7 @@ char **path_strv_resolve(char **l, const char *root) {
                 r = chase_symlinks(t, root, 0, &u);
                 if (r == -ENOENT) {
                         if (root) {
-                                u = orig;
-                                orig = NULL;
+                                u = TAKE_PTR(orig);
                                 free(t);
                         } else
                                 u = t;
@@ -542,7 +567,7 @@ bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool upd
 
         assert(timestamp);
 
-        if (paths == NULL)
+        if (!paths)
                 return false;
 
         STRV_FOREACH(i, paths) {
@@ -706,6 +731,37 @@ char* dirname_malloc(const char *path) {
 
         return dir2;
 }
+
+const char *last_path_component(const char *path) {
+        /* Finds the last component of the path, preserving the
+         * optional trailing slash that signifies a directory.
+         *    a/b/c → c
+         *    a/b/c/ → c/
+         *    / → /
+         *    // → /
+         *    /foo/a → a
+         *    /foo/a/ → a/
+         * This is different than basename, which returns "" when
+         * a trailing slash is present.
+         */
+
+        unsigned l, k;
+
+        l = k = strlen(path);
+        if (l == 0) /* special case — an empty string */
+                return path;
+
+        while (k > 0 && path[k-1] == '/')
+                k--;
+
+        if (k == 0) /* the root directory */
+                return path + l - 1;
+
+        while (k > 0 && path[k-1] != '/')
+                k--;
+
+        return path + k;
+}
 #endif /* NM_IGNORED */
 
 bool filename_is_valid(const char *p) {
@@ -728,7 +784,7 @@ bool filename_is_valid(const char *p) {
 }
 
 #if 0 /* NM_IGNORED */
-bool path_is_safe(const char *p) {
+bool path_is_normalized(const char *p) {
 
         if (isempty(p))
                 return false;
@@ -742,7 +798,6 @@ bool path_is_safe(const char *p) {
         if (strlen(p)+1 > PATH_MAX)
                 return false;
 
-        /* The following two checks are not really dangerous, but hey, they still are confusing */
         if (startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./"))
                 return false;
 
@@ -858,7 +913,9 @@ int systemd_installation_has_version(const char *root, unsigned minimal_version)
                         * for Gentoo which does a merge without making /lib a symlink.
                         */
                        "lib/systemd/libsystemd-shared-*.so\0"
-                       "usr/lib/systemd/libsystemd-shared-*.so\0") {
+                       "lib64/systemd/libsystemd-shared-*.so\0"
+                       "usr/lib/systemd/libsystemd-shared-*.so\0"
+                       "usr/lib64/systemd/libsystemd-shared-*.so\0") {
 
                 _cleanup_strv_free_ char **names = NULL;
                 _cleanup_free_ char *path = NULL;
diff --git a/src/systemd/src/basic/path-util.h b/src/systemd/src/basic/path-util.h
index 399ed5f9..8848abe9 100644
--- a/src/systemd/src/basic/path-util.h
+++ b/src/systemd/src/basic/path-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -28,8 +29,14 @@
 #include "time-util.h"
 
 #if 0 /* NM_IGNORED */
-#define DEFAULT_PATH_NORMAL "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
-#define DEFAULT_PATH_SPLIT_USR DEFAULT_PATH_NORMAL ":/sbin:/bin"
+#if HAVE_SPLIT_BIN
+#  define PATH_SBIN_BIN(x) x "sbin:" x "bin"
+#else
+#  define PATH_SBIN_BIN(x) x "bin"
+#endif
+
+#define DEFAULT_PATH_NORMAL PATH_SBIN_BIN("/usr/local/") ":" PATH_SBIN_BIN("/usr/")
+#define DEFAULT_PATH_SPLIT_USR DEFAULT_PATH_NORMAL ":" PATH_SBIN_BIN("/")
 
 #if HAVE_SPLIT_USR
 #  define DEFAULT_PATH DEFAULT_PATH_SPLIT_USR
@@ -42,6 +49,7 @@ bool is_path(const char *p) _pure_;
 int path_split_and_make_absolute(const char *p, char ***ret);
 bool path_is_absolute(const char *p) _pure_;
 char* path_make_absolute(const char *p, const char *prefix);
+int safe_getcwd(char **ret);
 int path_make_absolute_cwd(const char *p, char **ret);
 int path_make_relative(const char *from_dir, const char *to_path, char **_r);
 char* path_kill_slashes(char *path);
@@ -131,9 +139,10 @@ char *prefix_root(const char *root, const char *path);
 int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg);
 
 char* dirname_malloc(const char *path);
+const char *last_path_component(const char *path);
 
 bool filename_is_valid(const char *p) _pure_;
-bool path_is_safe(const char *p) _pure_;
+bool path_is_normalized(const char *p) _pure_;
 
 char *file_in_same_dir(const char *path, const char *filename);
 
diff --git a/src/systemd/src/basic/prioq.c b/src/systemd/src/basic/prioq.c
index 64ad638e..1e81bc71 100644
--- a/src/systemd/src/basic/prioq.c
+++ b/src/systemd/src/basic/prioq.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -174,7 +175,7 @@ int prioq_put(Prioq *q, void *data, unsigned *idx) {
                 struct prioq_item *j;
 
                 n = MAX((q->n_items+1) * 2, 16u);
-                j = realloc(q->items, sizeof(struct prioq_item) * n);
+                j = reallocarray(q->items, n, sizeof(struct prioq_item));
                 if (!j)
                         return -ENOMEM;
 
diff --git a/src/systemd/src/basic/prioq.h b/src/systemd/src/basic/prioq.h
index 113c73d0..a222955d 100644
--- a/src/systemd/src/basic/prioq.h
+++ b/src/systemd/src/basic/prioq.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/basic/process-util.c b/src/systemd/src/basic/process-util.c
index 272030d1..4128ac54 100644
--- a/src/systemd/src/basic/process-util.c
+++ b/src/systemd/src/basic/process-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -27,6 +28,7 @@
 #include <signal.h>
 #include <stdbool.h>
 #include <stdio.h>
+#include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mman.h>
@@ -58,6 +60,7 @@
 #include "stat-util.h"
 #include "string-table.h"
 #include "string-util.h"
+#include "terminal-util.h"
 #include "user-util.h"
 #include "util.h"
 
@@ -134,6 +137,8 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
                 return -errno;
         }
 
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         if (max_length == 1) {
 
                 /* If there's only room for one byte, return the empty string */
@@ -297,10 +302,17 @@ int rename_process(const char name[]) {
         if (isempty(name))
                 return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
 
+        if (!is_main_thread())
+                return -EPERM; /* Let's not allow setting the process name from other threads than the main one, as we
+                                * cache things without locking, and we make assumptions that PR_SET_NAME sets the
+                                * process name that isn't correct on any other threads */
+
         l = strlen(name);
 
-        /* First step, change the comm field. */
-        (void) prctl(PR_SET_NAME, name);
+        /* First step, change the comm field. The main thread's comm is identical to the process comm. This means we
+         * can use PR_SET_NAME, which sets the thread name for the calling thread. */
+        if (prctl(PR_SET_NAME, name) < 0)
+                log_debug_errno(errno, "PR_SET_NAME failed: %m");
         if (l > 15) /* Linux process names can be 15 chars at max */
                 truncated = true;
 
@@ -391,35 +403,61 @@ use_saved_argv:
 }
 
 int is_kernel_thread(pid_t pid) {
+        _cleanup_free_ char *line = NULL;
+        unsigned long long flags;
+        size_t l, i;
         const char *p;
-        size_t count;
-        char c;
-        bool eof;
-        FILE *f;
+        char *q;
+        int r;
 
         if (IN_SET(pid, 0, 1) || pid == getpid_cached()) /* pid 1, and we ourselves certainly aren't a kernel thread */
                 return 0;
+        if (!pid_is_valid(pid))
+                return -EINVAL;
 
-        assert(pid > 1);
+        p = procfs_file_alloca(pid, "stat");
+        r = read_one_line_file(p, &line);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
 
-        p = procfs_file_alloca(pid, "cmdline");
-        f = fopen(p, "re");
-        if (!f) {
-                if (errno == ENOENT)
-                        return -ESRCH;
-                return -errno;
+        /* Skip past the comm field */
+        q = strrchr(line, ')');
+        if (!q)
+                return -EINVAL;
+        q++;
+
+        /* Skip 6 fields to reach the flags field */
+        for (i = 0; i < 6; i++) {
+                l = strspn(q, WHITESPACE);
+                if (l < 1)
+                        return -EINVAL;
+                q += l;
+
+                l = strcspn(q, WHITESPACE);
+                if (l < 1)
+                        return -EINVAL;
+                q += l;
         }
 
-        count = fread(&c, 1, 1, f);
-        eof = feof(f);
-        fclose(f);
+        /* Skip preceeding whitespace */
+        l = strspn(q, WHITESPACE);
+        if (l < 1)
+                return -EINVAL;
+        q += l;
 
-        /* Kernel threads have an empty cmdline */
+        /* Truncate the rest */
+        l = strcspn(q, WHITESPACE);
+        if (l < 1)
+                return -EINVAL;
+        q[l] = 0;
 
-        if (count <= 0)
-                return eof ? 1 : -errno;
+        r = safe_atollu(q, &flags);
+        if (r < 0)
+                return r;
 
-        return 0;
+        return !!(flags & PF_KTHREAD);
 }
 
 int get_process_capeff(pid_t pid, char **capeff) {
@@ -491,6 +529,8 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) {
                 return -errno;
         }
 
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         FOREACH_LINE(line, f, return -errno) {
                 char *l;
 
@@ -569,6 +609,8 @@ int get_process_environ(pid_t pid, char **env) {
                 return -errno;
         }
 
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         while ((c = fgetc(f)) != EOF) {
                 if (!GREEDY_REALLOC(outcome, allocated, sz + 5))
                         return -ENOMEM;
@@ -586,8 +628,7 @@ int get_process_environ(pid_t pid, char **env) {
         } else
                 outcome[sz] = '\0';
 
-        *env = outcome;
-        outcome = NULL;
+        *env = TAKE_PTR(outcome);
 
         return 0;
 }
@@ -674,32 +715,104 @@ int wait_for_terminate(pid_t pid, siginfo_t *status) {
  * A warning is emitted if the process terminates abnormally,
  * and also if it returns non-zero unless check_exit_code is true.
  */
-int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code) {
-        int r;
+int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags) {
+        _cleanup_free_ char *buffer = NULL;
         siginfo_t status;
+        int r, prio;
 
-        assert(name);
         assert(pid > 1);
 
+        if (!name) {
+                r = get_process_comm(pid, &buffer);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to acquire process name of " PID_FMT ", ignoring: %m", pid);
+                else
+                        name = buffer;
+        }
+
+        prio = flags & WAIT_LOG_ABNORMAL ? LOG_ERR : LOG_DEBUG;
+
         r = wait_for_terminate(pid, &status);
         if (r < 0)
-                return log_warning_errno(r, "Failed to wait for %s: %m", name);
+                return log_full_errno(prio, r, "Failed to wait for %s: %m", strna(name));
 
         if (status.si_code == CLD_EXITED) {
-                if (status.si_status != 0)
-                        log_full(check_exit_code ? LOG_WARNING : LOG_DEBUG,
-                                 "%s failed with error code %i.", name, status.si_status);
+                if (status.si_status != EXIT_SUCCESS)
+                        log_full(flags & WAIT_LOG_NON_ZERO_EXIT_STATUS ? LOG_ERR : LOG_DEBUG,
+                                 "%s failed with exit status %i.", strna(name), status.si_status);
                 else
                         log_debug("%s succeeded.", name);
 
                 return status.si_status;
+
         } else if (IN_SET(status.si_code, CLD_KILLED, CLD_DUMPED)) {
 
-                log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status));
+                log_full(prio, "%s terminated by signal %s.", strna(name), signal_to_string(status.si_status));
                 return -EPROTO;
         }
 
-        log_warning("%s failed due to unknown reason.", name);
+        log_full(prio, "%s failed due to unknown reason.", strna(name));
+        return -EPROTO;
+}
+
+/*
+ * Return values:
+ * < 0 : wait_for_terminate_with_timeout() failed to get the state of the
+ *       process, the process timed out, the process was terminated by a
+ *       signal, or failed for an unknown reason.
+ * >=0 : The process terminated normally with no failures.
+ *
+ * Success is indicated by a return value of zero, a timeout is indicated
+ * by ETIMEDOUT, and all other child failure states are indicated by error
+ * is indicated by a non-zero value.
+ */
+int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout) {
+        sigset_t mask;
+        int r;
+        usec_t until;
+
+        assert_se(sigemptyset(&mask) == 0);
+        assert_se(sigaddset(&mask, SIGCHLD) == 0);
+
+        /* Drop into a sigtimewait-based timeout. Waiting for the
+         * pid to exit. */
+        until = now(CLOCK_MONOTONIC) + timeout;
+        for (;;) {
+                usec_t n;
+                siginfo_t status = {};
+                struct timespec ts;
+
+                n = now(CLOCK_MONOTONIC);
+                if (n >= until)
+                        break;
+
+                r = sigtimedwait(&mask, NULL, timespec_store(&ts, until - n)) < 0 ? -errno : 0;
+                /* Assuming we woke due to the child exiting. */
+                if (waitid(P_PID, pid, &status, WEXITED|WNOHANG) == 0) {
+                        if (status.si_pid == pid) {
+                                /* This is the correct child.*/
+                                if (status.si_code == CLD_EXITED)
+                                        return (status.si_status == 0) ? 0 : -EPROTO;
+                                else
+                                        return -EPROTO;
+                        }
+                }
+                /* Not the child, check for errors and proceed appropriately */
+                if (r < 0) {
+                        switch (r) {
+                        case -EAGAIN:
+                                /* Timed out, child is likely hung. */
+                                return -ETIMEDOUT;
+                        case -EINTR:
+                                /* Received a different signal and should retry */
+                                continue;
+                        default:
+                                /* Return any unexpected errors */
+                                return r;
+                        }
+                }
+        }
+
         return -EPROTO;
 }
 
@@ -711,6 +824,8 @@ void sigkill_wait(pid_t pid) {
 }
 
 void sigkill_waitp(pid_t *pid) {
+        PROTECT_ERRNO;
+
         if (!pid)
                 return;
         if (*pid <= 1)
@@ -719,6 +834,13 @@ void sigkill_waitp(pid_t *pid) {
         sigkill_wait(*pid);
 }
 
+void sigterm_wait(pid_t pid) {
+        assert(pid > 1);
+
+        if (kill_and_sigcont(pid, SIGTERM) > 0)
+                (void) wait_for_terminate(pid, NULL);
+}
+
 int kill_and_sigcont(pid_t pid, int sig) {
         int r;
 
@@ -732,17 +854,33 @@ int kill_and_sigcont(pid_t pid, int sig) {
         return r;
 }
 
-int getenv_for_pid(pid_t pid, const char *field, char **_value) {
+int getenv_for_pid(pid_t pid, const char *field, char **ret) {
         _cleanup_fclose_ FILE *f = NULL;
         char *value = NULL;
-        int r;
         bool done = false;
-        size_t l;
         const char *path;
+        size_t l;
 
         assert(pid >= 0);
         assert(field);
-        assert(_value);
+        assert(ret);
+
+        if (pid == 0 || pid == getpid_cached()) {
+                const char *e;
+
+                e = getenv(field);
+                if (!e) {
+                        *ret = NULL;
+                        return 0;
+                }
+
+                value = strdup(e);
+                if (!value)
+                        return -ENOMEM;
+
+                *ret = value;
+                return 1;
+        }
 
         path = procfs_file_alloca(pid, "environ");
 
@@ -750,11 +888,13 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) {
         if (!f) {
                 if (errno == ENOENT)
                         return -ESRCH;
+
                 return -errno;
         }
 
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         l = strlen(field);
-        r = 0;
 
         do {
                 char line[LINE_MAX];
@@ -779,14 +919,14 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) {
                         if (!value)
                                 return -ENOMEM;
 
-                        r = 1;
-                        break;
+                        *ret = value;
+                        return 1;
                 }
 
         } while (!done);
 
-        *_value = value;
-        return r;
+        *ret = NULL;
+        return 0;
 }
 
 bool pid_is_unwaited(pid_t pid) {
@@ -853,7 +993,7 @@ bool is_main_thread(void) {
 }
 
 #if 0 /* NM_IGNORED */
-noreturn void freeze(void) {
+_noreturn_ void freeze(void) {
 
         log_close();
 
@@ -862,6 +1002,17 @@ noreturn void freeze(void) {
 
         sync();
 
+        /* Let's not freeze right away, but keep reaping zombies. */
+        for (;;) {
+                int r;
+                siginfo_t si = {};
+
+                r = waitid(P_ALL, 0, &si, WEXITED);
+                if (r < 0 && errno != EINTR)
+                        break;
+        }
+
+        /* waitid() failed with an unexpected error, things are really borked. Freeze now! */
         for (;;)
                 pause();
 }
@@ -1010,7 +1161,7 @@ int ioprio_parse_priority(const char *s, int *ret) {
 
 static pid_t cached_pid = CACHED_PID_UNSET;
 
-static void reset_cached_pid(void) {
+void reset_cached_pid(void) {
         /* Invoked in the child after a fork(), i.e. at the first moment the PID changed */
         cached_pid = CACHED_PID_UNSET;
 }
@@ -1022,6 +1173,7 @@ extern int __register_atfork(void (*prepare) (void), void (*parent) (void), void
 extern void* __dso_handle __attribute__ ((__weak__));
 
 pid_t getpid_cached(void) {
+        static bool installed = false;
         pid_t current_value;
 
         /* getpid_cached() is much like getpid(), but caches the value in local memory, to avoid having to invoke a
@@ -1040,12 +1192,20 @@ pid_t getpid_cached(void) {
         case CACHED_PID_UNSET: { /* Not initialized yet, then do so now */
                 pid_t new_pid;
 
-                new_pid = getpid();
+                new_pid = raw_getpid();
+
+                if (!installed) {
+                        /* __register_atfork() either returns 0 or -ENOMEM, in its glibc implementation. Since it's
+                         * only half-documented (glibc doesn't document it but LSB does — though only superficially)
+                         * we'll check for errors only in the most generic fashion possible. */
+
+                        if (__register_atfork(NULL, NULL, reset_cached_pid, __dso_handle) != 0) {
+                                /* OOM? Let's try again later */
+                                cached_pid = CACHED_PID_UNSET;
+                                return new_pid;
+                        }
 
-                if (__register_atfork(NULL, NULL, reset_cached_pid, __dso_handle) != 0) {
-                        /* OOM? Let's try again later */
-                        cached_pid = CACHED_PID_UNSET;
-                        return new_pid;
+                        installed = true;
                 }
 
                 cached_pid = new_pid;
@@ -1053,7 +1213,7 @@ pid_t getpid_cached(void) {
         }
 
         case CACHED_PID_BUSY: /* Somebody else is currently initializing */
-                return getpid();
+                return raw_getpid();
 
         default: /* Properly initialized */
                 return current_value;
@@ -1061,6 +1221,252 @@ pid_t getpid_cached(void) {
 }
 
 #if 0 /* NM_IGNORED */
+int must_be_root(void) {
+
+        if (geteuid() == 0)
+                return 0;
+
+        log_error("Need to be root.");
+        return -EPERM;
+}
+
+int safe_fork_full(
+                const char *name,
+                const int except_fds[],
+                size_t n_except_fds,
+                ForkFlags flags,
+                pid_t *ret_pid) {
+
+        pid_t original_pid, pid;
+        sigset_t saved_ss, ss;
+        bool block_signals = false;
+        int prio, r;
+
+        /* A wrapper around fork(), that does a couple of important initializations in addition to mere forking. Always
+         * returns the child's PID in *ret_pid. Returns == 0 in the child, and > 0 in the parent. */
+
+        prio = flags & FORK_LOG ? LOG_ERR : LOG_DEBUG;
+
+        original_pid = getpid_cached();
+
+        if (flags & (FORK_RESET_SIGNALS|FORK_DEATHSIG)) {
+
+                /* We temporarily block all signals, so that the new child has them blocked initially. This way, we can
+                 * be sure that SIGTERMs are not lost we might send to the child. */
+
+                if (sigfillset(&ss) < 0)
+                        return log_full_errno(prio, errno, "Failed to reset signal set: %m");
+
+                block_signals = true;
+
+        } else if (flags & FORK_WAIT) {
+
+                /* Let's block SIGCHLD at least, so that we can safely watch for the child process */
+
+                if (sigemptyset(&ss) < 0)
+                        return log_full_errno(prio, errno, "Failed to clear signal set: %m");
+
+                if (sigaddset(&ss, SIGCHLD) < 0)
+                        return log_full_errno(prio, errno, "Failed to add SIGCHLD to signal set: %m");
+
+                block_signals = true;
+        }
+
+        if (block_signals)
+                if (sigprocmask(SIG_SETMASK, &ss, &saved_ss) < 0)
+                        return log_full_errno(prio, errno, "Failed to set signal mask: %m");
+
+        if (flags & FORK_NEW_MOUNTNS)
+                pid = raw_clone(SIGCHLD|CLONE_NEWNS);
+        else
+                pid = fork();
+        if (pid < 0) {
+                r = -errno;
+
+                if (block_signals) /* undo what we did above */
+                        (void) sigprocmask(SIG_SETMASK, &saved_ss, NULL);
+
+                return log_full_errno(prio, r, "Failed to fork: %m");
+        }
+        if (pid > 0) {
+                /* We are in the parent process */
+
+                log_debug("Successfully forked off '%s' as PID " PID_FMT ".", strna(name), pid);
+
+                if (flags & FORK_WAIT) {
+                        r = wait_for_terminate_and_check(name, pid, (flags & FORK_LOG ? WAIT_LOG : 0));
+                        if (r < 0)
+                                return r;
+                        if (r != EXIT_SUCCESS) /* exit status > 0 should be treated as failure, too */
+                                return -EPROTO;
+                }
+
+                if (block_signals) /* undo what we did above */
+                        (void) sigprocmask(SIG_SETMASK, &saved_ss, NULL);
+
+                if (ret_pid)
+                        *ret_pid = pid;
+
+                return 1;
+        }
+
+        /* We are in the child process */
+
+        if (flags & FORK_REOPEN_LOG) {
+                /* Close the logs if requested, before we log anything. And make sure we reopen it if needed. */
+                log_close();
+                log_set_open_when_needed(true);
+        }
+
+        if (name) {
+                r = rename_process(name);
+                if (r < 0)
+                        log_full_errno(flags & FORK_LOG ? LOG_WARNING : LOG_DEBUG,
+                                       r, "Failed to rename process, ignoring: %m");
+        }
+
+        if (flags & FORK_DEATHSIG)
+                if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) {
+                        log_full_errno(prio, errno, "Failed to set death signal: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+        if (flags & FORK_RESET_SIGNALS) {
+                r = reset_all_signal_handlers();
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to reset signal handlers: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                /* This implicitly undoes the signal mask stuff we did before the fork()ing above */
+                r = reset_signal_mask();
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to reset signal mask: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        } else if (block_signals) { /* undo what we did above */
+                if (sigprocmask(SIG_SETMASK, &saved_ss, NULL) < 0) {
+                        log_full_errno(prio, errno, "Failed to restore signal mask: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        if (flags & FORK_DEATHSIG) {
+                pid_t ppid;
+                /* Let's see if the parent PID is still the one we started from? If not, then the parent
+                 * already died by the time we set PR_SET_PDEATHSIG, hence let's emulate the effect */
+
+                ppid = getppid();
+                if (ppid == 0)
+                        /* Parent is in a differn't PID namespace. */;
+                else if (ppid != original_pid) {
+                        log_debug("Parent died early, raising SIGTERM.");
+                        (void) raise(SIGTERM);
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        if (flags & FORK_CLOSE_ALL_FDS) {
+                /* Close the logs here in case it got reopened above, as close_all_fds() would close them for us */
+                log_close();
+
+                r = close_all_fds(except_fds, n_except_fds);
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to close all file descriptors: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        /* When we were asked to reopen the logs, do so again now */
+        if (flags & FORK_REOPEN_LOG) {
+                log_open();
+                log_set_open_when_needed(false);
+        }
+
+        if (flags & FORK_NULL_STDIO) {
+                r = make_null_stdio();
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to connect stdin/stdout to /dev/null: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        if (ret_pid)
+                *ret_pid = getpid_cached();
+
+        return 0;
+}
+
+int fork_agent(const char *name, const int except[], unsigned n_except, pid_t *ret_pid, const char *path, ...) {
+        bool stdout_is_tty, stderr_is_tty;
+        unsigned n, i;
+        va_list ap;
+        char **l;
+        int r;
+
+        assert(path);
+
+        /* Spawns a temporary TTY agent, making sure it goes away when we go away */
+
+        r = safe_fork_full(name, except, n_except, FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS, ret_pid);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return 0;
+
+        /* In the child: */
+
+        stdout_is_tty = isatty(STDOUT_FILENO);
+        stderr_is_tty = isatty(STDERR_FILENO);
+
+        if (!stdout_is_tty || !stderr_is_tty) {
+                int fd;
+
+                /* Detach from stdout/stderr. and reopen
+                 * /dev/tty for them. This is important to
+                 * ensure that when systemctl is started via
+                 * popen() or a similar call that expects to
+                 * read EOF we actually do generate EOF and
+                 * not delay this indefinitely by because we
+                 * keep an unused copy of stdin around. */
+                fd = open("/dev/tty", O_WRONLY);
+                if (fd < 0) {
+                        log_error_errno(errno, "Failed to open /dev/tty: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                if (!stdout_is_tty && dup2(fd, STDOUT_FILENO) < 0) {
+                        log_error_errno(errno, "Failed to dup2 /dev/tty: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                if (!stderr_is_tty && dup2(fd, STDERR_FILENO) < 0) {
+                        log_error_errno(errno, "Failed to dup2 /dev/tty: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                safe_close_above_stdio(fd);
+        }
+
+        /* Count arguments */
+        va_start(ap, path);
+        for (n = 0; va_arg(ap, char*); n++)
+                ;
+        va_end(ap);
+
+        /* Allocate strv */
+        l = alloca(sizeof(char *) * (n + 1));
+
+        /* Fill in arguments */
+        va_start(ap, path);
+        for (i = 0; i <= n; i++)
+                l[i] = va_arg(ap, char*);
+        va_end(ap);
+
+        execv(path, l);
+        _exit(EXIT_FAILURE);
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",
@@ -1068,7 +1474,7 @@ static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_IDLE] = "idle"
 };
 
-DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, INT_MAX);
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, IOPRIO_N_CLASSES);
 
 static const char *const sigchld_code_table[] = {
         [CLD_EXITED] = "exited",
diff --git a/src/systemd/src/basic/process-util.h b/src/systemd/src/basic/process-util.h
index e1bd2c5b..3525c2d6 100644
--- a/src/systemd/src/basic/process-util.h
+++ b/src/systemd/src/basic/process-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -20,6 +21,7 @@
 ***/
 
 #include <alloca.h>
+#include <errno.h>
 #include <sched.h>
 #include <signal.h>
 #include <stdbool.h>
@@ -32,6 +34,7 @@
 #include "format-util.h"
 #include "ioprio.h"
 #include "macro.h"
+#include "time-util.h"
 
 #define procfs_file_alloca(pid, field)                                  \
         ({                                                              \
@@ -40,7 +43,7 @@
                 if (_pid_ == 0) {                                       \
                         _r_ = ("/proc/self/" field);                    \
                 } else {                                                \
-                        _r_ = alloca(strlen("/proc/") + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \
+                        _r_ = alloca(STRLEN("/proc/") + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \
                         sprintf((char*) _r_, "/proc/"PID_FMT"/" field, _pid_);                       \
                 }                                                       \
                 _r_;                                                    \
@@ -59,10 +62,21 @@ int get_process_environ(pid_t pid, char **environ);
 int get_process_ppid(pid_t pid, pid_t *ppid);
 
 int wait_for_terminate(pid_t pid, siginfo_t *status);
-int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code);
+
+typedef enum WaitFlags {
+        WAIT_LOG_ABNORMAL             = 1U << 0,
+        WAIT_LOG_NON_ZERO_EXIT_STATUS = 1U << 1,
+
+        /* A shortcut for requesting the most complete logging */
+        WAIT_LOG = WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS,
+} WaitFlags;
+
+int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags);
+int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout);
 
 void sigkill_wait(pid_t pid);
 void sigkill_waitp(pid_t *pid);
+void sigterm_wait(pid_t pid);
 
 int kill_and_sigcont(pid_t pid, int sig);
 
@@ -77,7 +91,7 @@ int pid_from_same_root_fs(pid_t pid);
 
 bool is_main_thread(void);
 
-noreturn void freeze(void);
+_noreturn_ void freeze(void);
 
 bool oom_score_adjust_is_valid(int oa);
 
@@ -103,8 +117,13 @@ int sigchld_code_from_string(const char *s) _pure_;
 int sched_policy_to_string_alloc(int i, char **s);
 int sched_policy_from_string(const char *s);
 
-#define PTR_TO_PID(p) ((pid_t) ((uintptr_t) p))
-#define PID_TO_PTR(p) ((void*) ((uintptr_t) p))
+static inline pid_t PTR_TO_PID(const void *p) {
+        return (pid_t) ((uintptr_t) p);
+}
+
+static inline void* PID_TO_PTR(pid_t pid) {
+        return (void*) ((uintptr_t) pid);
+}
 
 void valgrind_summary_hack(void);
 
@@ -134,8 +153,56 @@ static inline bool ioprio_priority_is_valid(int i) {
 static inline bool pid_is_valid(pid_t p) {
         return p > 0;
 }
+
+static inline int sched_policy_to_string_alloc_with_check(int n, char **s) {
+        if (!sched_policy_is_valid(n))
+                return -EINVAL;
+
+        return sched_policy_to_string_alloc(n, s);
+}
 #endif /* NM_IGNORED */
 
 int ioprio_parse_priority(const char *s, int *ret);
 
 pid_t getpid_cached(void);
+void reset_cached_pid(void);
+
+int must_be_root(void);
+
+typedef enum ForkFlags {
+        FORK_RESET_SIGNALS = 1U << 0,
+        FORK_CLOSE_ALL_FDS = 1U << 1,
+        FORK_DEATHSIG      = 1U << 2,
+        FORK_NULL_STDIO    = 1U << 3,
+        FORK_REOPEN_LOG    = 1U << 4,
+        FORK_LOG           = 1U << 5,
+        FORK_WAIT          = 1U << 6,
+        FORK_NEW_MOUNTNS   = 1U << 7,
+} ForkFlags;
+
+int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid);
+
+static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) {
+        return safe_fork_full(name, NULL, 0, flags, ret_pid);
+}
+
+int fork_agent(const char *name, const int except[], unsigned n_except, pid_t *pid, const char *path, ...);
+
+#if SIZEOF_PID_T == 4
+/* The highest possibly (theoretic) pid_t value on this architecture. */
+#define PID_T_MAX ((pid_t) INT32_MAX)
+/* The maximum number of concurrent processes Linux allows on this architecture, as well as the highest valid PID value
+ * the kernel will potentially assign. This reflects a value compiled into the kernel (PID_MAX_LIMIT), and sets the
+ * upper boundary on what may be written to the /proc/sys/kernel/pid_max sysctl (but do note that the sysctl is off by
+ * 1, since PID 0 can never exist and there can hence only be one process less than the limit would suggest). Since
+ * these values are documented in proc(5) we feel quite confident that they are stable enough for the near future at
+ * least to define them here too. */
+#define TASKS_MAX 4194303U
+#elif SIZEOF_PID_T == 2
+#define PID_T_MAX ((pid_t) INT16_MAX)
+#define TASKS_MAX 32767U
+#else
+#error "Unknown pid_t size"
+#endif
+
+assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX)
diff --git a/src/systemd/src/basic/random-util.c b/src/systemd/src/basic/random-util.c
index 3b6ddb7d..29ee3b45 100644
--- a/src/systemd/src/basic/random-util.c
+++ b/src/systemd/src/basic/random-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -22,11 +23,12 @@
 #include <elf.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <linux/random.h>
 #include <stdbool.h>
+#include <stdint.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/time.h>
-#include <linux/random.h>
-#include <stdint.h>
 
 #if HAVE_SYS_AUXV_H
 #  include <sys/auxv.h>
diff --git a/src/systemd/src/basic/random-util.h b/src/systemd/src/basic/random-util.h
index 804e225f..dd870151 100644
--- a/src/systemd/src/basic/random-util.h
+++ b/src/systemd/src/basic/random-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/basic/refcnt.h b/src/systemd/src/basic/refcnt.h
index 1d77a644..ae2e446d 100644
--- a/src/systemd/src/basic/refcnt.h
+++ b/src/systemd/src/basic/refcnt.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/basic/set.h b/src/systemd/src/basic/set.h
index 12d0fda1..156ab4b0 100644
--- a/src/systemd/src/basic/set.h
+++ b/src/systemd/src/basic/set.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -107,6 +108,18 @@ static inline void *set_steal_first(Set *s) {
         return internal_hashmap_steal_first(HASHMAP_BASE(s));
 }
 
+#define set_clear_with_destructor(_s, _f)               \
+        ({                                              \
+                void *_item;                            \
+                while ((_item = set_steal_first(_s)))   \
+                        _f(_item);                      \
+        })
+#define set_free_with_destructor(_s, _f)                \
+        ({                                              \
+                set_clear_with_destructor(_s, _f);      \
+                set_free(_s);                           \
+        })
+
 /* no set_steal_first_key */
 /* no set_first_key */
 
diff --git a/src/systemd/src/basic/signal-util.h b/src/systemd/src/basic/signal-util.h
index dfd6eb56..f6c3396e 100644
--- a/src/systemd/src/basic/signal-util.h
+++ b/src/systemd/src/basic/signal-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -44,13 +45,20 @@ static inline void block_signals_reset(sigset_t *ss) {
         assert_se(sigprocmask(SIG_SETMASK, ss, NULL) >= 0);
 }
 
-#define BLOCK_SIGNALS(...)                                                        \
-        _cleanup_(block_signals_reset) _unused_ sigset_t _saved_sigset = ({       \
-                sigset_t t;                                                       \
-                assert_se(sigprocmask_many(SIG_BLOCK, &t, __VA_ARGS__, -1) >= 0); \
-                t;                                                                \
+#define BLOCK_SIGNALS(...)                                                         \
+        _cleanup_(block_signals_reset) _unused_ sigset_t _saved_sigset = ({        \
+                sigset_t _t;                                                       \
+                assert_se(sigprocmask_many(SIG_BLOCK, &_t, __VA_ARGS__, -1) >= 0); \
+                _t;                                                                \
         })
 
 static inline bool SIGNAL_VALID(int signo) {
         return signo > 0 && signo < _NSIG;
 }
+
+static inline const char* signal_to_string_with_check(int n) {
+        if (!SIGNAL_VALID(n))
+                return NULL;
+
+        return signal_to_string(n);
+}
diff --git a/src/systemd/src/basic/socket-util.c b/src/systemd/src/basic/socket-util.c
index 798ab16e..8ae68b6f 100644
--- a/src/systemd/src/basic/socket-util.c
+++ b/src/systemd/src/basic/socket-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -42,6 +43,7 @@
 #include "missing.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "process-util.h"
 #include "socket-util.h"
 #include "string-table.h"
 #include "string-util.h"
@@ -52,14 +54,24 @@
 
 #if 0 /* NM_IGNORED */
 #if ENABLE_IDN
-#  define IDN_FLAGS (NI_IDN|NI_IDN_USE_STD3_ASCII_RULES)
+#  define IDN_FLAGS NI_IDN
 #else
 #  define IDN_FLAGS 0
 #endif
 
+static const char* const socket_address_type_table[] = {
+        [SOCK_STREAM] = "Stream",
+        [SOCK_DGRAM] = "Datagram",
+        [SOCK_RAW] = "Raw",
+        [SOCK_RDM] = "ReliableDatagram",
+        [SOCK_SEQPACKET] = "SequentialPacket",
+        [SOCK_DCCP] = "DatagramCongestionControl",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(socket_address_type, int);
+
 int socket_address_parse(SocketAddress *a, const char *s) {
         char *e, *n;
-        unsigned u;
         int r;
 
         assert(a);
@@ -69,6 +81,8 @@ int socket_address_parse(SocketAddress *a, const char *s) {
         a->type = SOCK_STREAM;
 
         if (*s == '[') {
+                uint16_t port;
+
                 /* IPv6 in [x:.....:z]:p notation */
 
                 e = strchr(s+1, ']');
@@ -86,15 +100,12 @@ int socket_address_parse(SocketAddress *a, const char *s) {
                         return -EINVAL;
 
                 e++;
-                r = safe_atou(e, &u);
+                r = parse_ip_port(e, &port);
                 if (r < 0)
                         return r;
 
-                if (u <= 0 || u > 0xFFFF)
-                        return -EINVAL;
-
                 a->sockaddr.in6.sin6_family = AF_INET6;
-                a->sockaddr.in6.sin6_port = htobe16((uint16_t)u);
+                a->sockaddr.in6.sin6_port = htobe16(port);
                 a->size = sizeof(struct sockaddr_in6);
 
         } else if (*s == '/') {
@@ -124,13 +135,14 @@ int socket_address_parse(SocketAddress *a, const char *s) {
 
         } else if (startswith(s, "vsock:")) {
                 /* AF_VSOCK socket in vsock:cid:port notation */
-                const char *cid_start = s + strlen("vsock:");
+                const char *cid_start = s + STRLEN("vsock:");
+                unsigned port;
 
                 e = strchr(cid_start, ':');
                 if (!e)
                         return -EINVAL;
 
-                r = safe_atou(e+1, &u);
+                r = safe_atou(e+1, &port);
                 if (r < 0)
                         return r;
 
@@ -143,19 +155,18 @@ int socket_address_parse(SocketAddress *a, const char *s) {
                         a->sockaddr.vm.svm_cid = VMADDR_CID_ANY;
 
                 a->sockaddr.vm.svm_family = AF_VSOCK;
-                a->sockaddr.vm.svm_port = u;
+                a->sockaddr.vm.svm_port = port;
                 a->size = sizeof(struct sockaddr_vm);
 
         } else {
+                uint16_t port;
+
                 e = strchr(s, ':');
                 if (e) {
-                        r = safe_atou(e+1, &u);
+                        r = parse_ip_port(e + 1, &port);
                         if (r < 0)
                                 return r;
 
-                        if (u <= 0 || u > 0xFFFF)
-                                return -EINVAL;
-
                         n = strndupa(s, e-s);
 
                         /* IPv4 in w.x.y.z:p notation? */
@@ -166,7 +177,7 @@ int socket_address_parse(SocketAddress *a, const char *s) {
                         if (r > 0) {
                                 /* Gotcha, it's a traditional IPv4 address */
                                 a->sockaddr.in.sin_family = AF_INET;
-                                a->sockaddr.in.sin_port = htobe16((uint16_t)u);
+                                a->sockaddr.in.sin_port = htobe16(port);
                                 a->size = sizeof(struct sockaddr_in);
                         } else {
                                 unsigned idx;
@@ -180,7 +191,7 @@ int socket_address_parse(SocketAddress *a, const char *s) {
                                         return -EINVAL;
 
                                 a->sockaddr.in6.sin6_family = AF_INET6;
-                                a->sockaddr.in6.sin6_port = htobe16((uint16_t)u);
+                                a->sockaddr.in6.sin6_port = htobe16(port);
                                 a->sockaddr.in6.sin6_scope_id = idx;
                                 a->sockaddr.in6.sin6_addr = in6addr_any;
                                 a->size = sizeof(struct sockaddr_in6);
@@ -188,21 +199,18 @@ int socket_address_parse(SocketAddress *a, const char *s) {
                 } else {
 
                         /* Just a port */
-                        r = safe_atou(s, &u);
+                        r = parse_ip_port(s, &port);
                         if (r < 0)
                                 return r;
 
-                        if (u <= 0 || u > 0xFFFF)
-                                return -EINVAL;
-
                         if (socket_ipv6_is_supported()) {
                                 a->sockaddr.in6.sin6_family = AF_INET6;
-                                a->sockaddr.in6.sin6_port = htobe16((uint16_t)u);
+                                a->sockaddr.in6.sin6_port = htobe16(port);
                                 a->sockaddr.in6.sin6_addr = in6addr_any;
                                 a->size = sizeof(struct sockaddr_in6);
                         } else {
                                 a->sockaddr.in.sin_family = AF_INET;
-                                a->sockaddr.in.sin_port = htobe16((uint16_t)u);
+                                a->sockaddr.in.sin_port = htobe16(port);
                                 a->sockaddr.in.sin_addr.s_addr = INADDR_ANY;
                                 a->size = sizeof(struct sockaddr_in);
                         }
@@ -530,22 +538,25 @@ bool socket_address_matches_fd(const SocketAddress *a, int fd) {
         return socket_address_equal(a, &b);
 }
 
-int sockaddr_port(const struct sockaddr *_sa, unsigned *port) {
+int sockaddr_port(const struct sockaddr *_sa, unsigned *ret_port) {
         union sockaddr_union *sa = (union sockaddr_union*) _sa;
 
+        /* Note, this returns the port as 'unsigned' rather than 'uint16_t', as AF_VSOCK knows larger ports */
+
         assert(sa);
 
         switch (sa->sa.sa_family) {
+
         case AF_INET:
-                *port = be16toh(sa->in.sin_port);
+                *ret_port = be16toh(sa->in.sin_port);
                 return 0;
 
         case AF_INET6:
-                *port = be16toh(sa->in6.sin6_port);
+                *ret_port = be16toh(sa->in6.sin6_port);
                 return 0;
 
         case AF_VSOCK:
-                *port = sa->vm.svm_port;
+                *ret_port = sa->vm.svm_port;
                 return 0;
 
         default:
@@ -750,19 +761,6 @@ int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret)
         return 0;
 }
 
-int getnameinfo_pretty(int fd, char **ret) {
-        union sockaddr_union sa;
-        socklen_t salen = sizeof(sa);
-
-        assert(fd >= 0);
-        assert(ret);
-
-        if (getsockname(fd, &sa.sa, &salen) < 0)
-                return -errno;
-
-        return socknameinfo_pretty(&sa, salen, ret);
-}
-
 int socket_address_unlink(SocketAddress *a) {
         assert(a);
 
@@ -809,6 +807,18 @@ static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIN
 
 DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only);
 
+SocketAddressBindIPv6Only parse_socket_address_bind_ipv6_only_or_bool(const char *n) {
+        int r;
+
+        r = parse_boolean(n);
+        if (r > 0)
+                return SOCKET_ADDRESS_IPV6_ONLY;
+        if (r == 0)
+                return SOCKET_ADDRESS_BOTH;
+
+        return socket_address_bind_ipv6_only_from_string(n);
+}
+
 bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b) {
         assert(a);
         assert(b);
@@ -943,56 +953,77 @@ int getpeercred(int fd, struct ucred *ucred) {
         if (n != sizeof(struct ucred))
                 return -EIO;
 
-        /* Check if the data is actually useful and not suppressed due
-         * to namespacing issues */
-        if (u.pid <= 0)
-                return -ENODATA;
-        if (u.uid == UID_INVALID)
-                return -ENODATA;
-        if (u.gid == GID_INVALID)
+        /* Check if the data is actually useful and not suppressed due to namespacing issues */
+        if (!pid_is_valid(u.pid))
                 return -ENODATA;
 
+        /* Note that we don't check UID/GID here, as namespace translation works differently there: instead of
+         * receiving in "invalid" user/group we get the overflow UID/GID. */
+
         *ucred = u;
         return 0;
 }
 
 int getpeersec(int fd, char **ret) {
+        _cleanup_free_ char *s = NULL;
         socklen_t n = 64;
-        char *s;
-        int r;
 
         assert(fd >= 0);
         assert(ret);
 
-        s = new0(char, n);
-        if (!s)
-                return -ENOMEM;
+        for (;;) {
+                s = new0(char, n+1);
+                if (!s)
+                        return -ENOMEM;
 
-        r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n);
-        if (r < 0) {
-                free(s);
+                if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0)
+                        break;
 
                 if (errno != ERANGE)
                         return -errno;
 
-                s = new0(char, n);
-                if (!s)
+                s = mfree(s);
+        }
+
+        if (isempty(s))
+                return -EOPNOTSUPP;
+
+        *ret = TAKE_PTR(s);
+
+        return 0;
+}
+
+int getpeergroups(int fd, gid_t **ret) {
+        socklen_t n = sizeof(gid_t) * 64;
+        _cleanup_free_ gid_t *d = NULL;
+
+        assert(fd >= 0);
+        assert(ret);
+
+        for (;;) {
+                d = malloc(n);
+                if (!d)
                         return -ENOMEM;
 
-                r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n);
-                if (r < 0) {
-                        free(s);
+                if (getsockopt(fd, SOL_SOCKET, SO_PEERGROUPS, d, &n) >= 0)
+                        break;
+
+                if (errno != ERANGE)
                         return -errno;
-                }
-        }
 
-        if (isempty(s)) {
-                free(s);
-                return -EOPNOTSUPP;
+                d = mfree(d);
         }
 
-        *ret = s;
-        return 0;
+        assert_se(n % sizeof(gid_t) == 0);
+        n /= sizeof(gid_t);
+
+        if ((socklen_t) (int) n != n)
+                return -E2BIG;
+
+        *ret = d;
+        d = NULL;
+
+        return (int) n;
 }
 
 int send_one_fd_sa(
diff --git a/src/systemd/src/basic/socket-util.h b/src/systemd/src/basic/socket-util.h
index d7e2d85f..ac957a76 100644
--- a/src/systemd/src/basic/socket-util.h
+++ b/src/systemd/src/basic/socket-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -35,18 +36,28 @@
 #include "util.h"
 
 union sockaddr_union {
+        /* The minimal, abstract version */
         struct sockaddr sa;
+
+        /* The libc provided version that allocates "enough room" for every protocol */
+        struct sockaddr_storage storage;
+
+        /* Protoctol-specific implementations */
         struct sockaddr_in in;
         struct sockaddr_in6 in6;
         struct sockaddr_un un;
         struct sockaddr_nl nl;
-        struct sockaddr_storage storage;
         struct sockaddr_ll ll;
 #if 0 /* NM_IGNORED */
         struct sockaddr_vm vm;
 #endif /* NM_IGNORED */
+
         /* Ensure there is enough space to store Infiniband addresses */
         uint8_t ll_buffer[offsetof(struct sockaddr_ll, sll_addr) + CONST_MAX(ETH_ALEN, INFINIBAND_ALEN)];
+
+        /* Ensure there is enough space after the AF_UNIX sun_path for one more NUL byte, just to be sure that the path
+         * component is always followed by at least one NUL byte. */
+        uint8_t un_buffer[sizeof(struct sockaddr_un) + 1];
 };
 
 typedef struct SocketAddress {
@@ -73,6 +84,9 @@ typedef enum SocketAddressBindIPv6Only {
 
 #define socket_address_family(a) ((a)->sockaddr.sa.sa_family)
 
+const char* socket_address_type_to_string(int t) _const_;
+int socket_address_type_from_string(const char *s) _pure_;
+
 int socket_address_parse(SocketAddress *a, const char *s);
 int socket_address_parse_and_warn(SocketAddress *a, const char *s);
 int socket_address_parse_netlink(SocketAddress *a, const char *s);
@@ -114,10 +128,10 @@ int getpeername_pretty(int fd, bool include_port, char **ret);
 int getsockname_pretty(int fd, char **ret);
 
 int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret);
-int getnameinfo_pretty(int fd, char **ret);
 
 const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_;
 SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_;
+SocketAddressBindIPv6Only parse_socket_address_bind_ipv6_only_or_bool(const char *s);
 
 int netlink_family_to_string_alloc(int b, char **s);
 int netlink_family_from_string(const char *s) _pure_;
@@ -135,6 +149,7 @@ bool address_label_valid(const char *p);
 
 int getpeercred(int fd, struct ucred *ucred);
 int getpeersec(int fd, char **ret);
+int getpeergroups(int fd, gid_t **ret);
 
 int send_one_fd_sa(int transport_fd,
                    int fd,
diff --git a/src/systemd/src/basic/sparse-endian.h b/src/systemd/src/basic/sparse-endian.h
index a3573b84..5e59de54 100644
--- a/src/systemd/src/basic/sparse-endian.h
+++ b/src/systemd/src/basic/sparse-endian.h
@@ -1,4 +1,6 @@
-/* Copyright (c) 2012 Josh Triplett <josh@joshtriplett.org>
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2012 Josh Triplett <josh@joshtriplett.org>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to
diff --git a/src/systemd/src/basic/stdio-util.h b/src/systemd/src/basic/stdio-util.h
index bd1144b4..d3fed365 100644
--- a/src/systemd/src/basic/stdio-util.h
+++ b/src/systemd/src/basic/stdio-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -26,9 +27,11 @@
 
 #include "macro.h"
 
-#define xsprintf(buf, fmt, ...) \
-        assert_message_se((size_t) snprintf(buf, ELEMENTSOF(buf), fmt, __VA_ARGS__) < ELEMENTSOF(buf), "xsprintf: " #buf "[] must be big enough")
+#define snprintf_ok(buf, len, fmt, ...) \
+        ((size_t) snprintf(buf, len, fmt, __VA_ARGS__) < (len))
 
+#define xsprintf(buf, fmt, ...) \
+        assert_message_se(snprintf_ok(buf, ELEMENTSOF(buf), fmt, __VA_ARGS__), "xsprintf: " #buf "[] must be big enough")
 
 #define VA_FORMAT_ADVANCE(format, ap)                                   \
 do {                                                                    \
diff --git a/src/systemd/src/basic/string-table.c b/src/systemd/src/basic/string-table.c
index df26d5fb..8ec3b266 100644
--- a/src/systemd/src/basic/string-table.c
+++ b/src/systemd/src/basic/string-table.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
diff --git a/src/systemd/src/basic/string-table.h b/src/systemd/src/basic/string-table.h
index 369610ef..e78a6dbd 100644
--- a/src/systemd/src/basic/string-table.h
+++ b/src/systemd/src/basic/string-table.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 
 #pragma once
 
@@ -92,13 +93,11 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
 
 #define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope)                    \
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope)          \
-        _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope)        \
-        struct __useless_struct_to_allow_trailing_semicolon__
+        _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope)
 
 #define _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,scope)   \
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope)          \
-        _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \
-        struct __useless_struct_to_allow_trailing_semicolon__
+        _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope)
 
 #define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,)
 #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static)
@@ -110,8 +109,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
 /* For string conversions where numbers are also acceptable */
 #define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max)         \
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,)  \
-        _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,) \
-        struct __useless_struct_to_allow_trailing_semicolon__
+        _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
 
 #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static)
diff --git a/src/systemd/src/basic/string-util.c b/src/systemd/src/basic/string-util.c
index 047eb162..a8e595db 100644
--- a/src/systemd/src/basic/string-util.c
+++ b/src/systemd/src/basic/string-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -23,6 +24,7 @@
 #include <stdarg.h>
 #include <stdint.h>
 #include <stdio.h>
+#include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -30,6 +32,7 @@
 #include "gunicode.h"
 #include "macro.h"
 #include "string-util.h"
+#include "terminal-util.h"
 #include "utf8.h"
 #include "util.h"
 
@@ -220,6 +223,7 @@ char *strappend(const char *s, const char *suffix) {
         return strnappend(s, suffix, strlen_ptr(suffix));
 }
 
+#if 0 /* NM_IGNORED */
 char *strjoin_real(const char *x, ...) {
         va_list ap;
         size_t l;
@@ -280,6 +284,9 @@ char *strjoin_real(const char *x, ...) {
 char *strstrip(char *s) {
         char *e;
 
+        if (!s)
+                return NULL;
+
         /* Drops trailing whitespace. Modifies the string in
          * place. Returns pointer to first non-space character */
 
@@ -297,7 +304,13 @@ char *strstrip(char *s) {
 char *delete_chars(char *s, const char *bad) {
         char *f, *t;
 
-        /* Drops all whitespace, regardless where in the string */
+        /* Drops all specified bad characters, regardless where in the string */
+
+        if (!s)
+                return NULL;
+
+        if (!bad)
+                bad = WHITESPACE;
 
         for (f = s, t = s; *f; f++) {
                 if (strchr(bad, *f))
@@ -311,6 +324,27 @@ char *delete_chars(char *s, const char *bad) {
         return s;
 }
 
+char *delete_trailing_chars(char *s, const char *bad) {
+        char *p, *c = s;
+
+        /* Drops all specified bad characters, at the end of the string */
+
+        if (!s)
+                return NULL;
+
+        if (!bad)
+                bad = WHITESPACE;
+
+        for (p = s; *p; p++)
+                if (!strchr(bad, *p))
+                        c = p + 1;
+
+        *c = 0;
+
+        return s;
+}
+#endif /* NM_IGNORED */
+
 char *truncate_nl(char *s) {
         assert(s);
 
@@ -475,6 +509,10 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne
 
         assert(s);
         assert(percent <= 100);
+
+        if (new_length == (size_t) -1)
+                return strndup(s, old_length);
+
         assert(new_length >= 3);
 
         /* if no multibyte characters use ascii_ellipsize_mem for speed */
@@ -542,6 +580,10 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne
 }
 
 char *ellipsize(const char *s, size_t length, unsigned percent) {
+
+        if (length == (size_t) -1)
+                return strdup(s);
+
         return ellipsize_mem(s, strlen(s), length, percent);
 }
 #endif /* NM_IGNORED */
@@ -569,26 +611,26 @@ char* strshorten(char *s, size_t l) {
 }
 
 char *strreplace(const char *text, const char *old_string, const char *new_string) {
+        size_t l, old_len, new_len, allocated = 0;
+        char *t, *ret = NULL;
         const char *f;
-        char *t, *r;
-        size_t l, old_len, new_len;
 
-        assert(text);
         assert(old_string);
         assert(new_string);
 
+        if (!text)
+                return NULL;
+
         old_len = strlen(old_string);
         new_len = strlen(new_string);
 
         l = strlen(text);
-        r = new(char, l+1);
-        if (!r)
+        if (!GREEDY_REALLOC(ret, allocated, l+1))
                 return NULL;
 
         f = text;
-        t = r;
+        t = ret;
         while (*f) {
-                char *a;
                 size_t d, nl;
 
                 if (!startswith(f, old_string)) {
@@ -596,28 +638,34 @@ char *strreplace(const char *text, const char *old_string, const char *new_strin
                         continue;
                 }
 
-                d = t - r;
+                d = t - ret;
                 nl = l - old_len + new_len;
-                a = realloc(r, nl + 1);
-                if (!a)
-                        goto oom;
+
+                if (!GREEDY_REALLOC(ret, allocated, nl + 1))
+                        return mfree(ret);
 
                 l = nl;
-                r = a;
-                t = r + d;
+                t = ret + d;
 
                 t = stpcpy(t, new_string);
                 f += old_len;
         }
 
         *t = 0;
-        return r;
+        return ret;
+}
 
-oom:
-        return mfree(r);
+static void advance_offsets(ssize_t diff, size_t offsets[2], size_t shift[2], size_t size) {
+        if (!offsets)
+                return;
+
+        if ((size_t) diff < offsets[0])
+                shift[0] += size;
+        if ((size_t) diff < offsets[1])
+                shift[1] += size;
 }
 
-char *strip_tab_ansi(char **ibuf, size_t *_isz) {
+char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
         const char *i, *begin = NULL;
         enum {
                 STATE_OTHER,
@@ -625,7 +673,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
                 STATE_BRACKET
         } state = STATE_OTHER;
         char *obuf = NULL;
-        size_t osz = 0, isz;
+        size_t osz = 0, isz, shift[2] = {};
         FILE *f;
 
         assert(ibuf);
@@ -639,10 +687,10 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
         if (!f)
                 return NULL;
 
-        /* Note we use the _unlocked() stdio variants on f for performance
-         * reasons.  It's safe to do so since we created f here and it
-         * doesn't leave our scope.
-         */
+        /* Note we turn off internal locking on f for performance reasons.  It's safe to do so since we created f here
+         * and it doesn't leave our scope. */
+
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
 
         for (i = *ibuf; i < *ibuf + isz + 1; i++) {
 
@@ -653,22 +701,26 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
                                 break;
                         else if (*i == '\x1B')
                                 state = STATE_ESCAPE;
-                        else if (*i == '\t')
-                                fputs_unlocked("        ", f);
-                        else
-                                fputc_unlocked(*i, f);
+                        else if (*i == '\t') {
+                                fputs("        ", f);
+                                advance_offsets(i - *ibuf, highlight, shift, 7);
+                        } else
+                                fputc(*i, f);
+
                         break;
 
                 case STATE_ESCAPE:
                         if (i >= *ibuf + isz) { /* EOT */
-                                fputc_unlocked('\x1B', f);
+                                fputc('\x1B', f);
+                                advance_offsets(i - *ibuf, highlight, shift, 1);
                                 break;
                         } else if (*i == '[') {
                                 state = STATE_BRACKET;
                                 begin = i + 1;
                         } else {
-                                fputc_unlocked('\x1B', f);
-                                fputc_unlocked(*i, f);
+                                fputc('\x1B', f);
+                                fputc(*i, f);
+                                advance_offsets(i - *ibuf, highlight, shift, 1);
                                 state = STATE_OTHER;
                         }
 
@@ -678,8 +730,9 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
 
                         if (i >= *ibuf + isz || /* EOT */
                             (!(*i >= '0' && *i <= '9') && !IN_SET(*i, ';', 'm'))) {
-                                fputc_unlocked('\x1B', f);
-                                fputc_unlocked('[', f);
+                                fputc('\x1B', f);
+                                fputc('[', f);
+                                advance_offsets(i - *ibuf, highlight, shift, 2);
                                 state = STATE_OTHER;
                                 i = begin-1;
                         } else if (*i == 'm')
@@ -701,19 +754,29 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
         if (_isz)
                 *_isz = osz;
 
+        if (highlight) {
+                highlight[0] += shift[0];
+                highlight[1] += shift[1];
+        }
+
         return obuf;
 }
 
-char *strextend(char **x, ...) {
-        va_list ap;
-        size_t f, l;
+#if 0 /* NM_IGNORED */
+char *strextend_with_separator(char **x, const char *separator, ...) {
+        bool need_separator;
+        size_t f, l, l_separator;
         char *r, *p;
+        va_list ap;
 
         assert(x);
 
         l = f = strlen_ptr(*x);
 
-        va_start(ap, x);
+        need_separator = !isempty(*x);
+        l_separator = strlen_ptr(separator);
+
+        va_start(ap, separator);
         for (;;) {
                 const char *t;
                 size_t n;
@@ -723,22 +786,29 @@ char *strextend(char **x, ...) {
                         break;
 
                 n = strlen(t);
+
+                if (need_separator)
+                        n += l_separator;
+
                 if (n > ((size_t) -1) - l) {
                         va_end(ap);
                         return NULL;
                 }
 
                 l += n;
+                need_separator = true;
         }
         va_end(ap);
 
+        need_separator = !isempty(*x);
+
         r = realloc(*x, l+1);
         if (!r)
                 return NULL;
 
         p = r + f;
 
-        va_start(ap, x);
+        va_start(ap, separator);
         for (;;) {
                 const char *t;
 
@@ -746,15 +816,23 @@ char *strextend(char **x, ...) {
                 if (!t)
                         break;
 
+                if (need_separator && separator)
+                        p = stpcpy(p, separator);
+
                 p = stpcpy(p, t);
+
+                need_separator = true;
         }
         va_end(ap);
 
+        assert(p == r + l);
+
         *p = 0;
         *x = r;
 
         return r + l;
 }
+#endif /* NM_IGNORED */
 
 char *strrep(const char *s, unsigned n) {
         size_t l;
diff --git a/src/systemd/src/basic/string-util.h b/src/systemd/src/basic/string-util.h
index 4c94b182..08eda4fc 100644
--- a/src/systemd/src/basic/string-util.h
+++ b/src/systemd/src/basic/string-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -51,15 +52,15 @@ static inline bool streq_ptr(const char *a, const char *b) {
 }
 
 static inline const char* strempty(const char *s) {
-        return s ? s : "";
+        return s ?: "";
 }
 
 static inline const char* strnull(const char *s) {
-        return s ? s : "(null)";
+        return s ?: "(null)";
 }
 
 static inline const char *strna(const char *s) {
-        return s ? s : "n/a";
+        return s ?: "n/a";
 }
 
 static inline bool isempty(const char *p) {
@@ -133,8 +134,20 @@ char *strjoin_real(const char *x, ...) _sentinel_;
 
 char *strstrip(char *s);
 char *delete_chars(char *s, const char *bad);
+char *delete_trailing_chars(char *s, const char *bad);
 char *truncate_nl(char *s);
 
+static inline char *skip_leading_chars(const char *s, const char *bad) {
+
+        if (!s)
+                return NULL;
+
+        if (!bad)
+                bad = WHITESPACE;
+
+        return (char*) s + strspn(s, bad);
+}
+
 char ascii_tolower(char x);
 char *ascii_strlower(char *s);
 char *ascii_strlower_n(char *s, size_t n);
@@ -164,9 +177,11 @@ char* strshorten(char *s, size_t l);
 
 char *strreplace(const char *text, const char *old_string, const char *new_string);
 
-char *strip_tab_ansi(char **p, size_t *l);
+char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]);
+
+char *strextend_with_separator(char **x, const char *separator, ...) _sentinel_;
 
-char *strextend(char **x, ...) _sentinel_;
+#define strextend(x, ...) strextend_with_separator(x, NULL, __VA_ARGS__)
 
 char *strrep(const char *s, unsigned n);
 
diff --git a/src/systemd/src/basic/strv.c b/src/systemd/src/basic/strv.c
index 08bcff6e..dee5bbd7 100644
--- a/src/systemd/src/basic/strv.c
+++ b/src/systemd/src/basic/strv.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -215,7 +216,7 @@ int strv_extend_strv(char ***a, char **b, bool filter_duplicates) {
         p = strv_length(*a);
         q = strv_length(b);
 
-        t = realloc(*a, sizeof(char*) * (p + q + 1));
+        t = reallocarray(*a, p + q + 1, sizeof(char *));
         if (!t)
                 return -ENOMEM;
 
@@ -343,8 +344,7 @@ int strv_split_extract(char ***t, const char *s, const char *separators, Extract
                 if (!GREEDY_REALLOC(l, allocated, n + 2))
                         return -ENOMEM;
 
-                l[n++] = word;
-                word = NULL;
+                l[n++] = TAKE_PTR(word);
 
                 l[n] = NULL;
         }
@@ -355,8 +355,7 @@ int strv_split_extract(char ***t, const char *s, const char *separators, Extract
                         return -ENOMEM;
         }
 
-        *t = l;
-        l = NULL;
+        *t = TAKE_PTR(l);
 
         return (int) n;
 }
@@ -396,42 +395,6 @@ char *strv_join(char **l, const char *separator) {
         return r;
 }
 
-char *strv_join_quoted(char **l) {
-        char *buf = NULL;
-        char **s;
-        size_t allocated = 0, len = 0;
-
-        STRV_FOREACH(s, l) {
-                /* assuming here that escaped string cannot be more
-                 * than twice as long, and reserving space for the
-                 * separator and quotes.
-                 */
-                _cleanup_free_ char *esc = NULL;
-                size_t needed;
-
-                if (!GREEDY_REALLOC(buf, allocated,
-                                    len + strlen(*s) * 2 + 3))
-                        goto oom;
-
-                esc = cescape(*s);
-                if (!esc)
-                        goto oom;
-
-                needed = snprintf(buf + len, allocated - len, "%s\"%s\"",
-                                  len > 0 ? " " : "", esc);
-                assert(needed < allocated - len);
-                len += needed;
-        }
-
-        if (!buf)
-                buf = malloc0(1);
-
-        return buf;
-
- oom:
-        return mfree(buf);
-}
-
 int strv_push(char ***l, char *value) {
         char **c;
         unsigned n, m;
@@ -446,7 +409,7 @@ int strv_push(char ***l, char *value) {
         if (m < n)
                 return -ENOMEM;
 
-        c = realloc_multiply(*l, sizeof(char*), m);
+        c = reallocarray(*l, m, sizeof(char*));
         if (!c)
                 return -ENOMEM;
 
@@ -471,7 +434,7 @@ int strv_push_pair(char ***l, char *a, char *b) {
         if (m < n)
                 return -ENOMEM;
 
-        c = realloc_multiply(*l, sizeof(char*), m);
+        c = reallocarray(*l, m, sizeof(char*));
         if (!c)
                 return -ENOMEM;
 
@@ -485,7 +448,7 @@ int strv_push_pair(char ***l, char *a, char *b) {
         return 0;
 }
 
-int strv_push_prepend(char ***l, char *value) {
+int strv_insert(char ***l, unsigned position, char *value) {
         char **c;
         unsigned n, m, i;
 
@@ -493,6 +456,7 @@ int strv_push_prepend(char ***l, char *value) {
                 return 0;
 
         n = strv_length(*l);
+        position = MIN(position, n);
 
         /* increase and check for overflow */
         m = n + 2;
@@ -503,10 +467,12 @@ int strv_push_prepend(char ***l, char *value) {
         if (!c)
                 return -ENOMEM;
 
-        for (i = 0; i < n; i++)
+        for (i = 0; i < position; i++)
+                c[i] = (*l)[i];
+        c[position] = value;
+        for (i = position; i < n; i++)
                 c[i+1] = (*l)[i];
 
-        c[0] = value;
         c[n+1] = NULL;
 
         free(*l);
@@ -582,7 +548,7 @@ int strv_extend_front(char ***l, const char *value) {
         if (!v)
                 return -ENOMEM;
 
-        c = realloc_multiply(*l, sizeof(char*), m);
+        c = reallocarray(*l, m, sizeof(char*));
         if (!c) {
                 free(v);
                 return -ENOMEM;
@@ -897,7 +863,7 @@ int strv_extend_n(char ***l, const char *value, size_t n) {
 
         k = strv_length(*l);
 
-        nl = realloc(*l, sizeof(char*) * (k + n + 1));
+        nl = reallocarray(*l, k + n + 1, sizeof(char *));
         if (!nl)
                 return -ENOMEM;
 
diff --git a/src/systemd/src/basic/strv.h b/src/systemd/src/basic/strv.h
index 385ad177..f169ac5d 100644
--- a/src/systemd/src/basic/strv.h
+++ b/src/systemd/src/basic/strv.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -53,7 +54,12 @@ int strv_extendf(char ***l, const char *format, ...) _printf_(2,0);
 int strv_extend_front(char ***l, const char *value);
 int strv_push(char ***l, char *value);
 int strv_push_pair(char ***l, char *a, char *b);
-int strv_push_prepend(char ***l, char *value);
+int strv_insert(char ***l, unsigned position, char *value);
+
+static inline int strv_push_prepend(char ***l, char *value) {
+        return strv_insert(l, 0, value);
+}
+
 int strv_consume(char ***l, char *value);
 int strv_consume_pair(char ***l, char *a, char *b);
 int strv_consume_prepend(char ***l, char *value);
@@ -85,7 +91,6 @@ char **strv_split_newlines(const char *s);
 int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags);
 
 char *strv_join(char **l, const char *separator);
-char *strv_join_quoted(char **l);
 
 char **strv_parse_nulstr(const char *s, size_t l);
 char **strv_split_nulstr(const char *s);
@@ -174,9 +179,18 @@ static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, i
 }
 
 char ***strv_free_free(char ***l);
+DEFINE_TRIVIAL_CLEANUP_FUNC(char***, strv_free_free);
 
 char **strv_skip(char **l, size_t n);
 
 int strv_extend_n(char ***l, const char *value, size_t n);
 
 int fputstrv(FILE *f, char **l, const char *separator, bool *space);
+
+#define strv_free_and_replace(a, b)             \
+        ({                                      \
+                strv_free(a);                   \
+                (a) = (b);                      \
+                (b) = NULL;                     \
+                0;                              \
+        })
diff --git a/src/systemd/src/basic/time-util.c b/src/systemd/src/basic/time-util.c
index 7f1c3f7c..95fbbd91 100644
--- a/src/systemd/src/basic/time-util.c
+++ b/src/systemd/src/basic/time-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -39,6 +40,7 @@
 #include "macro.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "process-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "time-util.h"
@@ -891,28 +893,24 @@ int parse_timestamp(const char *t, usec_t *usec) {
         char *last_space, *tz = NULL;
         ParseTimestampResult *shared, tmp;
         int r;
-        pid_t pid;
 
         last_space = strrchr(t, ' ');
         if (last_space != NULL && timezone_is_valid(last_space + 1))
                 tz = last_space + 1;
 
-        if (tz == NULL || endswith_no_case(t, " UTC"))
+        if (!tz || endswith_no_case(t, " UTC"))
                 return parse_timestamp_impl(t, usec, false);
 
         shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
         if (shared == MAP_FAILED)
                 return negative_errno();
 
-        pid = fork();
-
-        if (pid == -1) {
-                int fork_errno = errno;
+        r = safe_fork("(sd-timestamp)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL);
+        if (r < 0) {
                 (void) munmap(shared, sizeof *shared);
-                return -fork_errno;
+                return r;
         }
-
-        if (pid == 0) {
+        if (r == 0) {
                 bool with_tz = true;
 
                 if (setenv("TZ", tz, 1) != 0) {
@@ -935,12 +933,6 @@ int parse_timestamp(const char *t, usec_t *usec) {
                 _exit(EXIT_SUCCESS);
         }
 
-        r = wait_for_terminate(pid, NULL);
-        if (r < 0) {
-                (void) munmap(shared, sizeof *shared);
-                return r;
-        }
-
         tmp = *shared;
         if (munmap(shared, sizeof *shared) != 0)
                 return negative_errno();
@@ -1387,8 +1379,7 @@ bool clock_supported(clockid_t clock) {
                 if (!clock_boottime_supported())
                         return false;
 
-                /* fall through */
-
+                _fallthrough_;
         default:
                 /* For everything else, check properly */
                 return clock_gettime(clock, &ts) >= 0;
@@ -1463,4 +1454,10 @@ usec_t usec_shift_clock(usec_t x, clockid_t from, clockid_t to) {
                 /* x lies in the past */
                 return usec_sub_unsigned(b, usec_sub_unsigned(a, x));
 }
+
+bool in_utc_timezone(void) {
+        tzset();
+
+        return timezone == 0 && daylight == 0;
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/time-util.h b/src/systemd/src/basic/time-util.h
index 73f7e400..a8c5a837 100644
--- a/src/systemd/src/basic/time-util.h
+++ b/src/systemd/src/basic/time-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -99,12 +100,12 @@ triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u)
 #define TRIPLE_TIMESTAMP_HAS_CLOCK(clock)                               \
         IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM)
 
-static inline bool dual_timestamp_is_set(dual_timestamp *ts) {
+static inline bool dual_timestamp_is_set(const dual_timestamp *ts) {
         return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
                 (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY));
 }
 
-static inline bool triple_timestamp_is_set(triple_timestamp *ts) {
+static inline bool triple_timestamp_is_set(const triple_timestamp *ts) {
         return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
                 (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY) ||
                 (ts->boottime > 0 && ts->boottime != USEC_INFINITY));
@@ -155,6 +156,8 @@ struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc);
 
 unsigned long usec_to_jiffies(usec_t usec);
 
+bool in_utc_timezone(void);
+
 static inline usec_t usec_add(usec_t a, usec_t b) {
         usec_t c;
 
diff --git a/src/systemd/src/basic/umask-util.h b/src/systemd/src/basic/umask-util.h
index 359d87d2..638b37d7 100644
--- a/src/systemd/src/basic/umask-util.h
+++ b/src/systemd/src/basic/umask-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/basic/utf8.c b/src/systemd/src/basic/utf8.c
index ff281e43..c88eef38 100644
--- a/src/systemd/src/basic/utf8.c
+++ b/src/systemd/src/basic/utf8.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -409,3 +410,22 @@ int utf8_encoded_valid_unichar(const char *str) {
 
         return len;
 }
+
+size_t utf8_n_codepoints(const char *str) {
+        size_t n = 0;
+
+        /* Returns the number of UTF-8 codepoints in this string, or (size_t) -1 if the string is not valid UTF-8. */
+
+        while (*str != 0) {
+                int k;
+
+                k = utf8_encoded_valid_unichar(str);
+                if (k < 0)
+                        return (size_t) -1;
+
+                str += k;
+                n++;
+        }
+
+        return n;
+}
diff --git a/src/systemd/src/basic/utf8.h b/src/systemd/src/basic/utf8.h
index 322dac20..c5243c24 100644
--- a/src/systemd/src/basic/utf8.h
+++ b/src/systemd/src/basic/utf8.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -60,3 +61,5 @@ static inline bool utf16_is_trailing_surrogate(char16_t c) {
 static inline char32_t utf16_surrogate_pair_to_unichar(char16_t lead, char16_t trail) {
                 return ((lead - 0xd800) << 10) + (trail - 0xdc00) + 0x10000;
 }
+
+size_t utf8_n_codepoints(const char *str);
diff --git a/src/systemd/src/basic/util.c b/src/systemd/src/basic/util.c
index c8a22d68..d8c9e82d 100644
--- a/src/systemd/src/basic/util.c
+++ b/src/systemd/src/basic/util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -40,6 +41,7 @@
 #include "build.h"
 #include "cgroup-util.h"
 #include "def.h"
+#include "device-nodes.h"
 #include "dirent-util.h"
 #include "fd-util.h"
 #include "fileio.h"
@@ -52,6 +54,7 @@
 #include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
+#include "procfs-util.h"
 #include "set.h"
 #include "signal-util.h"
 #include "stat-util.h"
@@ -61,6 +64,7 @@
 #include "umask-util.h"
 #include "user-util.h"
 #include "util.h"
+#include "virt.h"
 
 #if 0 /* NM_IGNORED */
 int saved_argc = 0;
@@ -108,7 +112,7 @@ int socket_from_display(const char *display, char **path) {
 
         k = strspn(display+1, "0123456789");
 
-        f = new(char, strlen("/tmp/.X11-unix/X") + k + 1);
+        f = new(char, STRLEN("/tmp/.X11-unix/X") + k + 1);
         if (!f)
                 return -ENOMEM;
 
@@ -121,66 +125,6 @@ int socket_from_display(const char *display, char **path) {
         return 0;
 }
 
-int block_get_whole_disk(dev_t d, dev_t *ret) {
-        char *p, *s;
-        int r;
-        unsigned n, m;
-
-        assert(ret);
-
-        /* If it has a queue this is good enough for us */
-        if (asprintf(&p, "/sys/dev/block/%u:%u/queue", major(d), minor(d)) < 0)
-                return -ENOMEM;
-
-        r = access(p, F_OK);
-        free(p);
-
-        if (r >= 0) {
-                *ret = d;
-                return 0;
-        }
-
-        /* If it is a partition find the originating device */
-        if (asprintf(&p, "/sys/dev/block/%u:%u/partition", major(d), minor(d)) < 0)
-                return -ENOMEM;
-
-        r = access(p, F_OK);
-        free(p);
-
-        if (r < 0)
-                return -ENOENT;
-
-        /* Get parent dev_t */
-        if (asprintf(&p, "/sys/dev/block/%u:%u/../dev", major(d), minor(d)) < 0)
-                return -ENOMEM;
-
-        r = read_one_line_file(p, &s);
-        free(p);
-
-        if (r < 0)
-                return r;
-
-        r = sscanf(s, "%u:%u", &m, &n);
-        free(s);
-
-        if (r != 2)
-                return -EINVAL;
-
-        /* Only return this if it is really good enough for us. */
-        if (asprintf(&p, "/sys/dev/block/%u:%u/queue", m, n) < 0)
-                return -ENOMEM;
-
-        r = access(p, F_OK);
-        free(p);
-
-        if (r >= 0) {
-                *ret = makedev(m, n);
-                return 0;
-        }
-
-        return -ENOENT;
-}
-
 bool kexec_loaded(void) {
        _cleanup_free_ char *s = NULL;
 
@@ -208,112 +152,6 @@ int prot_from_flags(int flags) {
         }
 }
 
-int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) {
-        bool stdout_is_tty, stderr_is_tty;
-        pid_t parent_pid, agent_pid;
-        sigset_t ss, saved_ss;
-        unsigned n, i;
-        va_list ap;
-        char **l;
-
-        assert(pid);
-        assert(path);
-
-        /* Spawns a temporary TTY agent, making sure it goes away when
-         * we go away */
-
-        parent_pid = getpid_cached();
-
-        /* First we temporarily block all signals, so that the new
-         * child has them blocked initially. This way, we can be sure
-         * that SIGTERMs are not lost we might send to the agent. */
-        assert_se(sigfillset(&ss) >= 0);
-        assert_se(sigprocmask(SIG_SETMASK, &ss, &saved_ss) >= 0);
-
-        agent_pid = fork();
-        if (agent_pid < 0) {
-                assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0);
-                return -errno;
-        }
-
-        if (agent_pid != 0) {
-                assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0);
-                *pid = agent_pid;
-                return 0;
-        }
-
-        /* In the child:
-         *
-         * Make sure the agent goes away when the parent dies */
-        if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
-                _exit(EXIT_FAILURE);
-
-        /* Make sure we actually can kill the agent, if we need to, in
-         * case somebody invoked us from a shell script that trapped
-         * SIGTERM or so... */
-        (void) reset_all_signal_handlers();
-        (void) reset_signal_mask();
-
-        /* Check whether our parent died before we were able
-         * to set the death signal and unblock the signals */
-        if (getppid() != parent_pid)
-                _exit(EXIT_SUCCESS);
-
-        /* Don't leak fds to the agent */
-        close_all_fds(except, n_except);
-
-        stdout_is_tty = isatty(STDOUT_FILENO);
-        stderr_is_tty = isatty(STDERR_FILENO);
-
-        if (!stdout_is_tty || !stderr_is_tty) {
-                int fd;
-
-                /* Detach from stdout/stderr. and reopen
-                 * /dev/tty for them. This is important to
-                 * ensure that when systemctl is started via
-                 * popen() or a similar call that expects to
-                 * read EOF we actually do generate EOF and
-                 * not delay this indefinitely by because we
-                 * keep an unused copy of stdin around. */
-                fd = open("/dev/tty", O_WRONLY);
-                if (fd < 0) {
-                        log_error_errno(errno, "Failed to open /dev/tty: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                if (!stdout_is_tty && dup2(fd, STDOUT_FILENO) < 0) {
-                        log_error_errno(errno, "Failed to dup2 /dev/tty: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                if (!stderr_is_tty && dup2(fd, STDERR_FILENO) < 0) {
-                        log_error_errno(errno, "Failed to dup2 /dev/tty: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                if (fd > STDERR_FILENO)
-                        close(fd);
-        }
-
-        /* Count arguments */
-        va_start(ap, path);
-        for (n = 0; va_arg(ap, char*); n++)
-                ;
-        va_end(ap);
-
-        /* Allocate strv */
-        l = alloca(sizeof(char *) * (n + 1));
-
-        /* Fill in arguments */
-        va_start(ap, path);
-        for (i = 0; i <= n; i++)
-                l[i] = va_arg(ap, char*);
-        va_end(ap);
-
-        execv(path, l);
-        _exit(EXIT_FAILURE);
-}
-
 bool in_initrd(void) {
         struct statfs s;
 
@@ -348,11 +186,13 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
         const void *p;
         int comparison;
 
+        assert(!size_multiply_overflow(nmemb, size));
+
         l = 0;
         u = nmemb;
         while (l < u) {
                 idx = (l + u) / 2;
-                p = (const char *) base + idx * size;
+                p = (const uint8_t*) base + idx * size;
                 comparison = compar(key, p, arg);
                 if (comparison < 0)
                         u = idx;
@@ -641,31 +481,22 @@ uint64_t physical_memory_scale(uint64_t v, uint64_t max) {
 
 uint64_t system_tasks_max(void) {
 
-#if SIZEOF_PID_T == 4
-#define TASKS_MAX ((uint64_t) (INT32_MAX-1))
-#elif SIZEOF_PID_T == 2
-#define TASKS_MAX ((uint64_t) (INT16_MAX-1))
-#else
-#error "Unknown pid_t size"
-#endif
-
-        _cleanup_free_ char *value = NULL, *root = NULL;
         uint64_t a = TASKS_MAX, b = TASKS_MAX;
+        _cleanup_free_ char *root = NULL;
 
         /* Determine the maximum number of tasks that may run on this system. We check three sources to determine this
          * limit:
          *
-         * a) the maximum value for the pid_t type
+         * a) the maximum tasks value the kernel allows on this architecture
          * b) the cgroups pids_max attribute for the system
-         * c) the kernel's configure maximum PID value
+         * c) the kernel's configured maximum PID value
          *
          * And then pick the smallest of the three */
 
-        if (read_one_line_file("/proc/sys/kernel/pid_max", &value) >= 0)
-                (void) safe_atou64(value, &a);
+        (void) procfs_tasks_get_limit(&a);
 
         if (cg_get_root_path(&root) >= 0) {
-                value = mfree(value);
+                _cleanup_free_ char *value = NULL;
 
                 if (cg_get_attribute("pids", root, "pids.max", &value) >= 0)
                         (void) safe_atou64(value, &b);
@@ -694,162 +525,83 @@ uint64_t system_tasks_max_scale(uint64_t v, uint64_t max) {
         return m / max;
 }
 
-int update_reboot_parameter_and_warn(const char *param) {
-        int r;
-
-        if (isempty(param)) {
-                if (unlink("/run/systemd/reboot-param") < 0) {
-                        if (errno == ENOENT)
-                                return 0;
-
-                        return log_warning_errno(errno, "Failed to unlink reboot parameter file: %m");
-                }
-
-                return 0;
-        }
-
-        RUN_WITH_UMASK(0022) {
-                r = write_string_file("/run/systemd/reboot-param", param, WRITE_STRING_FILE_CREATE);
-                if (r < 0)
-                        return log_warning_errno(r, "Failed to write reboot parameter file: %m");
-        }
-
-        return 0;
-}
-
 int version(void) {
         puts(PACKAGE_STRING "\n"
              SYSTEMD_FEATURES);
         return 0;
 }
 
-int get_block_device(const char *path, dev_t *dev) {
-        struct stat st;
-        struct statfs sfs;
-
-        assert(path);
-        assert(dev);
-
-        /* Get's the block device directly backing a file system. If
-         * the block device is encrypted, returns the device mapper
-         * block device. */
-
-        if (lstat(path, &st))
-                return -errno;
-
-        if (major(st.st_dev) != 0) {
-                *dev = st.st_dev;
-                return 1;
-        }
-
-        if (statfs(path, &sfs) < 0)
-                return -errno;
-
-        if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC))
-                return btrfs_get_block_device(path, dev);
-
-        return 0;
+/* This is a direct translation of str_verscmp from boot.c */
+static bool is_digit(int c) {
+        return c >= '0' && c <= '9';
 }
 
-int get_block_device_harder(const char *path, dev_t *dev) {
-        _cleanup_closedir_ DIR *d = NULL;
-        _cleanup_free_ char *p = NULL, *t = NULL;
-        struct dirent *de, *found = NULL;
-        const char *q;
-        unsigned maj, min;
-        dev_t dt;
-        int r;
-
-        assert(path);
-        assert(dev);
+static int c_order(int c) {
+        if (c == 0 || is_digit(c))
+                return 0;
 
-        /* Gets the backing block device for a file system, and
-         * handles LUKS encrypted file systems, looking for its
-         * immediate parent, if there is one. */
+        if ((c >= 'a') && (c <= 'z'))
+                return c;
 
-        r = get_block_device(path, &dt);
-        if (r <= 0)
-                return r;
+        return c + 0x10000;
+}
 
-        if (asprintf(&p, "/sys/dev/block/%u:%u/slaves", major(dt), minor(dt)) < 0)
-                return -ENOMEM;
+int str_verscmp(const char *s1, const char *s2) {
+        const char *os1, *os2;
 
-        d = opendir(p);
-        if (!d) {
-                if (errno == ENOENT)
-                        goto fallback;
+        assert(s1);
+        assert(s2);
 
-                return -errno;
-        }
+        os1 = s1;
+        os2 = s2;
 
-        FOREACH_DIRENT_ALL(de, d, return -errno) {
+        while (*s1 || *s2) {
+                int first;
 
-                if (dot_or_dot_dot(de->d_name))
-                        continue;
+                while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) {
+                        int order;
 
-                if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN))
-                        continue;
-
-                if (found) {
-                        _cleanup_free_ char *u = NULL, *v = NULL, *a = NULL, *b = NULL;
-
-                        /* We found a device backed by multiple other devices. We don't really support automatic
-                         * discovery on such setups, with the exception of dm-verity partitions. In this case there are
-                         * two backing devices: the data partition and the hash partition. We are fine with such
-                         * setups, however, only if both partitions are on the same physical device. Hence, let's
-                         * verify this. */
-
-                        u = strjoin(p, "/", de->d_name, "/../dev");
-                        if (!u)
-                                return -ENOMEM;
-
-                        v = strjoin(p, "/", found->d_name, "/../dev");
-                        if (!v)
-                                return -ENOMEM;
-
-                        r = read_one_line_file(u, &a);
-                        if (r < 0) {
-                                log_debug_errno(r, "Failed to read %s: %m", u);
-                                goto fallback;
-                        }
-
-                        r = read_one_line_file(v, &b);
-                        if (r < 0) {
-                                log_debug_errno(r, "Failed to read %s: %m", v);
-                                goto fallback;
-                        }
-
-                        /* Check if the parent device is the same. If not, then the two backing devices are on
-                         * different physical devices, and we don't support that. */
-                        if (!streq(a, b))
-                                goto fallback;
+                        order = c_order(*s1) - c_order(*s2);
+                        if (order != 0)
+                                return order;
+                        s1++;
+                        s2++;
                 }
 
-                found = de;
-        }
-
-        if (!found)
-                goto fallback;
+                while (*s1 == '0')
+                        s1++;
+                while (*s2 == '0')
+                        s2++;
+
+                first = 0;
+                while (is_digit(*s1) && is_digit(*s2)) {
+                        if (first == 0)
+                                first = *s1 - *s2;
+                        s1++;
+                        s2++;
+                }
 
-        q = strjoina(p, "/", found->d_name, "/dev");
+                if (is_digit(*s1))
+                        return 1;
+                if (is_digit(*s2))
+                        return -1;
 
-        r = read_one_line_file(q, &t);
-        if (r == -ENOENT)
-                goto fallback;
-        if (r < 0)
-                return r;
+                if (first != 0)
+                        return first;
+        }
 
-        if (sscanf(t, "%u:%u", &maj, &min) != 2)
-                return -EINVAL;
+        return strcmp(os1, os2);
+}
 
-        if (maj == 0)
-                goto fallback;
+/* Turn off core dumps but only if we're running outside of a container. */
+void disable_coredumps(void) {
+        int r;
 
-        *dev = makedev(maj, min);
-        return 1;
+        if (detect_container() > 0)
+                return;
 
-fallback:
-        *dev = dt;
-        return 1;
+        r = write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0);
+        if (r < 0)
+                log_debug_errno(r, "Failed to turn off coredumps, ignoring: %m");
 }
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/util.h b/src/systemd/src/basic/util.h
index b31dfd1c..19e9eae1 100644
--- a/src/systemd/src/basic/util.h
+++ b/src/systemd/src/basic/util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -70,8 +71,6 @@ bool plymouth_running(void);
 bool display_is_local(const char *display) _pure_;
 int socket_from_display(const char *display, char **path);
 
-int block_get_whole_disk(dev_t d, dev_t *ret);
-
 #define NULSTR_FOREACH(i, l)                                    \
         for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1)
 
@@ -85,8 +84,6 @@ bool kexec_loaded(void);
 
 int prot_from_flags(int flags) _const_;
 
-int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...);
-
 bool in_initrd(void);
 void in_initrd_force(bool value);
 
@@ -95,6 +92,19 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
                  void *arg);
 
 /**
+ * Normal bsearch requires base to be nonnull. Here were require
+ * that only if nmemb > 0.
+ */
+static inline void* bsearch_safe(const void *key, const void *base,
+                                 size_t nmemb, size_t size, comparison_fn_t compar) {
+        if (nmemb <= 0)
+                return NULL;
+
+        assert(base);
+        return bsearch(key, base, nmemb, size, compar);
+}
+
+/**
  * Normal qsort requires base to be nonnull. Here were require
  * that only if nmemb > 0.
  */
@@ -189,9 +199,8 @@ uint64_t physical_memory_scale(uint64_t v, uint64_t max);
 uint64_t system_tasks_max(void);
 uint64_t system_tasks_max_scale(uint64_t v, uint64_t max);
 
-int update_reboot_parameter_and_warn(const char *param);
-
 int version(void);
 
-int get_block_device(const char *path, dev_t *dev);
-int get_block_device_harder(const char *path, dev_t *dev);
+int str_verscmp(const char *s1, const char *s2);
+
+void disable_coredumps(void);
diff --git a/src/systemd/src/libsystemd-network/arp-util.c b/src/systemd/src/libsystemd-network/arp-util.c
index 69bd3e75..9b721ee6 100644
--- a/src/systemd/src/libsystemd-network/arp-util.c
+++ b/src/systemd/src/libsystemd-network/arp-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -25,6 +26,7 @@
 
 #include "arp-util.h"
 #include "fd-util.h"
+#include "unaligned.h"
 #include "util.h"
 
 int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) {
@@ -49,12 +51,12 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0),                        /* protocol == reply ? */
                 BPF_STMT(BPF_RET + BPF_K, 0),                                                  /* ignore */
                 /* Sender Hardware Address must be different from our own */
-                BPF_STMT(BPF_LD + BPF_IMM, htobe32(*((uint32_t *) eth_mac))),                  /* A <- 4 bytes of client's MAC */
+                BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be32(&eth_mac->ether_addr_octet[0])),/* A <- 4 bytes of client's MAC */
                 BPF_STMT(BPF_MISC + BPF_TAX, 0),                                               /* X <- A */
                 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)),       /* A <- 4 bytes of SHA */
                 BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                        /* A xor X */
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6),                                  /* A == 0 ? */
-                BPF_STMT(BPF_LD + BPF_IMM, htobe16(*((uint16_t *) (((char *) eth_mac) + 4)))), /* A <- remainder of client's MAC */
+                BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be16(&eth_mac->ether_addr_octet[4])),/* A <- remainder of client's MAC */
                 BPF_STMT(BPF_MISC + BPF_TAX, 0),                                               /* X <- A */
                 BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4),   /* A <- remainder of SHA */
                 BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                        /* A xor X */
@@ -103,10 +105,7 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_
         if (r < 0)
                 return -errno;
 
-        r = s;
-        s = -1;
-
-        return r;
+        return TAKE_FD(s);
 }
 
 static int arp_send_packet(int fd, int ifindex,
diff --git a/src/systemd/src/libsystemd-network/arp-util.h b/src/systemd/src/libsystemd-network/arp-util.h
index 3ef56b00..decfce3f 100644
--- a/src/systemd/src/libsystemd-network/arp-util.h
+++ b/src/systemd/src/libsystemd-network/arp-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.c b/src/systemd/src/libsystemd-network/dhcp-identifier.c
index c1fa8763..df4d8afb 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.c
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.h b/src/systemd/src/libsystemd-network/dhcp-identifier.h
index 1cc0f9fb..0ccee7a7 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.h
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/dhcp-internal.h b/src/systemd/src/libsystemd-network/dhcp-internal.h
index 3fdf02da..a5352695 100644
--- a/src/systemd/src/libsystemd-network/dhcp-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-internal.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
index 7847ce07..65c182f4 100644
--- a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -33,6 +34,8 @@ struct sd_dhcp_route {
         struct in_addr dst_addr;
         struct in_addr gw_addr;
         unsigned char dst_prefixlen;
+
+        uint8_t option;
 };
 
 struct sd_dhcp_raw_option {
diff --git a/src/systemd/src/libsystemd-network/dhcp-network.c b/src/systemd/src/libsystemd-network/dhcp-network.c
index f01b2cfe..d9067ffb 100644
--- a/src/systemd/src/libsystemd-network/dhcp-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp-network.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -33,6 +34,7 @@
 #include "dhcp-internal.h"
 #include "fd-util.h"
 #include "socket-util.h"
+#include "unaligned.h"
 
 static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
                             uint32_t xid, const uint8_t *mac_addr,
@@ -71,13 +73,13 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
                 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.xid)),    /* A <- client identifier */
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, xid, 1, 0),                        /* client identifier == xid ? */
                 BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
-                BPF_STMT(BPF_LD + BPF_IMM, htobe32(*((unsigned int *) eth_mac))),                     /* A <- 4 bytes of client's MAC */
+                BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be32(&eth_mac->ether_addr_octet[0])),        /* A <- 4 bytes of client's MAC */
                 BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                       /* X <- A */
                 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr)),                 /* A <- 4 bytes of MAC from dhcp.chaddr */
                 BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                /* A xor X */
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0),                                          /* A == 0 ? */
                 BPF_STMT(BPF_RET + BPF_K, 0),                                                          /* ignore */
-                BPF_STMT(BPF_LD + BPF_IMM, htobe16(*((unsigned short *) (((char *) eth_mac) + 4)))),   /* A <- remainder of client's MAC */
+                BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be16(&eth_mac->ether_addr_octet[4])),        /* A <- remainder of client's MAC */
                 BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                       /* X <- A */
                 BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr) + 4),             /* A <- remainder of MAC from dhcp.chaddr */
                 BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                /* A xor X */
@@ -123,10 +125,7 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
         if (r < 0)
                 return -errno;
 
-        r = s;
-        s = -1;
-
-        return r;
+        return TAKE_FD(s);
 }
 
 int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link,
@@ -211,10 +210,7 @@ int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port) {
         if (r < 0)
                 return -errno;
 
-        r = s;
-        s = -1;
-
-        return r;
+        return TAKE_FD(s);
 }
 
 int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link,
diff --git a/src/systemd/src/libsystemd-network/dhcp-option.c b/src/systemd/src/libsystemd-network/dhcp-option.c
index e003e088..08ed8a9f 100644
--- a/src/systemd/src/libsystemd-network/dhcp-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp-option.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -192,9 +193,7 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo
                                 if (!ascii_is_valid(string))
                                         return -EINVAL;
 
-                                free(*error_message);
-                                *error_message = string;
-                                string = NULL;
+                                free_and_replace(*error_message, string);
                         }
 
                         break;
@@ -256,10 +255,8 @@ int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t c
         if (message_type == 0)
                 return -ENOMSG;
 
-        if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) {
-                *_error_message = error_message;
-                error_message = NULL;
-        }
+        if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE))
+                *_error_message = TAKE_PTR(error_message);
 
         return message_type;
 }
diff --git a/src/systemd/src/libsystemd-network/dhcp-packet.c b/src/systemd/src/libsystemd-network/dhcp-packet.c
index 1bb1bfcf..6d8f01f2 100644
--- a/src/systemd/src/libsystemd-network/dhcp-packet.c
+++ b/src/systemd/src/libsystemd-network/dhcp-packet.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
diff --git a/src/systemd/src/libsystemd-network/dhcp-protocol.h b/src/systemd/src/libsystemd-network/dhcp-protocol.h
index 5cf7abbf..73a9f75e 100644
--- a/src/systemd/src/libsystemd-network/dhcp-protocol.h
+++ b/src/systemd/src/libsystemd-network/dhcp-protocol.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/dhcp6-internal.h b/src/systemd/src/libsystemd-network/dhcp6-internal.h
index 945c3b97..13844a86 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-internal.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -28,25 +29,65 @@
 #include "macro.h"
 #include "sparse-endian.h"
 
+/* Common option header */
+typedef struct DHCP6Option {
+        be16_t code;
+        be16_t len;
+        uint8_t data[];
+} _packed_ DHCP6Option;
+
+/* Address option */
+struct iaaddr {
+        struct in6_addr address;
+        be32_t lifetime_preferred;
+        be32_t lifetime_valid;
+} _packed_;
+
+/* Prefix Delegation Prefix option */
+struct iapdprefix {
+        be32_t lifetime_preferred;
+        be32_t lifetime_valid;
+        uint8_t prefixlen;
+        struct in6_addr address;
+} _packed_;
+
 typedef struct DHCP6Address DHCP6Address;
 
 struct DHCP6Address {
         LIST_FIELDS(DHCP6Address, addresses);
 
-        struct {
-                struct in6_addr address;
-                be32_t lifetime_preferred;
-                be32_t lifetime_valid;
-        } iaaddr _packed_;
+        union {
+                struct iaaddr iaaddr;
+                struct iapdprefix iapdprefix;
+        };
 };
 
+/* Non-temporary Address option */
+struct ia_na {
+        be32_t id;
+        be32_t lifetime_t1;
+        be32_t lifetime_t2;
+} _packed_;
+
+/* Prefix Delegation option */
+struct ia_pd {
+        be32_t id;
+        be32_t lifetime_t1;
+        be32_t lifetime_t2;
+} _packed_;
+
+/* Temporary Address option */
+struct ia_ta {
+        be32_t id;
+} _packed_;
+
 struct DHCP6IA {
         uint16_t type;
-        struct {
-                be32_t id;
-                be32_t lifetime_t1;
-                be32_t lifetime_t2;
-        } _packed_;
+        union {
+                struct ia_na ia_na;
+                struct ia_pd ia_pd;
+                struct ia_ta ia_ta;
+        };
         sd_event_source *timeout_t1;
         sd_event_source *timeout_t2;
 
@@ -61,10 +102,12 @@ typedef struct DHCP6IA DHCP6IA;
 int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
                         size_t optlen, const void *optval);
 int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia);
+int dhcp6_option_append_pd(uint8_t *buf, size_t len, DHCP6IA *pd);
+int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn);
 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
                        size_t *optlen, uint8_t **optvalue);
-int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
-                          DHCP6IA *ia);
+int dhcp6_option_parse_status(DHCP6Option *option);
+int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia);
 int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
                                 struct in6_addr **addrs, size_t count,
                                 size_t *allocated);
diff --git a/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
index 14e708ef..45e0e824 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -35,8 +36,10 @@ struct sd_dhcp6_lease {
         bool rapid_commit;
 
         DHCP6IA ia;
+        DHCP6IA pd;
 
         DHCP6Address *addr_iter;
+        DHCP6Address *prefix_iter;
 
         struct in6_addr *dns;
         size_t dns_count;
diff --git a/src/systemd/src/libsystemd-network/dhcp6-network.c b/src/systemd/src/libsystemd-network/dhcp6-network.c
index e4883019..85231084 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-network.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -68,9 +69,7 @@ int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) {
         if (r < 0)
                 return -errno;
 
-        r = s;
-        s = -1;
-        return r;
+        return TAKE_FD(s);
 }
 
 int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
diff --git a/src/systemd/src/libsystemd-network/dhcp6-option.c b/src/systemd/src/libsystemd-network/dhcp6-option.c
index 3a77e34d..d449a9c7 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-option.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -27,6 +28,7 @@
 
 #include "alloc-util.h"
 #include "dhcp6-internal.h"
+#include "dhcp6-lease-internal.h"
 #include "dhcp6-protocol.h"
 #include "dns-domain.h"
 #include "sparse-endian.h"
@@ -34,14 +36,27 @@
 #include "unaligned.h"
 #include "util.h"
 
-#define DHCP6_OPTION_IA_NA_LEN                  12
-#define DHCP6_OPTION_IA_TA_LEN                  4
+typedef struct DHCP6StatusOption {
+        struct DHCP6Option option;
+        be16_t status;
+        char msg[];
+} _packed_ DHCP6StatusOption;
 
-typedef struct DHCP6Option {
-        be16_t code;
-        be16_t len;
-        uint8_t data[];
-} _packed_ DHCP6Option;
+typedef struct DHCP6AddressOption {
+        struct DHCP6Option option;
+        struct iaaddr iaaddr;
+        uint8_t options[];
+} _packed_ DHCP6AddressOption;
+
+typedef struct DHCP6PDPrefixOption {
+        struct DHCP6Option option;
+        struct iapdprefix iapdprefix;
+        uint8_t options[];
+} _packed_ DHCP6PDPrefixOption;
+
+#define DHCP6_OPTION_IA_NA_LEN                  (sizeof(struct ia_na))
+#define DHCP6_OPTION_IA_PD_LEN                  (sizeof(struct ia_pd))
+#define DHCP6_OPTION_IA_TA_LEN                  (sizeof(struct ia_ta))
 
 static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
                              size_t optlen) {
@@ -84,7 +99,7 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
 int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
         uint16_t len;
         uint8_t *ia_hdr;
-        size_t ia_buflen, ia_addrlen = 0;
+        size_t iaid_offset, ia_buflen, ia_addrlen = 0;
         DHCP6Address *addr;
         int r;
 
@@ -93,10 +108,12 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
         switch (ia->type) {
         case SD_DHCP6_OPTION_IA_NA:
                 len = DHCP6_OPTION_IA_NA_LEN;
+                iaid_offset = offsetof(DHCP6IA, ia_na);
                 break;
 
         case SD_DHCP6_OPTION_IA_TA:
                 len = DHCP6_OPTION_IA_TA_LEN;
+                iaid_offset = offsetof(DHCP6IA, ia_ta);
                 break;
 
         default:
@@ -112,7 +129,7 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
         *buf += sizeof(DHCP6Option);
         *buflen -= sizeof(DHCP6Option);
 
-        memcpy(*buf, &ia->id, len);
+        memcpy(*buf, (char*) ia + iaid_offset, len);
 
         *buf += len;
         *buflen -= len;
@@ -138,6 +155,69 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
         return 0;
 }
 
+int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
+        uint8_t buffer[1 + DNS_WIRE_FOMAT_HOSTNAME_MAX];
+        int r;
+
+        assert_return(buf && *buf && buflen && fqdn, -EINVAL);
+
+        buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
+
+        /* Store domain name after flags field */
+        r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1,  false);
+        if (r <= 0)
+                return r;
+
+        /*
+         * According to RFC 4704, chapter 4.2 only add terminating zero-length
+         * label in case a FQDN is provided. Since dns_name_to_wire_format
+         * always adds terminating zero-length label remove if only a hostname
+         * is provided.
+         */
+        if (dns_name_is_single_label(fqdn))
+                r--;
+
+        r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer);
+
+        return r;
+}
+
+int dhcp6_option_append_pd(uint8_t *buf, size_t len, DHCP6IA *pd) {
+        DHCP6Option *option = (DHCP6Option *)buf;
+        size_t i = sizeof(*option) + sizeof(pd->ia_pd);
+        DHCP6Address *prefix;
+
+        assert_return(buf, -EINVAL);
+        assert_return(pd, -EINVAL);
+        assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL);
+
+        if (len < i)
+                return -ENOBUFS;
+
+        option->code = htobe16(SD_DHCP6_OPTION_IA_PD);
+
+        memcpy(&option->data, &pd->ia_pd, sizeof(pd->ia_pd));
+
+        LIST_FOREACH(addresses, prefix, pd->addresses) {
+                DHCP6PDPrefixOption *prefix_opt;
+
+                if (len < i + sizeof(*prefix_opt))
+                        return -ENOBUFS;
+
+                prefix_opt = (DHCP6PDPrefixOption *)&buf[i];
+                prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX);
+                prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix));
+
+                memcpy(&prefix_opt->iapdprefix, &prefix->iapdprefix,
+                       sizeof(struct iapdprefix));
+
+                i += sizeof(*prefix_opt);
+        }
+
+        option->len = htobe16(i - sizeof(*option));
+
+        return i;
+}
 
 static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
         DHCP6Option *option = (DHCP6Option*) *buf;
@@ -184,35 +264,147 @@ int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
         return 0;
 }
 
-int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
-                          DHCP6IA *ia) {
+int dhcp6_option_parse_status(DHCP6Option *option) {
+        DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
+
+        if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*statusopt))
+                return -ENOBUFS;
+
+        return be16toh(statusopt->status);
+}
+
+static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia,
+                                      uint32_t *lifetime_valid) {
+        DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option;
+        DHCP6Address *addr;
+        uint32_t lt_valid, lt_pref;
+        int r;
+
+        if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*addr_option))
+                return -ENOBUFS;
+
+        lt_valid = be32toh(addr_option->iaaddr.lifetime_valid);
+        lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred);
+
+        if (lt_valid == 0 || lt_pref > lt_valid) {
+                log_dhcp6_client(client, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
+                                 lt_pref, lt_valid);
+
+                return 0;
+        }
+
+        if (be16toh(option->len) + sizeof(DHCP6Option) > sizeof(*addr_option)) {
+                r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options);
+                if (r != 0)
+                        return r < 0 ? r: 0;
+        }
+
+        addr = new0(DHCP6Address, 1);
+        if (!addr)
+                return -ENOMEM;
+
+        LIST_INIT(addresses, addr);
+        memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr));
+
+        LIST_PREPEND(addresses, ia->addresses, addr);
+
+        *lifetime_valid = be32toh(addr->iaaddr.lifetime_valid);
+
+        return 0;
+}
+
+static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia,
+                                       uint32_t *lifetime_valid) {
+        DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option;
+        DHCP6Address *prefix;
+        uint32_t lt_valid, lt_pref;
         int r;
-        uint16_t opt, status;
-        size_t optlen;
+
+        if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*pdprefix_option))
+                return -ENOBUFS;
+
+        lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid);
+        lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred);
+
+        if (lt_valid == 0 || lt_pref > lt_valid) {
+                log_dhcp6_client(client, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
+                                 lt_pref, lt_valid);
+
+                return 0;
+        }
+
+        if (be16toh(option->len) + sizeof(DHCP6Option) > sizeof(*pdprefix_option)) {
+                r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options);
+                if (r != 0)
+                        return r < 0 ? r: 0;
+        }
+
+        prefix = new0(DHCP6Address, 1);
+        if (!prefix)
+                return -ENOMEM;
+
+        LIST_INIT(addresses, prefix);
+        memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix));
+
+        LIST_PREPEND(addresses, ia->addresses, prefix);
+
+        *lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid);
+
+        return 0;
+}
+
+int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
+        uint16_t iatype, optlen;
+        size_t i, len;
+        int r = 0, status;
+        uint16_t opt;
         size_t iaaddr_offset;
-        DHCP6Address *addr;
-        uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
+        uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
 
         assert_return(ia, -EINVAL);
         assert_return(!ia->addresses, -EINVAL);
 
+        iatype = be16toh(iaoption->code);
+        len = be16toh(iaoption->len);
+
         switch (iatype) {
         case SD_DHCP6_OPTION_IA_NA:
 
-                if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) +
-                    sizeof(addr->iaaddr)) {
+                if (len < DHCP6_OPTION_IA_NA_LEN) {
                         r = -ENOBUFS;
                         goto error;
                 }
 
                 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
-                memcpy(&ia->id, *buf, iaaddr_offset);
+                memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na));
+
+                lt_t1 = be32toh(ia->ia_na.lifetime_t1);
+                lt_t2 = be32toh(ia->ia_na.lifetime_t2);
+
+                if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
+                        log_dhcp6_client(client, "IA NA T1 %ds > T2 %ds",
+                                         lt_t1, lt_t2);
+                        r = -EINVAL;
+                        goto error;
+                }
+
+                break;
 
-                lt_t1 = be32toh(ia->lifetime_t1);
-                lt_t2 = be32toh(ia->lifetime_t2);
+        case SD_DHCP6_OPTION_IA_PD:
+
+                if (len < sizeof(ia->ia_pd)) {
+                        r = -ENOBUFS;
+                        goto error;
+                }
+
+                iaaddr_offset = sizeof(ia->ia_pd);
+                memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd));
+
+                lt_t1 = be32toh(ia->ia_pd.lifetime_t1);
+                lt_t2 = be32toh(ia->ia_pd.lifetime_t2);
 
                 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
-                        log_dhcp6_client(client, "IA T1 %ds > T2 %ds",
+                        log_dhcp6_client(client, "IA PD T1 %ds > T2 %ds",
                                          lt_t1, lt_t2);
                         r = -EINVAL;
                         goto error;
@@ -221,17 +413,13 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
                 break;
 
         case SD_DHCP6_OPTION_IA_TA:
-                if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) +
-                    sizeof(addr->iaaddr)) {
+                if (len < DHCP6_OPTION_IA_TA_LEN) {
                         r = -ENOBUFS;
                         goto error;
                 }
 
                 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
-                memcpy(&ia->id, *buf, iaaddr_offset);
-
-                ia->lifetime_t1 = 0;
-                ia->lifetime_t2 = 0;
+                memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta));
 
                 break;
 
@@ -241,48 +429,63 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
         }
 
         ia->type = iatype;
+        i = iaaddr_offset;
 
-        *buflen -= iaaddr_offset;
-        *buf += iaaddr_offset;
+        while (i < len) {
+                DHCP6Option *option = (DHCP6Option *)&iaoption->data[i];
+
+                if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len)) {
+                        r = -ENOBUFS;
+                        goto error;
+                }
 
-        while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
+                opt = be16toh(option->code);
+                optlen = be16toh(option->len);
 
                 switch (opt) {
                 case SD_DHCP6_OPTION_IAADDR:
 
-                        addr = new0(DHCP6Address, 1);
-                        if (!addr) {
-                                r = -ENOMEM;
+                        if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) {
+                                log_dhcp6_client(client, "IA Address option not in IA NA or TA option");
+                                r = -EINVAL;
                                 goto error;
                         }
 
-                        LIST_INIT(addresses, addr);
+                        r = dhcp6_option_parse_address(option, ia, &lt_valid);
+                        if (r < 0)
+                                goto error;
+
+                        if (lt_valid < lt_min)
+                                lt_min = lt_valid;
 
-                        memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr));
+                        break;
 
-                        lt_valid = be32toh(addr->iaaddr.lifetime_valid);
-                        lt_pref = be32toh(addr->iaaddr.lifetime_valid);
+                case SD_DHCP6_OPTION_IA_PD_PREFIX:
 
-                        if (!lt_valid || lt_pref > lt_valid) {
-                                log_dhcp6_client(client, "IA preferred %ds > valid %ds",
-                                                 lt_pref, lt_valid);
-                                free(addr);
-                        } else {
-                                LIST_PREPEND(addresses, ia->addresses, addr);
-                                if (lt_valid < lt_min)
-                                        lt_min = lt_valid;
+                        if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_PD)) {
+                                log_dhcp6_client(client, "IA PD Prefix option not in IA PD option");
+                                r = -EINVAL;
+                                goto error;
                         }
 
+                        r = dhcp6_option_parse_pdprefix(option, ia, &lt_valid);
+                        if (r < 0)
+                                goto error;
+
+                        if (lt_valid < lt_min)
+                                lt_min = lt_valid;
+
                         break;
 
                 case SD_DHCP6_OPTION_STATUS_CODE:
-                        if (optlen < sizeof(status))
-                                break;
 
-                        status = (*buf)[0] << 8 | (*buf)[1];
+                        status = dhcp6_option_parse_status(option);
                         if (status) {
                                 log_dhcp6_client(client, "IA status %d",
                                                  status);
+
+                                dhcp6_lease_free_ia(ia);
+
                                 r = -EINVAL;
                                 goto error;
                         }
@@ -294,30 +497,41 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
                         break;
                 }
 
-                *buflen -= optlen;
-                *buf += optlen;
+                i += sizeof(*option) + optlen;
         }
 
-        if (r == -ENOMSG)
-                r = 0;
+        switch(iatype) {
+        case SD_DHCP6_OPTION_IA_NA:
+                if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) {
+                        lt_t1 = lt_min / 2;
+                        lt_t2 = lt_min / 10 * 8;
+                        ia->ia_na.lifetime_t1 = htobe32(lt_t1);
+                        ia->ia_na.lifetime_t2 = htobe32(lt_t2);
+
+                        log_dhcp6_client(client, "Computed IA NA T1 %ds and T2 %ds as both were zero",
+                                         lt_t1, lt_t2);
+                }
 
-        if (!ia->lifetime_t1 && !ia->lifetime_t2) {
-                lt_t1 = lt_min / 2;
-                lt_t2 = lt_min / 10 * 8;
-                ia->lifetime_t1 = htobe32(lt_t1);
-                ia->lifetime_t2 = htobe32(lt_t2);
+                break;
 
-                log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero",
-                                 lt_t1, lt_t2);
-        }
+        case SD_DHCP6_OPTION_IA_PD:
+                if (!ia->ia_pd.lifetime_t1 && !ia->ia_pd.lifetime_t2) {
+                        lt_t1 = lt_min / 2;
+                        lt_t2 = lt_min / 10 * 8;
+                        ia->ia_pd.lifetime_t1 = htobe32(lt_t1);
+                        ia->ia_pd.lifetime_t2 = htobe32(lt_t2);
 
-        if (*buflen)
-                r = -ENOMSG;
+                        log_dhcp6_client(client, "Computed IA PD T1 %ds and T2 %ds as both were zero",
+                                         lt_t1, lt_t2);
+                }
 
-error:
-        *buf += *buflen;
-        *buflen = 0;
+                break;
 
+        default:
+                break;
+        }
+
+error:
         return r;
 }
 
@@ -405,8 +619,7 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char *
                 idx++;
         }
 
-        *str_arr = names;
-        names = NULL;
+        *str_arr = TAKE_PTR(names);
 
         return idx;
 
diff --git a/src/systemd/src/libsystemd-network/dhcp6-protocol.h b/src/systemd/src/libsystemd-network/dhcp6-protocol.h
index 2487c470..5f7e809b 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-protocol.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -33,6 +34,7 @@ struct DHCP6Message {
                 } _packed_;
                 be32_t transaction_id;
         };
+        uint8_t options[];
 } _packed_;
 
 typedef struct DHCP6Message DHCP6Message;
@@ -104,3 +106,9 @@ enum {
         DHCP6_STATUS_USE_MULTICAST              = 5,
         _DHCP6_STATUS_MAX                       = 6,
 };
+
+enum {
+        DHCP6_FQDN_FLAG_S = (1 << 0),
+        DHCP6_FQDN_FLAG_O = (1 << 1),
+        DHCP6_FQDN_FLAG_N = (1 << 2),
+};
diff --git a/src/systemd/src/libsystemd-network/lldp-internal.h b/src/systemd/src/libsystemd-network/lldp-internal.h
index becc162f..2673aa1c 100644
--- a/src/systemd/src/libsystemd-network/lldp-internal.h
+++ b/src/systemd/src/libsystemd-network/lldp-internal.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.c b/src/systemd/src/libsystemd-network/lldp-neighbor.c
index c560a864..cffbfb98 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.c
+++ b/src/systemd/src/libsystemd-network/lldp-neighbor.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.h b/src/systemd/src/libsystemd-network/lldp-neighbor.h
index c1a7606d..76d4c0c7 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.h
+++ b/src/systemd/src/libsystemd-network/lldp-neighbor.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/lldp-network.c b/src/systemd/src/libsystemd-network/lldp-network.c
index 4466a050..b8a10d54 100644
--- a/src/systemd/src/libsystemd-network/lldp-network.c
+++ b/src/systemd/src/libsystemd-network/lldp-network.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -93,8 +94,5 @@ int lldp_network_bind_raw_socket(int ifindex) {
         if (r < 0)
                 return -errno;
 
-        r = fd;
-        fd = -1;
-
-        return r;
+        return TAKE_FD(fd);
 }
diff --git a/src/systemd/src/libsystemd-network/lldp-network.h b/src/systemd/src/libsystemd-network/lldp-network.h
index c4cf8c79..c0f8cbe1 100644
--- a/src/systemd/src/libsystemd-network/lldp-network.h
+++ b/src/systemd/src/libsystemd-network/lldp-network.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/network-internal.c b/src/systemd/src/libsystemd-network/network-internal.c
index de37b9f0..a8a5658b 100644
--- a/src/systemd/src/libsystemd-network/network-internal.c
+++ b/src/systemd/src/libsystemd-network/network-internal.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
  This file is part of systemd.
 
@@ -23,6 +24,7 @@
 #include <linux/if.h>
 #include <netinet/ether.h>
 
+#include "sd-id128.h"
 #include "sd-ndisc.h"
 
 #include "alloc-util.h"
@@ -118,7 +120,8 @@ bool net_match_config(const struct ether_addr *match_mac,
                       char * const *match_names,
                       Condition *match_host,
                       Condition *match_virt,
-                      Condition *match_kernel,
+                      Condition *match_kernel_cmdline,
+                      Condition *match_kernel_version,
                       Condition *match_arch,
                       const struct ether_addr *dev_mac,
                       const char *dev_path,
@@ -133,7 +136,10 @@ bool net_match_config(const struct ether_addr *match_mac,
         if (match_virt && condition_test(match_virt) <= 0)
                 return false;
 
-        if (match_kernel && condition_test(match_kernel) <= 0)
+        if (match_kernel_cmdline && condition_test(match_kernel_cmdline) <= 0)
+                return false;
+
+        if (match_kernel_version && condition_test(match_kernel_version) <= 0)
                 return false;
 
         if (match_arch && condition_test(match_arch) <= 0)
@@ -273,10 +279,9 @@ int config_parse_ifalias(const char *unit,
         }
 
         free(*s);
-        if (*n) {
-                *s = n;
-                n = NULL;
-        } else
+        if (*n)
+                *s = TAKE_PTR(n);
+        else
                 *s = NULL;
 
         return 0;
@@ -422,7 +427,7 @@ int deserialize_in_addrs(struct in_addr **ret, const char *string) {
                 if (r == 0)
                         break;
 
-                new_addresses = realloc(addresses, (size + 1) * sizeof(struct in_addr));
+                new_addresses = reallocarray(addresses, size + 1, sizeof(struct in_addr));
                 if (!new_addresses)
                         return -ENOMEM;
                 else
@@ -435,8 +440,7 @@ int deserialize_in_addrs(struct in_addr **ret, const char *string) {
                 size++;
         }
 
-        *ret = addresses;
-        addresses = NULL;
+        *ret = TAKE_PTR(addresses);
 
         return size;
 }
@@ -476,7 +480,7 @@ int deserialize_in6_addrs(struct in6_addr **ret, const char *string) {
                 if (r == 0)
                         break;
 
-                new_addresses = realloc(addresses, (size + 1) * sizeof(struct in6_addr));
+                new_addresses = reallocarray(addresses, size + 1, sizeof(struct in6_addr));
                 if (!new_addresses)
                         return -ENOMEM;
                 else
@@ -489,8 +493,7 @@ int deserialize_in6_addrs(struct in6_addr **ret, const char *string) {
                 size++;
         }
 
-        *ret = addresses;
-        addresses = NULL;
+        *ret = TAKE_PTR(addresses);
 
         return size;
 }
@@ -583,8 +586,7 @@ int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t
 
         *ret_size = size;
         *ret_allocated = allocated;
-        *ret = routes;
-        routes = NULL;
+        *ret = TAKE_PTR(routes);
 
         return 0;
 }
diff --git a/src/systemd/src/libsystemd-network/network-internal.h b/src/systemd/src/libsystemd-network/network-internal.h
index 4666f174..4e69f1a5 100644
--- a/src/systemd/src/libsystemd-network/network-internal.h
+++ b/src/systemd/src/libsystemd-network/network-internal.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -36,7 +37,8 @@ bool net_match_config(const struct ether_addr *match_mac,
                       char * const *match_name,
                       Condition *match_host,
                       Condition *match_virt,
-                      Condition *match_kernel,
+                      Condition *match_kernel_cmdline,
+                      Condition *match_kernel_version,
                       Condition *match_arch,
                       const struct ether_addr *dev_mac,
                       const char *dev_path,
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index 5eab0050..b93b7334 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -357,9 +358,10 @@ int sd_dhcp_client_set_client_id(
  * without further modification. Otherwise, if duid_type is supported, DUID
  * is set based on that type. Otherwise, an error is returned.
  */
-int sd_dhcp_client_set_iaid_duid(
+static int dhcp_client_set_iaid_duid(
                 sd_dhcp_client *client,
                 uint32_t iaid,
+                bool append_iaid,
                 uint16_t duid_type,
                 const void *duid,
                 size_t duid_len) {
@@ -380,15 +382,17 @@ int sd_dhcp_client_set_iaid_duid(
         zero(client->client_id);
         client->client_id.type = 255;
 
-        /* If IAID is not configured, generate it. */
-        if (iaid == 0) {
-                r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr,
-                                             client->mac_addr_len,
-                                             &client->client_id.ns.iaid);
-                if (r < 0)
-                        return r;
-        } else
-                client->client_id.ns.iaid = htobe32(iaid);
+        if (append_iaid) {
+                /* If IAID is not configured, generate it. */
+                if (iaid == 0) {
+                        r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr,
+                                                     client->mac_addr_len,
+                                                     &client->client_id.ns.iaid);
+                        if (r < 0)
+                                return r;
+                } else
+                        client->client_id.ns.iaid = htobe32(iaid);
+        }
 
         if (duid != NULL) {
                 client->client_id.ns.duid.type = htobe16(duid_type);
@@ -402,7 +406,7 @@ int sd_dhcp_client_set_iaid_duid(
                 return -EOPNOTSUPP;
 
         client->client_id_len = sizeof(client->client_id.type) + len +
-                                sizeof(client->client_id.ns.iaid);
+                                (append_iaid ? sizeof(client->client_id.ns.iaid) : 0);
 
         if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
                 log_dhcp_client(client, "Configured IAID+DUID, restarting.");
@@ -412,6 +416,23 @@ int sd_dhcp_client_set_iaid_duid(
 
         return 0;
 }
+
+int sd_dhcp_client_set_iaid_duid(
+                sd_dhcp_client *client,
+                uint32_t iaid,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len) {
+        return dhcp_client_set_iaid_duid(client, iaid, true, duid_type, duid, duid_len);
+}
+
+int sd_dhcp_client_set_duid(
+                sd_dhcp_client *client,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len) {
+        return dhcp_client_set_iaid_duid(client, 0, false, duid_type, duid, duid_len);
+}
 #endif /* NM_IGNORED */
 
 int sd_dhcp_client_set_hostname(
@@ -420,9 +441,9 @@ int sd_dhcp_client_set_hostname(
 
         assert_return(client, -EINVAL);
 
-        /* Refuse hostnames that neither qualify as DNS nor as Linux hosntames */
+        /* Make sure hostnames qualify as DNS and as Linux hostnames */
         if (hostname &&
-            !(hostname_is_valid(hostname, false) || dns_name_is_valid(hostname) > 0))
+            !(hostname_is_valid(hostname, false) && dns_name_is_valid(hostname) > 0))
                 return -EINVAL;
 
         return free_and_strdup(&client->hostname, hostname);
@@ -833,7 +854,6 @@ static int client_send_request(sd_dhcp_client *client) {
                    client’s IP address.
                 */
 
-                /* fall through */
         case DHCP_STATE_REBINDING:
                 /* ’server identifier’ MUST NOT be filled in, ’requested IP address’
                    option MUST NOT be filled in, ’ciaddr’ MUST be filled in with
@@ -1265,9 +1285,9 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_
         if (!lease->have_subnet_mask) {
                 r = dhcp_lease_set_default_subnet_mask(lease);
                 if (r < 0) {
-                        log_dhcp_client(client, "received lease lacks subnet "
-                                        "mask, and a fallback one can not be "
-                                        "generated, ignoring");
+                        log_dhcp_client(client,
+                                        "received lease lacks subnet mask, "
+                                        "and a fallback one cannot be generated, ignoring");
                         return -ENOMSG;
                 }
         }
@@ -1336,9 +1356,9 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t le
         if (lease->subnet_mask == INADDR_ANY) {
                 r = dhcp_lease_set_default_subnet_mask(lease);
                 if (r < 0) {
-                        log_dhcp_client(client, "received lease lacks subnet "
-                                        "mask, and a fallback one can not be "
-                                        "generated, ignoring");
+                        log_dhcp_client(client,
+                                        "received lease lacks subnet mask, "
+                                        "and a fallback one cannot be generated, ignoring");
                         return -ENOMSG;
                 }
         }
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index c00190b5..c272a61e 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -23,6 +24,7 @@
 #include <arpa/inet.h>
 #include <errno.h>
 #include <stdio.h>
+#include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -394,9 +396,7 @@ static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) {
         if (dns_name_is_root(normalized))
                 return -EINVAL;
 
-        free(*ret);
-        *ret = normalized;
-        normalized = NULL;
+        free_and_replace(*ret, normalized);
 
         return 0;
 }
@@ -473,6 +473,7 @@ static int lease_parse_routes(
                 struct sd_dhcp_route *route = *routes + *routes_size;
                 int r;
 
+                route->option = SD_DHCP_OPTION_STATIC_ROUTE;
                 r = in4_addr_default_prefixlen((struct in_addr*) option, &route->dst_prefixlen);
                 if (r < 0) {
                         log_debug("Failed to determine destination prefix length from class based IP, ignoring");
@@ -516,6 +517,7 @@ static int lease_parse_classless_routes(
                         return -ENOMEM;
 
                 route = *routes + *routes_size;
+                route->option = SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE;
 
                 dst_octets = (*option == 0 ? 0 : ((*option - 1) / 8) + 1);
                 route->dst_prefixlen = *option;
@@ -685,9 +687,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
                         return 0;
                 }
 
-                free(lease->timezone);
-                lease->timezone = tz;
-                tz = NULL;
+                free_and_replace(lease->timezone, tz);
 
                 break;
         }
@@ -809,8 +809,7 @@ int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***d
                       pos = next_chunk;
         }
 
-        *domains = names;
-        names = NULL;
+        *domains = TAKE_PTR(names);
 
         return cnt;
 }
@@ -881,7 +880,8 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         if (r < 0)
                 goto fail;
 
-        fchmod(fileno(f), 0644);
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        (void) fchmod(fileno(f), 0644);
 
         fprintf(f,
                 "# This is private data. Do not parse.\n");
@@ -928,16 +928,16 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
 
         r = sd_dhcp_lease_get_dns(lease, &addresses);
         if (r > 0) {
-                fputs_unlocked("DNS=", f);
+                fputs("DNS=", f);
                 serialize_in_addrs(f, addresses, r);
-                fputs_unlocked("\n", f);
+                fputs("\n", f);
         }
 
         r = sd_dhcp_lease_get_ntp(lease, &addresses);
         if (r > 0) {
-                fputs_unlocked("NTP=", f);
+                fputs("NTP=", f);
                 serialize_in_addrs(f, addresses, r);
-                fputs_unlocked("\n", f);
+                fputs("\n", f);
         }
 
         r = sd_dhcp_lease_get_domainname(lease, &string);
@@ -946,9 +946,9 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
 
         r = sd_dhcp_lease_get_search_domains(lease, &search_domains);
         if (r > 0) {
-                fputs_unlocked("DOMAIN_SEARCH_LIST=", f);
+                fputs("DOMAIN_SEARCH_LIST=", f);
                 fputstrv(f, search_domains, NULL, NULL);
-                fputs_unlocked("\n", f);
+                fputs("\n", f);
         }
 
         r = sd_dhcp_lease_get_hostname(lease, &string);
@@ -992,7 +992,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         }
 
         LIST_FOREACH(options, option, lease->private_options) {
-                char key[strlen("OPTION_000")+1];
+                char key[STRLEN("OPTION_000")+1];
 
                 xsprintf(key, "OPTION_%" PRIu8, option->tag);
                 r = serialize_dhcp_option(f, key, option->data, option->length);
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
index f512b65b..f22daa3c 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -31,7 +32,9 @@
 #include "dhcp6-internal.h"
 #include "dhcp6-lease-internal.h"
 #include "dhcp6-protocol.h"
+#include "dns-domain.h"
 #include "fd-util.h"
+#include "hostname-util.h"
 #include "in-addr-util.h"
 #include "network-internal.h"
 #include "random-util.h"
@@ -53,6 +56,8 @@ struct sd_dhcp6_client {
         size_t mac_addr_len;
         uint16_t arp_type;
         DHCP6IA ia_na;
+        DHCP6IA ia_pd;
+        bool prefix_delegation;
         be32_t transaction_id;
         usec_t transaction_start;
         struct sd_dhcp6_lease *lease;
@@ -61,6 +66,7 @@ struct sd_dhcp6_client {
         be16_t *req_opts;
         size_t req_opts_allocated;
         size_t req_opts_len;
+        char *fqdn;
         sd_event_source *receive_message;
         usec_t retransmit_time;
         uint8_t retransmit_count;
@@ -229,12 +235,27 @@ int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
         assert_return(client, -EINVAL);
         assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
 
-        client->ia_na.id = htobe32(iaid);
+        client->ia_na.ia_na.id = htobe32(iaid);
+        client->ia_pd.ia_pd.id = htobe32(iaid);
 
         return 0;
 }
 #endif /* NM_IGNORED */
 
+int sd_dhcp6_client_set_fqdn(
+                sd_dhcp6_client *client,
+                const char *fqdn) {
+
+        assert_return(client, -EINVAL);
+
+        /* Make sure FQDN qualifies as DNS and as Linux hostname */
+        if (fqdn &&
+            !(hostname_is_valid(fqdn, false) && dns_name_is_valid(fqdn) > 0))
+                return -EINVAL;
+
+        return free_and_strdup(&client->fqdn, fqdn);
+}
+
 int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) {
         assert_return(client, -EINVAL);
         assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
@@ -265,6 +286,7 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option)
         case SD_DHCP6_OPTION_DOMAIN_LIST:
         case SD_DHCP6_OPTION_SNTP_SERVERS:
         case SD_DHCP6_OPTION_NTP_SERVER:
+        case SD_DHCP6_OPTION_RAPID_COMMIT:
                 break;
 
         default:
@@ -284,6 +306,14 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option)
         return 0;
 }
 
+int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, bool delegation) {
+        assert_return(client, -EINVAL);
+
+        client->prefix_delegation = delegation;
+
+        return 0;
+}
+
 int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
         assert_return(client, -EINVAL);
 
@@ -322,8 +352,6 @@ static int client_reset(sd_dhcp6_client *client) {
         client->receive_message =
                 sd_event_source_unref(client->receive_message);
 
-        client->fd = safe_close(client->fd);
-
         client->transaction_id = 0;
         client->transaction_start = 0;
 
@@ -393,6 +421,21 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                 if (r < 0)
                         return r;
 
+                if (client->fqdn) {
+                        r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->prefix_delegation) {
+                        r = dhcp6_option_append_pd(opt, optlen, &client->ia_pd);
+                        if (r < 0)
+                                return r;
+
+                        opt += r;
+                        optlen -= r;
+                }
+
                 break;
 
         case DHCP6_STATE_REQUEST:
@@ -413,6 +456,21 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                 if (r < 0)
                         return r;
 
+                if (client->fqdn) {
+                        r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->prefix_delegation) {
+                        r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd);
+                        if (r < 0)
+                                return r;
+
+                        opt += r;
+                        optlen -= r;
+                }
+
                 break;
 
         case DHCP6_STATE_REBIND:
@@ -422,6 +480,21 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                 if (r < 0)
                         return r;
 
+                if (client->fqdn) {
+                        r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->prefix_delegation) {
+                        r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd);
+                        if (r < 0)
+                                return r;
+
+                        opt += r;
+                        optlen -= r;
+                }
+
                 break;
 
         case DHCP6_STATE_STOPPED:
@@ -435,7 +508,7 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
         if (r < 0)
                 return r;
 
-        assert (client->duid_len);
+        assert(client->duid_len);
         r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_CLIENTID,
                                 client->duid_len, &client->duid);
         if (r < 0)
@@ -677,16 +750,20 @@ error:
 
 static int client_ensure_iaid(sd_dhcp6_client *client) {
         int r;
+        be32_t iaid;
 
         assert(client);
 
-        if (client->ia_na.id)
+        if (client->ia_na.ia_na.id)
                 return 0;
 
-        r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, &client->ia_na.id);
+        r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, &iaid);
         if (r < 0)
                 return r;
 
+        client->ia_na.ia_na.id = iaid;
+        client->ia_pd.ia_pd.id = iaid;
+
         return 0;
 }
 
@@ -695,23 +772,33 @@ static int client_parse_message(
                 DHCP6Message *message,
                 size_t len,
                 sd_dhcp6_lease *lease) {
+        size_t pos = 0;
         int r;
-        uint8_t *optval, *option, *id = NULL;
-        uint16_t optcode, status;
-        size_t optlen, id_len;
         bool clientid = false;
-        be32_t iaid_lease;
+        uint32_t lt_t1 = ~0, lt_t2 = ~0;
 
         assert(client);
         assert(message);
         assert(len >= sizeof(DHCP6Message));
         assert(lease);
 
-        option = (uint8_t *)message + sizeof(DHCP6Message);
         len -= sizeof(DHCP6Message);
 
-        while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
-                                       &optval)) >= 0) {
+        while (pos < len) {
+                DHCP6Option *option = (DHCP6Option *)&message->options[pos];
+                uint16_t optcode, optlen;
+                int status;
+                uint8_t *optval;
+                be32_t iaid_lease;
+
+                if (len < offsetof(DHCP6Option, data) ||
+                    len < offsetof(DHCP6Option, data) + be16toh(option->len))
+                        return -ENOBUFS;
+
+                optcode = be16toh(option->code);
+                optlen = be16toh(option->len);
+                optval = option->data;
+
                 switch (optcode) {
                 case SD_DHCP6_OPTION_CLIENTID:
                         if (clientid) {
@@ -732,8 +819,8 @@ static int client_parse_message(
                         break;
 
                 case SD_DHCP6_OPTION_SERVERID:
-                        r = dhcp6_lease_get_serverid(lease, &id, &id_len);
-                        if (r >= 0 && id) {
+                        r = dhcp6_lease_get_serverid(lease, NULL, NULL);
+                        if (r >= 0) {
                                 log_dhcp6_client(client, "%s contains multiple serverids",
                                                  dhcp6_message_type_to_string(message->type));
                                 return -EINVAL;
@@ -749,21 +836,21 @@ static int client_parse_message(
                         if (optlen != 1)
                                 return -EINVAL;
 
-                        r = dhcp6_lease_set_preference(lease, *optval);
+                        r = dhcp6_lease_set_preference(lease, optval[0]);
                         if (r < 0)
                                 return r;
 
                         break;
 
                 case SD_DHCP6_OPTION_STATUS_CODE:
-                        if (optlen < 2)
-                                return -EINVAL;
-
-                        status = optval[0] << 8 | optval[1];
+                        status = dhcp6_option_parse_status(option);
                         if (status) {
                                 log_dhcp6_client(client, "%s Status %s",
                                                  dhcp6_message_type_to_string(message->type),
                                                  dhcp6_message_status_to_string(status));
+                                dhcp6_lease_free_ia(&lease->ia);
+                                dhcp6_lease_free_ia(&lease->pd);
+
                                 return -EINVAL;
                         }
 
@@ -776,8 +863,35 @@ static int client_parse_message(
                                 break;
                         }
 
-                        r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
-                                                  &lease->ia);
+                        r = dhcp6_option_parse_ia(option, &lease->ia);
+                        if (r < 0 && r != -ENOMSG)
+                                return r;
+
+                        r = dhcp6_lease_get_iaid(lease, &iaid_lease);
+                        if (r < 0)
+                                return r;
+
+                        if (client->ia_na.ia_na.id != iaid_lease) {
+                                log_dhcp6_client(client, "%s has wrong IAID for IA NA",
+                                                 dhcp6_message_type_to_string(message->type));
+                                return -EINVAL;
+                        }
+
+                        if (lease->ia.addresses) {
+                                lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1));
+                                lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t1));
+                        }
+
+                        break;
+
+                case SD_DHCP6_OPTION_IA_PD:
+                        if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+                                log_dhcp6_client(client, "Information request ignoring IA PD option");
+
+                                break;
+                        }
+
+                        r = dhcp6_option_parse_ia(option, &lease->pd);
                         if (r < 0 && r != -ENOMSG)
                                 return r;
 
@@ -785,12 +899,17 @@ static int client_parse_message(
                         if (r < 0)
                                 return r;
 
-                        if (client->ia_na.id != iaid_lease) {
-                                log_dhcp6_client(client, "%s has wrong IAID",
+                        if (client->ia_pd.ia_pd.id != iaid_lease) {
+                                log_dhcp6_client(client, "%s has wrong IAID for IA PD",
                                                  dhcp6_message_type_to_string(message->type));
                                 return -EINVAL;
                         }
 
+                        if (lease->pd.addresses) {
+                                lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1));
+                                lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2));
+                        }
+
                         break;
 
                 case SD_DHCP6_OPTION_RAPID_COMMIT:
@@ -829,25 +948,36 @@ static int client_parse_message(
                         break;
                 }
 
+                pos += sizeof(*option) + optlen;
         }
 
-        if (r == -ENOMSG)
-                r = 0;
-
-        if (r < 0 || !clientid) {
+        if (!clientid) {
                 log_dhcp6_client(client, "%s has incomplete options",
                                  dhcp6_message_type_to_string(message->type));
                 return -EINVAL;
         }
 
         if (client->state != DHCP6_STATE_INFORMATION_REQUEST) {
-                r = dhcp6_lease_get_serverid(lease, &id, &id_len);
-                if (r < 0)
+                r = dhcp6_lease_get_serverid(lease, NULL, NULL);
+                if (r < 0) {
                         log_dhcp6_client(client, "%s has no server id",
                                          dhcp6_message_type_to_string(message->type));
+                        return -EINVAL;
+                }
+
+        } else {
+                if (lease->ia.addresses) {
+                        lease->ia.ia_na.lifetime_t1 = htobe32(lt_t1);
+                        lease->ia.ia_na.lifetime_t2 = htobe32(lt_t2);
+                }
+
+                if (lease->pd.addresses) {
+                        lease->pd.ia_pd.lifetime_t1 = htobe32(lt_t1);
+                        lease->pd.ia_pd.lifetime_t2 = htobe32(lt_t2);
+                }
         }
 
-        return r;
+        return 0;
 }
 
 static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, size_t len) {
@@ -1003,7 +1133,7 @@ static int client_receive_message(
                         break;
                 }
 
-                /* fall through */ /* for Soliciation Rapid Commit option check */
+                _fallthrough_; /* for Soliciation Rapid Commit option check */
         case DHCP6_STATE_REQUEST:
         case DHCP6_STATE_RENEW:
         case DHCP6_STATE_REBIND:
@@ -1060,6 +1190,24 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
         if (r < 0)
                 return r;
 
+        if (!client->receive_message) {
+                r = sd_event_add_io(client->event, &client->receive_message,
+                                    client->fd, EPOLLIN, client_receive_message,
+                                    client);
+                if (r < 0)
+                        goto error;
+
+                r = sd_event_source_set_priority(client->receive_message,
+                                                 client->event_priority);
+                if (r < 0)
+                        goto error;
+
+                r = sd_event_source_set_description(client->receive_message,
+                                                    "dhcp6-receive-message");
+                if (r < 0)
+                        goto error;
+        }
+
         switch (state) {
         case DHCP6_STATE_STOPPED:
                 if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
@@ -1068,7 +1216,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
                         return 0;
                 }
 
-                /* fall through */
+                _fallthrough_;
         case DHCP6_STATE_SOLICITATION:
                 client->state = DHCP6_STATE_SOLICITATION;
 
@@ -1085,17 +1233,17 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
 
         case DHCP6_STATE_BOUND:
 
-                if (client->lease->ia.lifetime_t1 == 0xffffffff ||
-                    client->lease->ia.lifetime_t2 == 0xffffffff) {
+                if (client->lease->ia.ia_na.lifetime_t1 == 0xffffffff ||
+                    client->lease->ia.ia_na.lifetime_t2 == 0xffffffff) {
 
                         log_dhcp6_client(client, "Infinite T1 0x%08x or T2 0x%08x",
-                                         be32toh(client->lease->ia.lifetime_t1),
-                                         be32toh(client->lease->ia.lifetime_t2));
+                                         be32toh(client->lease->ia.ia_na.lifetime_t1),
+                                         be32toh(client->lease->ia.ia_na.lifetime_t2));
 
                         return 0;
                 }
 
-                timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t1) * USEC_PER_SEC);
+                timeout = client_timeout_compute_random(be32toh(client->lease->ia.ia_na.lifetime_t1) * USEC_PER_SEC);
 
                 log_dhcp6_client(client, "T1 expires in %s",
                                  format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
@@ -1106,18 +1254,18 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
                                       10 * USEC_PER_SEC, client_timeout_t1,
                                       client);
                 if (r < 0)
-                        return r;
+                        goto error;
 
                 r = sd_event_source_set_priority(client->lease->ia.timeout_t1,
                                                  client->event_priority);
                 if (r < 0)
-                        return r;
+                        goto error;
 
                 r = sd_event_source_set_description(client->lease->ia.timeout_t1, "dhcp6-t1-timeout");
                 if (r < 0)
-                        return r;
+                        goto error;
 
-                timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t2) * USEC_PER_SEC);
+                timeout = client_timeout_compute_random(be32toh(client->lease->ia.ia_na.lifetime_t2) * USEC_PER_SEC);
 
                 log_dhcp6_client(client, "T2 expires in %s",
                                  format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
@@ -1128,16 +1276,16 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
                                       10 * USEC_PER_SEC, client_timeout_t2,
                                       client);
                 if (r < 0)
-                        return r;
+                        goto error;
 
                 r = sd_event_source_set_priority(client->lease->ia.timeout_t2,
                                                  client->event_priority);
                 if (r < 0)
-                        return r;
+                        goto error;
 
                 r = sd_event_source_set_description(client->lease->ia.timeout_t2, "dhcp6-t2-timeout");
                 if (r < 0)
-                        return r;
+                        goto error;
 
                 client->state = state;
 
@@ -1151,18 +1299,22 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
                               clock_boottime_or_monotonic(), 0, 0, client_timeout_resend,
                               client);
         if (r < 0)
-                return r;
+                goto error;
 
         r = sd_event_source_set_priority(client->timeout_resend,
                                          client->event_priority);
         if (r < 0)
-                return r;
+                goto error;
 
         r = sd_event_source_set_description(client->timeout_resend, "dhcp6-resend-timeout");
         if (r < 0)
-                return r;
+                goto error;
 
         return 0;
+
+ error:
+        client_reset(client);
+        return r;
 }
 
 int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
@@ -1170,6 +1322,8 @@ int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
 
         client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
 
+        client->fd = safe_close(client->fd);
+
         return 0;
 }
 
@@ -1203,32 +1357,18 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) {
         if (r < 0)
                 return r;
 
-        r = dhcp6_network_bind_udp_socket(client->ifindex, &client->local_address);
-        if (r < 0) {
-                _cleanup_free_ char *p = NULL;
-
-                (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p);
-                return log_dhcp6_client_errno(client, r,
-                                              "Failed to bind to UDP socket at address %s: %m", strna(p));
-        }
-
-        client->fd = r;
+        if (client->fd < 0) {
+                r = dhcp6_network_bind_udp_socket(client->ifindex, &client->local_address);
+                if (r < 0) {
+                        _cleanup_free_ char *p = NULL;
 
-        r = sd_event_add_io(client->event, &client->receive_message,
-                            client->fd, EPOLLIN, client_receive_message,
-                            client);
-        if (r < 0)
-                goto error;
-
-        r = sd_event_source_set_priority(client->receive_message,
-                                         client->event_priority);
-        if (r < 0)
-                goto error;
+                        (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p);
+                        return log_dhcp6_client_errno(client, r,
+                                                      "Failed to bind to UDP socket at address %s: %m", strna(p));
+                }
 
-        r = sd_event_source_set_description(client->receive_message,
-                                            "dhcp6-receive-message");
-        if (r < 0)
-                goto error;
+                client->fd = r;
+        }
 
         if (client->information_request)
                 state = DHCP6_STATE_INFORMATION_REQUEST;
@@ -1238,10 +1378,6 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) {
                          "Managed");
 
         return client_start(client, state);
-
-error:
-        client_reset(client);
-        return r;
 }
 
 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64_t priority) {
@@ -1301,9 +1437,12 @@ sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
 
         client_reset(client);
 
+        client->fd = safe_close(client->fd);
+
         sd_dhcp6_client_detach_event(client);
 
         free(client->req_opts);
+        free(client->fqdn);
         return mfree(client);
 }
 
@@ -1319,6 +1458,7 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret) {
 
         client->n_ref = 1;
         client->ia_na.type = SD_DHCP6_OPTION_IA_NA;
+        client->ia_pd.type = SD_DHCP6_OPTION_IA_PD;
         client->ifindex = -1;
         client->fd = -1;
 
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
index 63a813aa..00b9fdf0 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -50,7 +51,7 @@ int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) {
                         valid = t;
         }
 
-        t = be32toh(ia->lifetime_t2);
+        t = be32toh(ia->ia_na.lifetime_t2);
         if (t > valid)
                 return -EINVAL;
 
@@ -96,11 +97,14 @@ int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id,
 
 int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len) {
         assert_return(lease, -EINVAL);
-        assert_return(id, -EINVAL);
-        assert_return(len, -EINVAL);
 
-        *id = lease->serverid;
-        *len = lease->serverid_len;
+        if (!lease->serverid)
+                return -ENOMSG;
+
+        if (id)
+                *id = lease->serverid;
+        if (len)
+                *len = lease->serverid_len;
 
         return 0;
 }
@@ -145,7 +149,7 @@ int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid) {
         assert_return(lease, -EINVAL);
         assert_return(iaid, -EINVAL);
 
-        *iaid = lease->ia.id;
+        *iaid = lease->ia.ia_na.id;
 
         return 0;
 }
@@ -177,6 +181,37 @@ void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) {
                 lease->addr_iter = lease->ia.addresses;
 }
 
+int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix,
+                          uint8_t *prefix_len,
+                          uint32_t *lifetime_preferred,
+                          uint32_t *lifetime_valid) {
+        assert_return(lease, -EINVAL);
+        assert_return(prefix, -EINVAL);
+        assert_return(prefix_len, -EINVAL);
+        assert_return(lifetime_preferred, -EINVAL);
+        assert_return(lifetime_valid, -EINVAL);
+
+        if (!lease->prefix_iter)
+                return -ENOMSG;
+
+        memcpy(prefix, &lease->prefix_iter->iapdprefix.address,
+               sizeof(struct in6_addr));
+        *prefix_len = lease->prefix_iter->iapdprefix.prefixlen;
+        *lifetime_preferred =
+                be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred);
+        *lifetime_valid =
+                be32toh(lease->prefix_iter->iapdprefix.lifetime_valid);
+
+        lease->prefix_iter = lease->prefix_iter->addresses_next;
+
+        return 0;
+}
+
+void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) {
+        if (lease)
+                lease->prefix_iter = lease->pd.addresses;
+}
+
 int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
         int r;
 
@@ -383,6 +418,7 @@ sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease) {
 
         free(lease->serverid);
         dhcp6_lease_free_ia(&lease->ia);
+        dhcp6_lease_free_ia(&lease->pd);
 
         free(lease->dns);
 
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
index 694384b5..08f46dd2 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -289,8 +290,7 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata)
                         break;
                 }
 
-                /* fall through */
-
+                _fallthrough_;
         case IPV4ACD_STATE_WAITING_ANNOUNCE:
                 /* Send announcement packet */
                 r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr);
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
index 47fc141c..f1e7b404 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -26,6 +27,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "sd-id128.h"
 #include "sd-ipv4acd.h"
 #include "sd-ipv4ll.h"
 
diff --git a/src/systemd/src/libsystemd-network/sd-lldp.c b/src/systemd/src/libsystemd-network/sd-lldp.c
index 31e24486..20956041 100644
--- a/src/systemd/src/libsystemd-network/sd-lldp.c
+++ b/src/systemd/src/libsystemd-network/sd-lldp.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
diff --git a/src/systemd/src/libsystemd/sd-event/sd-event.c b/src/systemd/src/libsystemd/sd-event/sd-event.c
index 9dfe6847..4fb8ed7f 100644
--- a/src/systemd/src/libsystemd/sd-event/sd-event.c
+++ b/src/systemd/src/libsystemd/sd-event/sd-event.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -123,6 +124,7 @@ struct sd_event_source {
                         uint32_t events;
                         uint32_t revents;
                         bool registered:1;
+                        bool owned:1;
                 } io;
                 struct {
                         sd_event_time_handler_t callback;
@@ -241,8 +243,14 @@ struct sd_event {
         unsigned delays[sizeof(usec_t) * 8];
 };
 
+static thread_local sd_event *default_event = NULL;
+
 static void source_disconnect(sd_event_source *s);
 
+static sd_event *event_resolve(sd_event *e) {
+        return e == SD_EVENT_DEFAULT ? default_event : e;
+}
+
 static int pending_prioq_compare(const void *a, const void *b) {
         const sd_event_source *x = a, *y = b;
 
@@ -451,6 +459,8 @@ _public_ int sd_event_new(sd_event** ret) {
                 goto fail;
         }
 
+        e->epoll_fd = fd_move_above_stdio(e->epoll_fd);
+
         if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) {
                 log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 ... 2^63 us will be logged every 5s.");
                 e->profile_delays = true;
@@ -689,7 +699,7 @@ static int event_make_signal_data(
                 return 0;
         }
 
-        d->fd = r;
+        d->fd = fd_move_above_stdio(r);
 
         ev.events = EPOLLIN;
         ev.data.ptr = d;
@@ -884,6 +894,10 @@ static void source_free(sd_event_source *s) {
         assert(s);
 
         source_disconnect(s);
+
+        if (s->type == SOURCE_IO && s->io.owned)
+                safe_close(s->io.fd);
+
         free(s->description);
         free(s);
 }
@@ -968,6 +982,7 @@ _public_ int sd_event_add_io(
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(fd >= 0, -EBADF);
         assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
         assert_return(callback, -EINVAL);
@@ -1034,6 +1049,8 @@ static int event_setup_timer_fd(
         if (fd < 0)
                 return -errno;
 
+        fd = fd_move_above_stdio(fd);
+
         ev.events = EPOLLIN;
         ev.data.ptr = d;
 
@@ -1068,6 +1085,7 @@ _public_ int sd_event_add_time(
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(accuracy != (uint64_t) -1, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
@@ -1150,6 +1168,7 @@ _public_ int sd_event_add_signal(
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(SIGNAL_VALID(sig), -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
@@ -1210,6 +1229,7 @@ _public_ int sd_event_add_child(
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(pid > 1, -EINVAL);
         assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
         assert_return(options != 0, -EINVAL);
@@ -1267,6 +1287,7 @@ _public_ int sd_event_add_defer(
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(callback, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
@@ -1301,6 +1322,7 @@ _public_ int sd_event_add_post(
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(callback, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
@@ -1339,6 +1361,7 @@ _public_ int sd_event_add_exit(
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(callback, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
@@ -1484,6 +1507,21 @@ _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) {
         return 0;
 }
 
+_public_ int sd_event_source_get_io_fd_own(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_IO, -EDOM);
+
+        return s->io.owned;
+}
+
+_public_ int sd_event_source_set_io_fd_own(sd_event_source *s, int own) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_IO, -EDOM);
+
+        s->io.owned = own;
+        return 0;
+}
+
 _public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events) {
         assert_return(s, -EINVAL);
         assert_return(events, -EINVAL);
@@ -2452,6 +2490,7 @@ _public_ int sd_event_prepare(sd_event *e) {
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(!event_pid_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
@@ -2509,6 +2548,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
         int r, m, i;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(!event_pid_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_ARMED, -EBUSY);
@@ -2615,6 +2655,7 @@ _public_ int sd_event_dispatch(sd_event *e) {
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(!event_pid_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_PENDING, -EBUSY);
@@ -2656,6 +2697,7 @@ _public_ int sd_event_run(sd_event *e, uint64_t timeout) {
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(!event_pid_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
@@ -2700,6 +2742,7 @@ _public_ int sd_event_loop(sd_event *e) {
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(!event_pid_changed(e), -ECHILD);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
 
@@ -2721,6 +2764,7 @@ finish:
 _public_ int sd_event_get_fd(sd_event *e) {
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(!event_pid_changed(e), -ECHILD);
 
         return e->epoll_fd;
@@ -2728,6 +2772,7 @@ _public_ int sd_event_get_fd(sd_event *e) {
 
 _public_ int sd_event_get_state(sd_event *e) {
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(!event_pid_changed(e), -ECHILD);
 
         return e->state;
@@ -2735,6 +2780,7 @@ _public_ int sd_event_get_state(sd_event *e) {
 
 _public_ int sd_event_get_exit_code(sd_event *e, int *code) {
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(code, -EINVAL);
         assert_return(!event_pid_changed(e), -ECHILD);
 
@@ -2747,6 +2793,7 @@ _public_ int sd_event_get_exit_code(sd_event *e, int *code) {
 
 _public_ int sd_event_exit(sd_event *e, int code) {
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
 
@@ -2758,6 +2805,7 @@ _public_ int sd_event_exit(sd_event *e, int code) {
 
 _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) {
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(usec, -EINVAL);
         assert_return(!event_pid_changed(e), -ECHILD);
 
@@ -2782,8 +2830,6 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) {
 }
 
 _public_ int sd_event_default(sd_event **ret) {
-
-        static thread_local sd_event *default_event = NULL;
         sd_event *e = NULL;
         int r;
 
@@ -2810,6 +2856,7 @@ _public_ int sd_event_default(sd_event **ret) {
 #if 0 /* NM_IGNORED */
 _public_ int sd_event_get_tid(sd_event *e, pid_t *tid) {
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(tid, -EINVAL);
         assert_return(!event_pid_changed(e), -ECHILD);
 
@@ -2825,6 +2872,7 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) {
         int r;
 
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(!event_pid_changed(e), -ECHILD);
 
         if (e->watchdog == !!b)
@@ -2875,6 +2923,7 @@ fail:
 
 _public_ int sd_event_get_watchdog(sd_event *e) {
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(!event_pid_changed(e), -ECHILD);
 
         return e->watchdog;
@@ -2882,6 +2931,7 @@ _public_ int sd_event_get_watchdog(sd_event *e) {
 
 _public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) {
         assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(!event_pid_changed(e), -ECHILD);
 
         *ret = e->iteration;
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.c b/src/systemd/src/libsystemd/sd-id128/id128-util.c
index 19277021..f1033346 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.c
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -19,10 +20,12 @@
 
 #include "nm-sd-adapt.h"
 
+#include <errno.h>
 #include <fcntl.h>
 #include <unistd.h>
 
 #include "fd-util.h"
+#include "fs-util.h"
 #include "hexdecoct.h"
 #include "id128-util.h"
 #include "io-util.h"
@@ -120,7 +123,7 @@ int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) {
                 if (buffer[32] != '\n')
                         return -EINVAL;
 
-                /* fall through */
+                _fallthrough_;
         case 32: /* plain UUID without trailing newline */
                 if (f == ID128_UUID)
                         return -EINVAL;
@@ -132,7 +135,7 @@ int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) {
                 if (buffer[36] != '\n')
                         return -EINVAL;
 
-                /* fall through */
+                _fallthrough_;
         case 36: /* RFC UUID without trailing newline */
                 if (f == ID128_PLAIN)
                         return -EINVAL;
@@ -183,9 +186,13 @@ int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync) {
         if (do_sync) {
                 if (fsync(fd) < 0)
                         return -errno;
+
+                r = fsync_directory_of_file(fd);
+                if (r < 0)
+                        return r;
         }
 
-        return r;
+        return 0;
 }
 
 int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync) {
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.h b/src/systemd/src/libsystemd/sd-id128/id128-util.h
index 6b3855ac..9f3340e5 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.h
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
index 052110d5..e5bd447a 100644
--- a/src/systemd/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
diff --git a/src/systemd/src/shared/dns-domain.c b/src/systemd/src/shared/dns-domain.c
index c313a033..cfc1f3b3 100644
--- a/src/systemd/src/shared/dns-domain.c
+++ b/src/systemd/src/shared/dns-domain.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -301,8 +302,7 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) {
         if (r < 0)
                 return r;
 
-        *ret = s;
-        s = NULL;
+        *ret = TAKE_PTR(s);
 
         return r;
 }
@@ -609,8 +609,7 @@ int dns_name_endswith(const char *name, const char *suffix) {
 
                         /* Not the same, let's jump back, and try with the next label again */
                         s = suffix;
-                        n = saved_n;
-                        saved_n = NULL;
+                        n = TAKE_PTR(saved_n);
                 }
         }
 }
@@ -701,23 +700,26 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
 }
 
 int dns_name_between(const char *a, const char *b, const char *c) {
-        int n;
-
         /* 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. */
 
-        n = dns_name_compare_func(a, c);
-        if (n == 0)
-                return -EINVAL;
-        else if (n < 0)
-                /*       a<---b--->c       */
+        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
-                /* <--b--c         a--b--> */
+                /*
+                   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;
 }
@@ -969,6 +971,12 @@ bool dns_srv_type_is_valid(const char *name) {
         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;
 
diff --git a/src/systemd/src/shared/dns-domain.h b/src/systemd/src/shared/dns-domain.h
index d1a99be7..3ebd4018 100644
--- a/src/systemd/src/shared/dns-domain.h
+++ b/src/systemd/src/shared/dns-domain.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
@@ -97,6 +98,7 @@ 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);
diff --git a/src/systemd/src/systemd/_sd-common.h b/src/systemd/src/systemd/_sd-common.h
index 97c39438..b4400e7b 100644
--- a/src/systemd/src/systemd/_sd-common.h
+++ b/src/systemd/src/systemd/_sd-common.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #ifndef foosdcommonhfoo
 #define foosdcommonhfoo
 
diff --git a/src/systemd/src/systemd/sd-dhcp-client.h b/src/systemd/src/systemd/sd-dhcp-client.h
index 5e46d8d0..789cc501 100644
--- a/src/systemd/src/systemd/sd-dhcp-client.h
+++ b/src/systemd/src/systemd/sd-dhcp-client.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #ifndef foosddhcpclienthfoo
 #define foosddhcpclienthfoo
 
@@ -131,6 +132,11 @@ int sd_dhcp_client_set_iaid_duid(
                 uint16_t duid_type,
                 const void *duid,
                 size_t duid_len);
+int sd_dhcp_client_set_duid(
+                sd_dhcp_client *client,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len);
 int sd_dhcp_client_get_client_id(
                 sd_dhcp_client *client,
                 uint8_t *type,
diff --git a/src/systemd/src/systemd/sd-dhcp-lease.h b/src/systemd/src/systemd/sd-dhcp-lease.h
index 7ab99ccc..3cc7fcab 100644
--- a/src/systemd/src/systemd/sd-dhcp-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp-lease.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #ifndef foosddhcpleasehfoo
 #define foosddhcpleasehfoo
 
diff --git a/src/systemd/src/systemd/sd-dhcp6-client.h b/src/systemd/src/systemd/sd-dhcp6-client.h
index 7819f0d2..cadb32a0 100644
--- a/src/systemd/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/src/systemd/sd-dhcp6-client.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #ifndef foosddhcp6clienthfoo
 #define foosddhcp6clienthfoo
 
@@ -22,6 +23,7 @@
 
 #include <inttypes.h>
 #include <net/ethernet.h>
+#include <stdbool.h>
 #include <sys/types.h>
 
 #include "sd-dhcp6-lease.h"
@@ -63,11 +65,15 @@ enum {
 
         SD_DHCP6_OPTION_DNS_SERVERS                = 23,  /* RFC 3646 */
         SD_DHCP6_OPTION_DOMAIN_LIST                = 24,  /* RFC 3646 */
+        SD_DHCP6_OPTION_IA_PD                      = 25,  /* RFC 3633, prefix delegation */
+        SD_DHCP6_OPTION_IA_PD_PREFIX               = 26,  /* RFC 3633, prefix delegation */
 
         SD_DHCP6_OPTION_SNTP_SERVERS               = 31,  /* RFC 4075, deprecated */
 
         /* option code 35 is unassigned */
 
+        SD_DHCP6_OPTION_FQDN                       = 39,  /* RFC 4704 */
+
         SD_DHCP6_OPTION_NTP_SERVER                 = 56,  /* RFC 5908 */
 
         /* option codes 89-142 are unassigned */
@@ -101,6 +107,9 @@ int sd_dhcp6_client_set_duid(
 int sd_dhcp6_client_set_iaid(
                 sd_dhcp6_client *client,
                 uint32_t iaid);
+int sd_dhcp6_client_set_fqdn(
+                sd_dhcp6_client *client,
+                const char *fqdn);
 int sd_dhcp6_client_set_information_request(
                 sd_dhcp6_client *client,
                 int enabled);
@@ -110,6 +119,8 @@ int sd_dhcp6_client_get_information_request(
 int sd_dhcp6_client_set_request_option(
                 sd_dhcp6_client *client,
                 uint16_t option);
+int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client,
+                                          bool delegation);
 
 int sd_dhcp6_client_get_lease(
                 sd_dhcp6_client *client,
diff --git a/src/systemd/src/systemd/sd-dhcp6-lease.h b/src/systemd/src/systemd/sd-dhcp6-lease.h
index 184fbb8e..22a5f8ce 100644
--- a/src/systemd/src/systemd/sd-dhcp6-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp6-lease.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #ifndef foosddhcp6leasehfoo
 #define foosddhcp6leasehfoo
 
@@ -35,6 +36,11 @@ int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease,
                                struct in6_addr *addr,
                                uint32_t *lifetime_preferred,
                                uint32_t *lifetime_valid);
+void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease);
+int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix,
+                          uint8_t *prefix_len,
+                          uint32_t *lifetime_preferred,
+                          uint32_t *lifetime_valid);
 
 int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, struct in6_addr **addrs);
 int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains);
diff --git a/src/systemd/src/systemd/sd-event.h b/src/systemd/src/systemd/sd-event.h
index f8cb8956..ec4b7bcf 100644
--- a/src/systemd/src/systemd/sd-event.h
+++ b/src/systemd/src/systemd/sd-event.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #ifndef foosdeventhfoo
 #define foosdeventhfoo
 
@@ -25,6 +26,7 @@
 #include <sys/epoll.h>
 #include <sys/signalfd.h>
 #include <sys/types.h>
+#include <time.h>
 
 #include "_sd-common.h"
 
@@ -39,6 +41,8 @@
 
 _SD_BEGIN_DECLARATIONS;
 
+#define SD_EVENT_DEFAULT ((sd_event *) 1)
+
 typedef struct sd_event sd_event;
 typedef struct sd_event_source sd_event_source;
 
@@ -123,6 +127,8 @@ int sd_event_source_get_enabled(sd_event_source *s, int *enabled);
 int sd_event_source_set_enabled(sd_event_source *s, int enabled);
 int sd_event_source_get_io_fd(sd_event_source *s);
 int sd_event_source_set_io_fd(sd_event_source *s, int fd);
+int sd_event_source_get_io_fd_own(sd_event_source *s);
+int sd_event_source_set_io_fd_own(sd_event_source *s, int own);
 int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events);
 int sd_event_source_set_io_events(sd_event_source *s, uint32_t events);
 int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents);
diff --git a/src/systemd/src/systemd/sd-id128.h b/src/systemd/src/systemd/sd-id128.h
index 9b38969b..67fc5956 100644
--- a/src/systemd/src/systemd/sd-id128.h
+++ b/src/systemd/src/systemd/sd-id128.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #ifndef foosdid128hfoo
 #define foosdid128hfoo
 
diff --git a/src/systemd/src/systemd/sd-ipv4acd.h b/src/systemd/src/systemd/sd-ipv4acd.h
index 16d99983..677ae3b2 100644
--- a/src/systemd/src/systemd/sd-ipv4acd.h
+++ b/src/systemd/src/systemd/sd-ipv4acd.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #ifndef foosdipv4acdfoo
 #define foosdipv4acdfoo
 
diff --git a/src/systemd/src/systemd/sd-ipv4ll.h b/src/systemd/src/systemd/sd-ipv4ll.h
index 5ba92083..c330b0ae 100644
--- a/src/systemd/src/systemd/sd-ipv4ll.h
+++ b/src/systemd/src/systemd/sd-ipv4ll.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #ifndef foosdipv4llfoo
 #define foosdipv4llfoo
 
diff --git a/src/systemd/src/systemd/sd-lldp.h b/src/systemd/src/systemd/sd-lldp.h
index 3f35eebe..0a76fa63 100644
--- a/src/systemd/src/systemd/sd-lldp.h
+++ b/src/systemd/src/systemd/sd-lldp.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #ifndef foosdlldphfoo
 #define foosdlldphfoo
 
diff --git a/src/systemd/src/systemd/sd-ndisc.h b/src/systemd/src/systemd/sd-ndisc.h
index 9f7d4ef7..15211450 100644
--- a/src/systemd/src/systemd/sd-ndisc.h
+++ b/src/systemd/src/systemd/sd-ndisc.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #ifndef foosdndiscfoo
 #define foosdndiscfoo
 
diff --git a/src/tests/config/meson.build b/src/tests/config/meson.build
new file mode 100644
index 00000000..fb40d9a9
--- /dev/null
+++ b/src/tests/config/meson.build
@@ -0,0 +1,26 @@
+test_unit = 'test-config'
+
+sources = files(
+  'nm-test-device.c',
+  'test-config.c'
+)
+
+test_config_dir = meson.current_source_dir()
+
+cflags = [
+  '-DSRCDIR="@0@"'.format(test_config_dir),
+  '-DBUILDDIR="@0@"'.format(test_config_dir)
+]
+
+exe = executable(
+  test_unit,
+  sources,
+  dependencies: test_nm_dep,
+  c_args: cflags
+)
+
+test(
+  'config/' + test_unit,
+  test_script,
+  args: test_args + [exe.full_path()]
+)
diff --git a/src/tests/config/nm-test-device.c b/src/tests/config/nm-test-device.c
index 3ec866f5..49631583 100644
--- a/src/tests/config/nm-test-device.c
+++ b/src/tests/config/nm-test-device.c
@@ -57,7 +57,7 @@ nm_test_device_init (NMTestDevice *self)
 }
 
 /* We jump over NMDevice's construct/destruct methods, which require NMPlatform
- * and NMConnectionProvider to be initialized.
+ * and NMSettings to be initialized.
  */
 static void
 constructed (GObject *object)
diff --git a/src/tests/config/test-config.c b/src/tests/config/test-config.c
index 89b22a9f..50215756 100644
--- a/src/tests/config/test-config.c
+++ b/src/tests/config/test-config.c
@@ -25,7 +25,7 @@
 #include "nm-config.h"
 #include "nm-test-device.h"
 #include "platform/nm-fake-platform.h"
-#include "nm-bus-manager.h"
+#include "nm-dbus-manager.h"
 #include "nm-connectivity.h"
 
 #include "nm-test-utils-core.h"
@@ -254,7 +254,7 @@ test_config_global_dns (void)
 	NMConfig *config;
 	const NMGlobalDnsConfig *dns;
 	NMGlobalDnsDomain *domain;
-	const char *const *strv;
+	const char *const*strv;
 
 	config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "", NULL,
 	                       "/no/such/dir", "", NULL);
@@ -318,10 +318,10 @@ test_config_global_dns (void)
 	g_object_unref (config);
 }
 
-#if WITH_CONCHECK
 static void
 test_config_connectivity_check (void)
 {
+#if WITH_CONCHECK
 	const char *CONFIG_INTERN = BUILDDIR"/test-connectivity-check-intern.conf";
 	NMConfig *config;
 	NMConnectivity *connectivity;
@@ -334,14 +334,14 @@ test_config_connectivity_check (void)
 	g_assert (nm_connectivity_check_enabled (connectivity));
 
 	/* disable connectivity checking */
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal *");
+	NMTST_EXPECT_NM_INFO ("config: signal: *");
 	nm_config_set_connectivity_check_enabled (config, FALSE);
 	g_test_assert_expected_messages ();
 
 	g_assert (!nm_connectivity_check_enabled (connectivity));
 
 	/* re-enable connectivity checking */
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal *");
+	NMTST_EXPECT_NM_INFO ("config: signal: *");
 	nm_config_set_connectivity_check_enabled (config, TRUE);
 	g_test_assert_expected_messages ();
 
@@ -351,8 +351,10 @@ test_config_connectivity_check (void)
 	g_object_unref (config);
 
 	g_assert (remove (CONFIG_INTERN) == 0);
-}
+#else
+	g_test_skip ("concheck disabled");
 #endif
+}
 
 static void
 test_config_no_auto_default (void)
@@ -386,7 +388,7 @@ test_config_no_auto_default (void)
 	g_assert (!nm_config_get_no_auto_default_for_device (config, dev3));
 	g_assert (nm_config_get_no_auto_default_for_device (config, dev4));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal NO_AUTO_DEFAULT,no-auto-default *");
+	NMTST_EXPECT_NM_INFO ("config: signal: NO_AUTO_DEFAULT,no-auto-default *");
 	nm_config_set_no_auto_default_for_device (config, dev3);
 	g_test_assert_expected_messages ();
 
@@ -597,9 +599,9 @@ _set_values_user (NMConfig *config,
 	config_data_before = g_object_ref (nm_config_get_data (config));
 
 	if (expected_changes != NM_CONFIG_CHANGE_NONE)
-		g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal *");
+		NMTST_EXPECT_NM_INFO ("config: signal: *");
 	else
-		g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal SIGHUP (no changes from disk)*");
+		NMTST_EXPECT_NM_INFO ("config: signal: SIGHUP (no changes from disk)*");
 
 	nm_config_reload (config, NM_CONFIG_CHANGE_CAUSE_SIGHUP);
 
@@ -641,7 +643,7 @@ _set_values_intern (NMConfig *config,
 	                  &config_changed_data);
 
 	if (expected_changes != NM_CONFIG_CHANGE_NONE)
-		g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal *");
+		NMTST_EXPECT_NM_INFO ("config: signal: *");
 
 	nm_config_set_values (config, keyfile_intern, TRUE, FALSE);
 
@@ -904,15 +906,15 @@ test_config_signal (void)
 	                  &expected);
 
 	expected = NM_CONFIG_CHANGE_CAUSE_SIGUSR1;
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal SIGUSR1");
+	NMTST_EXPECT_NM_INFO ("config: signal: SIGUSR1");
 	nm_config_reload (config, expected);
 
 	expected = NM_CONFIG_CHANGE_CAUSE_SIGUSR2;
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal SIGUSR2");
+	NMTST_EXPECT_NM_INFO ("config: signal: SIGUSR2");
 	nm_config_reload (config, expected);
 
 	expected = NM_CONFIG_CHANGE_CAUSE_SIGHUP;
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal SIGHUP (no changes from disk)*");
+	NMTST_EXPECT_NM_INFO ("config: signal: SIGHUP (no changes from disk)*");
 	nm_config_reload (config, expected);
 
 
@@ -925,7 +927,7 @@ test_config_signal (void)
 	                  G_CALLBACK (_test_signal_config_changed_cb2),
 	                  &expected);
 	expected = NM_CONFIG_CHANGE_CAUSE_SIGUSR2;
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal SIGUSR2");
+	NMTST_EXPECT_NM_INFO ("config: signal: SIGUSR2");
 	nm_config_reload (config, NM_CONFIG_CHANGE_CAUSE_SIGUSR2);
 	g_signal_handlers_disconnect_by_func (config, _test_signal_config_changed_cb2, &expected);
 
@@ -1037,13 +1039,6 @@ main (int argc, char **argv)
 {
 	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
 
-	/* Initialize the DBus manager singleton explicitly, because it is accessed by
-	 * the class initializer of NMDevice (used by the NMTestDevice stub).
-	 * This way, we skip calling nm_bus_manager_init_bus() which would
-	 * either fail and/or cause unexpected actions in the test.
-	 * */
-	nm_bus_manager_setup (g_object_new (NM_TYPE_BUS_MANAGER, NULL));
-
 	nm_fake_platform_setup ();
 
 	g_test_add_func ("/config/simple", test_config_simple);
@@ -1055,9 +1050,7 @@ main (int argc, char **argv)
 
 	g_test_add_func ("/config/set-values", test_config_set_values);
 	g_test_add_func ("/config/global-dns", test_config_global_dns);
-#if WITH_CONCHECK
 	g_test_add_func ("/config/connectivity-check", test_config_connectivity_check);
-#endif
 
 	g_test_add_func ("/config/signal", test_config_signal);
 
diff --git a/src/tests/meson.build b/src/tests/meson.build
new file mode 100644
index 00000000..386ede87
--- /dev/null
+++ b/src/tests/meson.build
@@ -0,0 +1,47 @@
+subdir('config')
+
+test_units = [
+  'test-general',
+  'test-general-with-expect',
+  'test-ip4-config',
+  'test-ip6-config',
+  'test-dcb',
+  'test-wired-defname',
+  'test-utils'
+]
+
+foreach test_unit: test_units
+  exe = executable(
+    test_unit,
+    test_unit + '.c',
+    dependencies: test_nm_dep
+  )
+
+  test(
+    'src/' + test_unit,
+    test_script,
+    args: test_args + [exe.full_path()]
+  )
+endforeach
+
+test_unit = 'test-systemd'
+
+cflags = [
+  '-DNETWORKMANAGER_COMPILATION_TEST',
+  '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD',
+]
+
+exe = executable(
+  test_unit,
+  [test_unit + '.c'] + shared_siphash,
+  include_directories: src_inc,
+  dependencies: nm_core_dep,
+  c_args: cflags,
+  link_with: libsystemd_nm
+)
+
+test(
+  'src/' + test_unit,
+  test_script,
+  args: test_args + [exe.full_path()]
+)
diff --git a/src/tests/test-general-with-expect.c b/src/tests/test-general-with-expect.c
index 2911d84d..f67c332b 100644
--- a/src/tests/test-general-with-expect.c
+++ b/src/tests/test-general-with-expect.c
@@ -167,7 +167,7 @@ test_nm_utils_kill_child_spawn (char **argv, gboolean do_not_reap_child)
 }
 
 static pid_t
-test_nm_utils_kill_child_create_and_join_pgroup (void)
+do_test_nm_utils_kill_child_create_and_join_pgroup (void)
 {
 	int err, tmp = 0;
 	int pipefd[2];
@@ -177,10 +177,7 @@ test_nm_utils_kill_child_create_and_join_pgroup (void)
 	g_assert (err == 0);
 
 	pgid = fork();
-	if (pgid < 0) {
-		g_assert_not_reached ();
-		return pgid;
-	}
+	g_assert (pgid >= 0);
 
 	if (pgid == 0) {
 		/* child process... */
@@ -206,7 +203,6 @@ test_nm_utils_kill_child_create_and_join_pgroup (void)
 	err = setpgid (0, pgid);
 	g_assert (err == 0);
 
-
 	do {
 		err = waitpid (pgid, &tmp, 0);
 	} while (err == -1 && errno == EINTR);
@@ -219,9 +215,8 @@ test_nm_utils_kill_child_create_and_join_pgroup (void)
 #define TEST_TOKEN  "nm_test_kill_child_process"
 
 static void
-test_nm_utils_kill_child (void)
+do_test_nm_utils_kill_child (void)
 {
-	int err;
 	GLogLevelFlags fatal_mask;
 	char *argv_watchdog[] = {
 			"bash",
@@ -255,7 +250,6 @@ test_nm_utils_kill_child (void)
 			"trap \"while true; do :; done\" TERM; while true; do :; done; #" TEST_TOKEN,
 			NULL,
 		};
-	pid_t gpid;
 	pid_t pid1a_1, pid1a_2, pid1a_3, pid2a, pid3a, pid4a;
 	pid_t pid1s_1, pid1s_2, pid1s_3, pid2s, pid3s, pid4s;
 
@@ -263,8 +257,6 @@ test_nm_utils_kill_child (void)
 	const int expected_signal_TERM = SIGTERM;
 	const int expected_signal_KILL = SIGKILL;
 
-	gpid = test_nm_utils_kill_child_create_and_join_pgroup ();
-
 	test_nm_utils_kill_child_spawn (argv_watchdog, FALSE);
 
 	pid1s_1 = test_nm_utils_kill_child_spawn (argv1, TRUE);
@@ -284,92 +276,124 @@ test_nm_utils_kill_child (void)
 	/* give processes time to start (and potentially block signals) ... */
 	g_usleep (G_USEC_PER_SEC / 10);
 
-
 	fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-1-1' (*): waiting up to 3000 milliseconds for process to terminate normally after sending SIGTERM (15)...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-1-1' (*): after sending SIGTERM (15), process * exited by signal 15 (* usec elapsed)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-1-1' (*): waiting up to 3000 milliseconds for process to terminate normally after sending SIGTERM (15)...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-1-1' (*): after sending SIGTERM (15), process * exited by signal 15 (* usec elapsed)");
 	test_nm_utils_kill_child_sync_do ("test-s-1-1", pid1s_1, SIGTERM, 3000, TRUE,  &expected_signal_TERM);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-1-2' (*): waiting for process to terminate after sending SIGKILL (9)...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-1-2' (*): after sending SIGKILL (9), process * exited by signal 9 (* usec elapsed)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-1-2' (*): waiting for process to terminate after sending SIGKILL (9)...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-1-2' (*): after sending SIGKILL (9), process * exited by signal 9 (* usec elapsed)");
 	test_nm_utils_kill_child_sync_do ("test-s-1-2", pid1s_2, SIGKILL, 1000 / 2, TRUE,  &expected_signal_KILL);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-1-3' (*): waiting up to 1 milliseconds for process to terminate normally after sending no signal (0)...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-1-3' (*): sending SIGKILL...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-1-3' (*): after sending no signal (0) and SIGKILL, process * exited by signal 9 (* usec elapsed)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-1-3' (*): waiting up to 1 milliseconds for process to terminate normally after sending no signal (0)...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-1-3' (*): sending SIGKILL...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-1-3' (*): after sending no signal (0) and SIGKILL, process * exited by signal 9 (* usec elapsed)");
 	test_nm_utils_kill_child_sync_do ("test-s-1-3", pid1s_3, 0, 1, TRUE,  &expected_signal_KILL);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-2' (*): process * already terminated normally with status 47");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-2' (*): process * already terminated normally with status 47");
 	test_nm_utils_kill_child_sync_do ("test-s-2", pid2s, SIGTERM, 3000, TRUE,  &expected_exit_47);
 
 	/* send invalid signal. */
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*kill child process 'test-s-3-0' (*): failed to send Unexpected signal: Invalid argument (22)");
+	NMTST_EXPECT_NM_ERROR ("kill child process 'test-s-3-0' (*): failed to send Unexpected signal: Invalid argument (22)");
 	test_nm_utils_kill_child_sync_do ("test-s-3-0", pid3s, -1, 0, FALSE, NULL);
 
 	/* really kill pid3s */
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-3-1' (*): waiting up to 3000 milliseconds for process to terminate normally after sending SIGTERM (15)...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-3-1' (*): after sending SIGTERM (15), process * exited normally with status 47 (* usec elapsed)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-3-1' (*): waiting up to 3000 milliseconds for process to terminate normally after sending SIGTERM (15)...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-3-1' (*): after sending SIGTERM (15), process * exited normally with status 47 (* usec elapsed)");
 	test_nm_utils_kill_child_sync_do ("test-s-3-1", pid3s, SIGTERM, 3000, TRUE,  &expected_exit_47);
 
 	/* pid3s should not be a valid process, hence the call should fail. Note, that there
 	 * is a race here. */
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*kill child process 'test-s-3-2' (*): failed due to unexpected return value -1 by waitpid (No child processes, 10) after sending no signal (0)");
+	NMTST_EXPECT_NM_ERROR ("kill child process 'test-s-3-2' (*): failed due to unexpected return value -1 by waitpid (No child processes, 10) after sending no signal (0)");
 	test_nm_utils_kill_child_sync_do ("test-s-3-2", pid3s, 0, 0, FALSE, NULL);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-4' (*): waiting up to 1 milliseconds for process to terminate normally after sending SIGTERM (15)...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-4' (*): sending SIGKILL...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-s-4' (*): after sending SIGTERM (15) and SIGKILL, process * exited by signal 9 (* usec elapsed)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-4' (*): waiting up to 1 milliseconds for process to terminate normally after sending SIGTERM (15)...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-4' (*): sending SIGKILL...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-s-4' (*): after sending SIGTERM (15) and SIGKILL, process * exited by signal 9 (* usec elapsed)");
 	test_nm_utils_kill_child_sync_do ("test-s-4", pid4s, SIGTERM, 1, TRUE, &expected_signal_KILL);
 
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-1-1' (*): wait for process to terminate after sending SIGTERM (15) (send SIGKILL in 3000 milliseconds)...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-1-1' (*): terminated by signal 15 (* usec elapsed)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-1-1' (*): wait for process to terminate after sending SIGTERM (15) (send SIGKILL in 3000 milliseconds)...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-1-1' (*): terminated by signal 15 (* usec elapsed)");
 	test_nm_utils_kill_child_async_do ("test-a-1-1", pid1a_1, SIGTERM, 3000, TRUE, &expected_signal_TERM);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-1-2' (*): wait for process to terminate after sending SIGKILL (9)...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-1-2' (*): terminated by signal 9 (* usec elapsed)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-1-2' (*): wait for process to terminate after sending SIGKILL (9)...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-1-2' (*): terminated by signal 9 (* usec elapsed)");
 	test_nm_utils_kill_child_async_do ("test-a-1-2", pid1a_2, SIGKILL, 1000 / 2, TRUE, &expected_signal_KILL);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-1-3' (*): wait for process to terminate after sending no signal (0) (send SIGKILL in 1 milliseconds)...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-1-3' (*): process not terminated after * usec. Sending SIGKILL signal");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-1-3' (*): terminated by signal 9 (* usec elapsed)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-1-3' (*): wait for process to terminate after sending no signal (0) (send SIGKILL in 1 milliseconds)...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-1-3' (*): process not terminated after * usec. Sending SIGKILL signal");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-1-3' (*): terminated by signal 9 (* usec elapsed)");
 	test_nm_utils_kill_child_async_do ("test-a-1-3", pid1a_3, 0, 1, TRUE, &expected_signal_KILL);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-2' (*): process * already terminated normally with status 47");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-2' (*): invoke callback: terminated normally with status 47");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-2' (*): process * already terminated normally with status 47");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-2' (*): invoke callback: terminated normally with status 47");
 	test_nm_utils_kill_child_async_do ("test-a-2", pid2a, SIGTERM, 3000, TRUE, &expected_exit_47);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*kill child process 'test-a-3-0' (*): unexpected error sending Unexpected signal: Invalid argument (22)");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-3-0' (*): invoke callback: killing child failed");
+	NMTST_EXPECT_NM_ERROR ("kill child process 'test-a-3-0' (*): unexpected error sending Unexpected signal: Invalid argument (22)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-3-0' (*): invoke callback: killing child failed");
 	/* coverity[negative_returns] */
 	test_nm_utils_kill_child_async_do ("test-a-3-0", pid3a, -1, 1000 / 2, FALSE, NULL);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-3-1' (*): wait for process to terminate after sending SIGTERM (15) (send SIGKILL in 3000 milliseconds)...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-3-1' (*): terminated normally with status 47 (* usec elapsed)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-3-1' (*): wait for process to terminate after sending SIGTERM (15) (send SIGKILL in 3000 milliseconds)...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-3-1' (*): terminated normally with status 47 (* usec elapsed)");
 	test_nm_utils_kill_child_async_do ("test-a-3-1", pid3a, SIGTERM, 3000, TRUE, &expected_exit_47);
 
 	/* pid3a should not be a valid process, hence the call should fail. Note, that there
 	 * is a race here. */
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*kill child process 'test-a-3-2' (*): failed due to unexpected return value -1 by waitpid (No child processes, 10) after sending no signal (0)");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-3-2' (*): invoke callback: killing child failed");
+	NMTST_EXPECT_NM_ERROR ("kill child process 'test-a-3-2' (*): failed due to unexpected return value -1 by waitpid (No child processes, 10) after sending no signal (0)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-3-2' (*): invoke callback: killing child failed");
 	test_nm_utils_kill_child_async_do ("test-a-3-2", pid3a, 0, 0, FALSE, NULL);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-4' (*): wait for process to terminate after sending SIGTERM (15) (send SIGKILL in 1 milliseconds)...");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-4' (*): process not terminated after * usec. Sending SIGKILL signal");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_DEBUG, "*kill child process 'test-a-4' (*): terminated by signal 9 (* usec elapsed)");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-4' (*): wait for process to terminate after sending SIGTERM (15) (send SIGKILL in 1 milliseconds)...");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-4' (*): process not terminated after * usec. Sending SIGKILL signal");
+	NMTST_EXPECT_NM_DEBUG ("kill child process 'test-a-4' (*): terminated by signal 9 (* usec elapsed)");
 	test_nm_utils_kill_child_async_do ("test-a-4", pid4a, SIGTERM, 1, TRUE, &expected_signal_KILL);
 
-	err = setpgid (0, 0);
-	g_assert (err == 0);
-
-	kill (-gpid, SIGKILL);
-
 	g_log_set_always_fatal (fatal_mask);
 
 	g_test_assert_expected_messages ();
 }
 
+static void
+test_nm_utils_kill_child (void)
+{
+	int err;
+	int exit_status;
+	pid_t gpid;
+	pid_t child_pid;
+
+	/* the tests spawns several processes, we want to clean them up
+	 * by sending a SIGKILL to the process group.
+	 *
+	 * The current process might be a session leader, which prevents it from
+	 * creating a new process group. Hence, first fork and let the child
+	 * create a new process group, run the tests, and kill all pending
+	 * processes. */
+	child_pid = fork ();
+	g_assert (child_pid >= 0);
+
+	if (child_pid == 0) {
+		gpid = do_test_nm_utils_kill_child_create_and_join_pgroup ();
+
+		do_test_nm_utils_kill_child ();
+
+		err = setpgid (0, 0);
+		g_assert (err == 0);
+
+		kill (-gpid, SIGKILL);
+
+		exit (0);
+	};
+
+	do {
+		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);
+}
+
 /*****************************************************************************/
 
 static void
@@ -432,7 +456,7 @@ test_nm_utils_array_remove_at_indexes (void)
 
 	idx = g_array_new (FALSE, FALSE, sizeof (guint));
 	array = g_array_new (FALSE, FALSE, sizeof (gssize));
-	unique = g_hash_table_new (NULL, NULL);
+	unique = g_hash_table_new (nm_direct_hash, NULL);
 	for (i_len = 1; i_len < 20; i_len++) {
 		for (i_idx_len = 1; i_idx_len <= i_len; i_idx_len++) {
 			for (i_rnd = 0; i_rnd < 20; i_rnd++) {
diff --git a/src/tests/test-general.c b/src/tests/test-general.c
index bfa2ea73..38eaf25e 100644
--- a/src/tests/test-general.c
+++ b/src/tests/test-general.c
@@ -232,13 +232,13 @@ test_nm_utils_log_connection_diff (void)
 
 	connection = nm_simple_connection_new ();
 	nm_connection_add_setting (connection, nm_setting_connection_new ());
-	nm_utils_log_connection_diff (connection, NULL, LOGL_DEBUG, LOGD_CORE, "test1", ">>> ");
+	nm_utils_log_connection_diff (connection, NULL, LOGL_DEBUG, LOGD_CORE, "test1", ">>> ", NULL);
 
 	nm_connection_add_setting (connection, nm_setting_wired_new ());
-	nm_utils_log_connection_diff (connection, NULL, LOGL_DEBUG, LOGD_CORE, "test2", ">>> ");
+	nm_utils_log_connection_diff (connection, NULL, LOGL_DEBUG, LOGD_CORE, "test2", ">>> ", NULL);
 
 	connection2 = nm_simple_connection_new_clone (connection);
-	nm_utils_log_connection_diff (connection, connection2, LOGL_DEBUG, LOGD_CORE, "test3", ">>> ");
+	nm_utils_log_connection_diff (connection, connection2, LOGL_DEBUG, LOGD_CORE, "test3", ">>> ", NULL);
 
 	g_object_set (nm_connection_get_setting_connection (connection),
 	              NM_SETTING_CONNECTION_ID, "id",
@@ -248,24 +248,24 @@ test_nm_utils_log_connection_diff (void)
 	              NM_SETTING_CONNECTION_ID, "id2",
 	              NM_SETTING_CONNECTION_MASTER, "master2",
 	              NULL);
-	nm_utils_log_connection_diff (connection, connection2, LOGL_DEBUG, LOGD_CORE, "test4", ">>> ");
+	nm_utils_log_connection_diff (connection, connection2, LOGL_DEBUG, LOGD_CORE, "test4", ">>> ", NULL);
 
 	nm_connection_add_setting (connection, nm_setting_802_1x_new ());
-	nm_utils_log_connection_diff (connection, connection2, LOGL_DEBUG, LOGD_CORE, "test5", ">>> ");
+	nm_utils_log_connection_diff (connection, connection2, LOGL_DEBUG, LOGD_CORE, "test5", ">>> ", NULL);
 
 	g_object_set (nm_connection_get_setting_802_1x (connection),
 	              NM_SETTING_802_1X_PASSWORD, "id2",
 	              NM_SETTING_802_1X_PASSWORD_FLAGS, NM_SETTING_SECRET_FLAG_NOT_SAVED,
 	              NULL);
-	nm_utils_log_connection_diff (connection, NULL, LOGL_DEBUG, LOGD_CORE, "test6", ">>> ");
-	nm_utils_log_connection_diff (connection, connection2, LOGL_DEBUG, LOGD_CORE, "test7", ">>> ");
-	nm_utils_log_connection_diff (connection2, connection, LOGL_DEBUG, LOGD_CORE, "test8", ">>> ");
+	nm_utils_log_connection_diff (connection, NULL, LOGL_DEBUG, LOGD_CORE, "test6", ">>> ", NULL);
+	nm_utils_log_connection_diff (connection, connection2, LOGL_DEBUG, LOGD_CORE, "test7", ">>> ", NULL);
+	nm_utils_log_connection_diff (connection2, connection, LOGL_DEBUG, LOGD_CORE, "test8", ">>> ", NULL);
 
 	g_clear_object (&connection);
 	g_clear_object (&connection2);
 
 	connection = nmtst_create_minimal_connection ("id-vpn-1", NULL, NM_SETTING_VPN_SETTING_NAME, NULL);
-	nm_utils_log_connection_diff (connection, NULL, LOGL_DEBUG, LOGD_CORE, "test-vpn-1", ">>> ");
+	nm_utils_log_connection_diff (connection, NULL, LOGL_DEBUG, LOGD_CORE, "test-vpn-1", ">>> ", NULL);
 
 	g_clear_object (&connection);
 }
@@ -1460,6 +1460,11 @@ test_nm_utils_strbuf_append (void)
 static void
 test_duplicate_decl_specifier (void)
 {
+	/* We're intentionally assigning values to static arrays v_const
+	 * and v_result without using it afterwards just so that valgrind
+	 * doesn't complain about the leak. */
+	NM_PRAGMA_WARNING_DISABLE("-Wunused-but-set-variable")
+
 	/* have some static variables, so that the result is certainly not optimized out. */
 	static const int v_const[1] = { 1 };
 	static int v_result[1] = { };
@@ -1477,6 +1482,8 @@ test_duplicate_decl_specifier (void)
 	})
 
 	v_result[0] = TEST_MAX (v_const[0], nmtst_get_rand_int () % 5) + v2;
+
+	NM_PRAGMA_WARNING_REENABLE
 }
 
 static void
diff --git a/src/tests/test-ip6-config.c b/src/tests/test-ip6-config.c
index bcbeee3e..816a816f 100644
--- a/src/tests/test-ip6-config.c
+++ b/src/tests/test-ip6-config.c
@@ -246,8 +246,10 @@ test_nm_ip6_config_addresses_sort_check (NMIP6Config *config, NMSettingIP6Config
 	int *idx = g_new (int, addr_count);
 
 	nm_ip6_config_set_privacy (config, use_tempaddr);
-	copy = nmtst_ip6_config_clone (config);
-	copy2 = nmtst_ip6_config_clone (config);
+	copy = nm_ip6_config_clone (config);
+	g_assert (copy);
+	copy2 = nm_ip6_config_clone (config);
+	g_assert (copy2);
 
 	/* initialize the array of indeces, and keep shuffling them for every @repeat iteration. */
 	for (i = 0; i < addr_count; i++)
@@ -296,9 +298,9 @@ test_nm_ip6_config_addresses_sort (void)
 	ADDR_ADD("2607:f0d0:1002:51::4",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, 0);
 	ADDR_ADD("2607:f0d0:1002:51::5",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, 0);
 	ADDR_ADD("2607:f0d0:1002:51::6",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_NDISC,  0, 0, 0, IFA_F_MANAGETEMPADDR);
-	ADDR_ADD("2607:f0d0:1002:51::3",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, IFA_F_SECONDARY);
-	ADDR_ADD("2607:f0d0:1002:51::8",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, IFA_F_SECONDARY);
-	ADDR_ADD("2607:f0d0:1002:51::0",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, IFA_F_SECONDARY);
+	ADDR_ADD("2607:f0d0:1002:51::3",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, IFA_F_TEMPORARY);
+	ADDR_ADD("2607:f0d0:1002:51::8",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, IFA_F_TEMPORARY);
+	ADDR_ADD("2607:f0d0:1002:51::0",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, IFA_F_TEMPORARY);
 	ADDR_ADD("fec0::1",                  NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0);
 	ADDR_ADD("fe80::208:74ff:feda:625c", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0);
 	ADDR_ADD("fe80::208:74ff:feda:625d", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0);
@@ -309,11 +311,11 @@ test_nm_ip6_config_addresses_sort (void)
 	test_nm_ip6_config_addresses_sort_check (config, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR, 8);
 
 	nm_ip6_config_reset_addresses (config);
-	ADDR_ADD("2607:f0d0:1002:51::3",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, IFA_F_SECONDARY);
+	ADDR_ADD("2607:f0d0:1002:51::3",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, IFA_F_TEMPORARY);
 	ADDR_ADD("2607:f0d0:1002:51::4",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, 0);
 	ADDR_ADD("2607:f0d0:1002:51::5",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, 0);
-	ADDR_ADD("2607:f0d0:1002:51::8",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, IFA_F_SECONDARY);
-	ADDR_ADD("2607:f0d0:1002:51::0",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, IFA_F_SECONDARY);
+	ADDR_ADD("2607:f0d0:1002:51::8",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_USER,   0, 0, 0, IFA_F_TEMPORARY);
+	ADDR_ADD("2607:f0d0:1002:51::0",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, IFA_F_TEMPORARY);
 	ADDR_ADD("2607:f0d0:1002:51::6",     NULL,  64, 0, NM_IP_CONFIG_SOURCE_NDISC,  0, 0, 0, IFA_F_MANAGETEMPADDR);
 	ADDR_ADD("fec0::1",                  NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0);
 	ADDR_ADD("fe80::208:74ff:feda:625c", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0);
diff --git a/src/tests/test-resolvconf-capture.c b/src/tests/test-resolvconf-capture.c
deleted file mode 100644
index 2c34ff74..00000000
--- a/src/tests/test-resolvconf-capture.c
+++ /dev/null
@@ -1,298 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2013 Red Hat, Inc.
- *
- */
-
-#include "nm-default.h"
-
-#include <string.h>
-#include <arpa/inet.h>
-
-#include "NetworkManagerUtils.h"
-#include "nm-ip4-config.h"
-#include "nm-ip6-config.h"
-#include "platform/nm-platform.h"
-
-#include "nm-test-utils-core.h"
-
-static void
-test_capture_empty (void)
-{
-	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
-	GArray *ns6 = g_array_new (FALSE, FALSE, sizeof (struct in6_addr));
-
-	g_assert (!nm_utils_resolve_conf_parse (AF_INET, "", ns4, NULL));
-	g_assert_cmpint (ns4->len, ==, 0);
-
-	g_assert (!nm_utils_resolve_conf_parse (AF_INET6, "", ns6, NULL));
-	g_assert_cmpint (ns6->len, ==, 0);
-
-	g_array_free (ns4, TRUE);
-	g_array_free (ns6, TRUE);
-}
-
-#define assert_dns4_entry(a, i, s) \
-	g_assert_cmpint ((g_array_index ((a), guint32, (i))), ==, nmtst_inet4_from_string (s));
-
-#define assert_dns6_entry(a, i, s) \
-	g_assert (IN6_ARE_ADDR_EQUAL (&g_array_index ((a), struct in6_addr, (i)), nmtst_inet6_from_string (s)))
-
-#define assert_dns_option(a, i, s) \
-	g_assert_cmpstr ((a)->pdata[(i)], ==, (s));
-
-static void
-test_capture_basic4 (void)
-{
-	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
-	const char *rc =
-"# neato resolv.conf\r\n"
-"domain foobar.com\r\n"
-"search foobar.com\r\n"
-"nameserver 4.2.2.1\r\n"
-"nameserver 4.2.2.2\r\n";
-
-	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, NULL));
-	g_assert_cmpint (ns4->len, ==, 2);
-	assert_dns4_entry (ns4, 0, "4.2.2.1");
-	assert_dns4_entry (ns4, 1, "4.2.2.2");
-
-	g_array_free (ns4, TRUE);
-}
-
-static void
-test_capture_dup4 (void)
-{
-	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
-	const char *rc =
-"# neato resolv.conf\r\n"
-"domain foobar.com\r\n"
-"search foobar.com\r\n"
-"nameserver 4.2.2.1\r\n"
-"nameserver 4.2.2.1\r\n"
-"nameserver 4.2.2.2\r\n";
-
-	/* Check that duplicates are ignored */
-	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, NULL));
-	g_assert_cmpint (ns4->len, ==, 2);
-	assert_dns4_entry (ns4, 0, "4.2.2.1");
-	assert_dns4_entry (ns4, 1, "4.2.2.2");
-
-	g_array_free (ns4, TRUE);
-}
-
-static void
-test_capture_basic6 (void)
-{
-	GArray *ns6 = g_array_new (FALSE, FALSE, sizeof (struct in6_addr));
-	const char *rc =
-"# neato resolv.conf\r\n"
-"domain foobar.com\r\n"
-"search foobar.com\r\n"
-"nameserver 2001:4860:4860::8888\r\n"
-"nameserver 2001:4860:4860::8844\r\n";
-
-	g_assert (nm_utils_resolve_conf_parse (AF_INET6, rc, ns6, NULL));
-	g_assert_cmpint (ns6->len, ==, 2);
-	assert_dns6_entry (ns6, 0, "2001:4860:4860::8888");
-	assert_dns6_entry (ns6, 1, "2001:4860:4860::8844");
-
-	g_array_free (ns6, TRUE);
-}
-
-static void
-test_capture_dup6 (void)
-{
-	GArray *ns6 = g_array_new (FALSE, FALSE, sizeof (struct in6_addr));
-	const char *rc =
-"# neato resolv.conf\r\n"
-"domain foobar.com\r\n"
-"search foobar.com\r\n"
-"nameserver 2001:4860:4860::8888\r\n"
-"nameserver 2001:4860:4860::8888\r\n"
-"nameserver 2001:4860:4860::8844\r\n";
-
-	/* Check that duplicates are ignored */
-	g_assert (nm_utils_resolve_conf_parse (AF_INET6, rc, ns6, NULL));
-	g_assert_cmpint (ns6->len, ==, 2);
-	assert_dns6_entry (ns6, 0, "2001:4860:4860::8888");
-	assert_dns6_entry (ns6, 1, "2001:4860:4860::8844");
-
-	g_array_free (ns6, TRUE);
-}
-
-static void
-test_capture_addr4_with_6 (void)
-{
-	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
-	const char *rc =
-"# neato resolv.conf\r\n"
-"domain foobar.com\r\n"
-"search foobar.com\r\n"
-"nameserver 4.2.2.1\r\n"
-"nameserver 4.2.2.2\r\n"
-"nameserver 2001:4860:4860::8888\r\n";
-
-	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, NULL));
-	g_assert_cmpint (ns4->len, ==, 2);
-	assert_dns4_entry (ns4, 0, "4.2.2.1");
-	assert_dns4_entry (ns4, 1, "4.2.2.2");
-
-	g_array_free (ns4, TRUE);
-}
-
-static void
-test_capture_addr6_with_4 (void)
-{
-	GArray *ns6 = g_array_new (FALSE, FALSE, sizeof (struct in6_addr));
-	const char *rc =
-"# neato resolv.conf\r\n"
-"domain foobar.com\r\n"
-"search foobar.com\r\n"
-"nameserver 4.2.2.1\r\n"
-"nameserver 2001:4860:4860::8888\r\n"
-"nameserver 2001:4860:4860::8844\r\n";
-
-	g_assert (nm_utils_resolve_conf_parse (AF_INET6, rc, ns6, NULL));
-	g_assert_cmpint (ns6->len, ==, 2);
-	assert_dns6_entry (ns6, 0, "2001:4860:4860::8888");
-	assert_dns6_entry (ns6, 1, "2001:4860:4860::8844");
-
-	g_array_free (ns6, TRUE);
-}
-
-static void
-test_capture_format (void)
-{
-	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
-	const char *rc =
-" nameserver 4.2.2.1\r\n"     /* bad */
-"nameserver4.2.2.1\r\n"       /* bad */
-"nameserver     4.2.2.3\r"    /* good */
-"nameserver\t\t4.2.2.4\r\n"   /* good */
-"nameserver  4.2.2.5\t\t\r\n" /* good */
-"nameserver  4.2.2.6   \r\n"; /* good */
-
-	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, NULL));
-	g_assert_cmpint (ns4->len, ==, 4);
-	assert_dns4_entry (ns4, 0, "4.2.2.3");
-	assert_dns4_entry (ns4, 1, "4.2.2.4");
-	assert_dns4_entry (ns4, 2, "4.2.2.5");
-	assert_dns4_entry (ns4, 3, "4.2.2.6");
-
-	g_array_free (ns4, TRUE);
-}
-
-static void
-test_capture_dns_options (void)
-{
-	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
-	GPtrArray *dns_options = g_ptr_array_new_with_free_func (g_free);
-	const char *rc =
-"nameserver 4.2.2.1\r\n"
-"options debug rotate  timeout:5 \r\n"
-"options edns0\r\n";
-
-	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, dns_options));
-	g_assert_cmpint (dns_options->len, ==, 4);
-	assert_dns_option (dns_options, 0, "debug");
-	assert_dns_option (dns_options, 1, "rotate");
-	assert_dns_option (dns_options, 2, "timeout:5");
-	assert_dns_option (dns_options, 3, "edns0");
-
-	g_array_free (ns4, TRUE);
-	g_ptr_array_free (dns_options, TRUE);
-}
-
-static void
-test_capture_dns_options_dup (void)
-{
-	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
-	GPtrArray *dns_options = g_ptr_array_new_with_free_func (g_free);
-	const char *rc =
-"options debug rotate timeout:3\r\n"
-"options edns0 debug\r\n"
-"options timeout:5\r\n";
-
-	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, dns_options));
-	g_assert_cmpint (dns_options->len, ==, 4);
-	assert_dns_option (dns_options, 0, "debug");
-	assert_dns_option (dns_options, 1, "rotate");
-	assert_dns_option (dns_options, 2, "timeout:3");
-	assert_dns_option (dns_options, 3, "edns0");
-
-	g_array_free (ns4, TRUE);
-	g_ptr_array_free (dns_options, TRUE);
-}
-
-static void
-test_capture_dns_options_valid4 (void)
-{
-	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
-	GPtrArray *dns_options = g_ptr_array_new_with_free_func (g_free);
-	const char *rc =
-"options debug: rotate:yes edns0 foobar : inet6\r\n";
-
-	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, dns_options));
-	g_assert_cmpint (dns_options->len, ==, 1);
-	assert_dns_option (dns_options, 0, "edns0");
-
-	g_array_free (ns4, TRUE);
-	g_ptr_array_free (dns_options, TRUE);
-}
-
-static void
-test_capture_dns_options_valid6 (void)
-{
-	GArray *ns6 = g_array_new (FALSE, FALSE, sizeof (struct in6_addr));
-	GPtrArray *dns_options = g_ptr_array_new_with_free_func (g_free);
-	const char *rc =
-"options inet6 debug foobar rotate:\r\n";
-
-	g_assert (nm_utils_resolve_conf_parse (AF_INET6, rc, ns6, dns_options));
-	g_assert_cmpint (dns_options->len, ==, 2);
-	assert_dns_option (dns_options, 0, "inet6");
-	assert_dns_option (dns_options, 1, "debug");
-
-	g_array_free (ns6, TRUE);
-	g_ptr_array_free (dns_options, TRUE);
-}
-/*****************************************************************************/
-
-NMTST_DEFINE ();
-
-int
-main (int argc, char **argv)
-{
-	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
-
-	g_test_add_func ("/resolvconf-capture/empty", test_capture_empty);
-	g_test_add_func ("/resolvconf-capture/basic4", test_capture_basic4);
-	g_test_add_func ("/resolvconf-capture/dup4", test_capture_dup4);
-	g_test_add_func ("/resolvconf-capture/basic6", test_capture_basic6);
-	g_test_add_func ("/resolvconf-capture/dup6", test_capture_dup6);
-	g_test_add_func ("/resolvconf-capture/addr4-with-6", test_capture_addr4_with_6);
-	g_test_add_func ("/resolvconf-capture/addr6-with-4", test_capture_addr6_with_4);
-	g_test_add_func ("/resolvconf-capture/format", test_capture_format);
-	g_test_add_func ("/resolvconf-capture/dns-options", test_capture_dns_options);
-	g_test_add_func ("/resolvconf-capture/dns-options-dup", test_capture_dns_options_dup);
-	g_test_add_func ("/resolvconf-capture/dns-options-valid4", test_capture_dns_options_valid4);
-	g_test_add_func ("/resolvconf-capture/dns-options-valid6", test_capture_dns_options_valid6);
-
-	return g_test_run ();
-}
-
diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c
index 6c600c46..436fc68b 100644
--- a/src/vpn/nm-vpn-connection.c
+++ b/src/vpn/nm-vpn-connection.c
@@ -51,8 +51,6 @@
 #include "nm-vpn-manager.h"
 #include "dns/nm-dns-manager.h"
 
-#include "introspection/org.freedesktop.NetworkManager.VPN.Connection.h"
-
 typedef enum {
 	/* Only system secrets */
 	SECRETS_REQ_SYSTEM = 0,
@@ -82,7 +80,6 @@ typedef enum {
 } VpnState;
 
 enum {
-	VPN_STATE_CHANGED,
 	INTERNAL_STATE_CHANGED,
 	INTERNAL_RETRY_AFTER_FAILURE,
 
@@ -159,19 +156,6 @@ struct _NMVpnConnection {
 
 struct _NMVpnConnectionClass {
 	NMActiveConnectionClass parent;
-
-	/* Signals */
-	void (*vpn_state_changed) (NMVpnConnection *self,
-	                           NMVpnConnectionState new_state,
-	                           NMActiveConnectionStateReason reason);
-
-	/* not exported over D-Bus */
-	void (*internal_state_changed) (NMVpnConnection *self,
-	                                NMVpnConnectionState new_state,
-	                                NMVpnConnectionState old_state,
-	                                NMActiveConnectionStateReason reason);
-
-	void (*internal_failed_retry)  (NMVpnConnection *self);
 };
 
 G_DEFINE_TYPE (NMVpnConnection, nm_vpn_connection, NM_TYPE_ACTIVE_CONNECTION)
@@ -180,6 +164,9 @@ G_DEFINE_TYPE (NMVpnConnection, nm_vpn_connection, NM_TYPE_ACTIVE_CONNECTION)
 
 /*****************************************************************************/
 
+static const NMDBusInterfaceInfoExtended interface_info_vpn_connection;
+static const GDBusSignalInfo signal_info_vpn_state_changed;
+
 static NMSettingsConnection *_get_settings_connection (NMVpnConnection *self,
                                                        gboolean allow_missing);
 
@@ -511,7 +498,12 @@ _set_vpn_state (NMVpnConnection *self,
 	old_external_state = _state_to_nm_vpn_state (old_vpn_state);
 	new_external_state = _state_to_nm_vpn_state (priv->vpn_state);
 	if (new_external_state != old_external_state) {
-		g_signal_emit (self, signals[VPN_STATE_CHANGED], 0, new_external_state, reason);
+		nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
+		                            &interface_info_vpn_connection,
+		                            &signal_info_vpn_state_changed,
+		                            "(uu)",
+		                            (guint32) new_external_state,
+		                            (guint32) reason);
 		g_signal_emit (self, signals[INTERNAL_STATE_CHANGED], 0,
 		               new_external_state,
 		               old_external_state,
@@ -858,16 +850,19 @@ NMVpnConnection *
 nm_vpn_connection_new (NMSettingsConnection *settings_connection,
                        NMDevice *parent_device,
                        const char *specific_object,
+                       NMActivationReason activation_reason,
                        NMAuthSubject *subject)
 {
 	g_return_val_if_fail (!settings_connection || NM_IS_SETTINGS_CONNECTION (settings_connection), NULL);
 	g_return_val_if_fail (NM_IS_DEVICE (parent_device), NULL);
+	g_return_val_if_fail (specific_object, NULL);
 
 	return (NMVpnConnection *) g_object_new (NM_TYPE_VPN_CONNECTION,
 	                                         NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION, settings_connection,
 	                                         NM_ACTIVE_CONNECTION_INT_DEVICE, parent_device,
 	                                         NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT, specific_object,
 	                                         NM_ACTIVE_CONNECTION_INT_SUBJECT, subject,
+	                                         NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON, activation_reason,
 	                                         NM_ACTIVE_CONNECTION_VPN, TRUE,
 	                                         NULL);
 }
@@ -1176,8 +1171,8 @@ _cleanup_failed_config (NMVpnConnection *self)
 {
 	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
-	nm_exported_object_clear_and_unexport (&priv->ip4_config);
-	nm_exported_object_clear_and_unexport (&priv->ip6_config);
+	nm_dbus_object_clear_and_unexport (&priv->ip4_config);
+	nm_dbus_object_clear_and_unexport (&priv->ip6_config);
 
 	_LOGW ("VPN connection: did not receive valid IP config information");
 	_set_vpn_state (self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID, FALSE);
@@ -1394,12 +1389,12 @@ nm_vpn_connection_config_get (NMVpnConnection *self, GVariant *dict)
 	priv->has_ip4 = FALSE;
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_CONFIG_HAS_IP4, "b", &b))
 		priv->has_ip4 = b;
-	nm_exported_object_clear_and_unexport (&priv->ip4_config);
+	nm_dbus_object_clear_and_unexport (&priv->ip4_config);
 
 	priv->has_ip6 = FALSE;
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_CONFIG_HAS_IP6, "b", &b))
 		priv->has_ip6 = b;
-	nm_exported_object_clear_and_unexport (&priv->ip6_config);
+	nm_dbus_object_clear_and_unexport (&priv->ip6_config);
 
 	nm_vpn_connection_config_maybe_complete (self, TRUE);
 }
@@ -1460,6 +1455,7 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 	NMPlatformIP4Address address;
 	guint32 u32, route_metric;
 	NMSettingIPConfig *s_ip;
+	NMSettingConnection *s_con;
 	guint32 route_table;
 	NMIP4Config *config;
 	GVariantIter *iter;
@@ -1564,6 +1560,7 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 	route_table = get_route_table (self, AF_INET, TRUE);
 	route_metric = nm_vpn_connection_get_ip4_route_metric (self);
 	s_ip = nm_connection_get_setting_ip4_config (_get_applied_connection (self));
+	s_con = nm_connection_get_setting_connection (_get_applied_connection (self));
 
 	if (nm_setting_ip_config_get_ignore_auto_routes (s_ip)) {
 		/* ignore VPN routes */
@@ -1621,6 +1618,7 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 	/* Merge in user overrides from the NMConnection's IPv4 setting */
 	nm_ip4_config_merge_setting (config,
 	                             s_ip,
+	                             nm_setting_connection_get_mdns (s_con),
 	                             route_table,
 	                             route_metric);
 
@@ -1650,7 +1648,7 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 		g_object_unref (config);
 	} else {
 		priv->ip4_config = config;
-		nm_exported_object_export (NM_EXPORTED_OBJECT (config));
+		nm_dbus_object_export (NM_DBUS_OBJECT (config));
 		g_object_notify ((GObject *) self, NM_ACTIVE_CONNECTION_IP4_CONFIG);
 	}
 
@@ -1839,7 +1837,7 @@ next:
 		g_object_unref (config);
 	} else {
 		priv->ip6_config = config;
-		nm_exported_object_export (NM_EXPORTED_OBJECT (config));
+		nm_dbus_object_export (NM_DBUS_OBJECT (config));
 		g_object_notify ((GObject *) self, NM_ACTIVE_CONNECTION_IP6_CONFIG);
 	}
 
@@ -2326,7 +2324,7 @@ nm_vpn_connection_activate (NMVpnConnection *self,
 	if (nm_vpn_plugin_info_supports_multiple (plugin_info)) {
 		const char *path;
 
-		path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (self));
+		path = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
 		if (path)
 			path = strrchr (path, '/');
 		g_return_if_fail (path);
@@ -2549,7 +2547,6 @@ static void
 plugin_new_secrets_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 {
 	NMVpnConnection *self;
-	NMVpnConnectionPrivate *priv;
 	gs_unref_variant GVariant *reply = NULL;
 	gs_free_error GError *error = NULL;
 
@@ -2558,7 +2555,6 @@ plugin_new_secrets_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_da
 		return;
 
 	self = NM_VPN_CONNECTION (user_data);
-	priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
 	if (error) {
 		g_dbus_error_strip_remote_error (error);
@@ -2778,8 +2774,8 @@ dispose (GObject *object)
 	nm_clear_g_cancellable (&priv->cancellable);
 
 	g_clear_object (&priv->proxy_config);
-	nm_exported_object_clear_and_unexport (&priv->ip4_config);
-	nm_exported_object_clear_and_unexport (&priv->ip6_config);
+	nm_dbus_object_clear_and_unexport (&priv->ip4_config);
+	nm_dbus_object_clear_and_unexport (&priv->ip6_config);
 	g_clear_object (&priv->proxy);
 	g_clear_object (&priv->plugin_info);
 
@@ -2829,14 +2825,14 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_string (value, priv->banner ? priv->banner : "");
 		break;
 	case PROP_IP4_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->vpn_state) ? priv->ip4_config : NULL);
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->vpn_state) ? priv->ip4_config : NULL);
 		break;
 	case PROP_IP6_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->vpn_state) ? priv->ip6_config : NULL);
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->vpn_state) ? priv->ip6_config : NULL);
 		break;
 	case PROP_MASTER:
 		parent_dev = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (object));
-		nm_utils_g_value_set_object_path (value, parent_dev);
+		nm_dbus_utils_g_value_set_object_path (value, parent_dev);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -2844,16 +2840,42 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
+static const GDBusSignalInfo signal_info_vpn_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"VpnStateChanged",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("state",  "u"),
+		NM_DEFINE_GDBUS_ARG_INFO ("reason", "u"),
+	),
+);
+
+static const NMDBusInterfaceInfoExtended interface_info_vpn_connection = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_VPN_CONNECTION,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+			&signal_info_vpn_state_changed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("VpnState", "u", NM_VPN_CONNECTION_VPN_STATE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Banner",   "s", NM_VPN_CONNECTION_BANNER),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_vpn_connection_class_init (NMVpnConnectionClass *connection_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (connection_class);
 	NMActiveConnectionClass *active_class = NM_ACTIVE_CONNECTION_CLASS (connection_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (connection_class);
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_vpn_connection);
 
-	/* virtual methods */
 	object_class->get_property = get_property;
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
+
 	active_class->device_state_changed = device_state_changed;
 	active_class->device_changed = device_changed;
 
@@ -2880,13 +2902,6 @@ nm_vpn_connection_class_init (NMVpnConnectionClass *connection_class)
 	g_object_class_override_property (object_class, PROP_IP6_CONFIG,
 	                                  NM_ACTIVE_CONNECTION_IP6_CONFIG);
 
-	signals[VPN_STATE_CHANGED] =
-	    g_signal_new ("vpn-state-changed",
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
-
 	signals[INTERNAL_STATE_CHANGED] =
 	    g_signal_new (NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
@@ -2900,8 +2915,4 @@ nm_vpn_connection_class_init (NMVpnConnectionClass *connection_class)
 	                  G_SIGNAL_RUN_FIRST,
 	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 0);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (connection_class),
-	                                        NMDBUS_TYPE_VPN_CONNECTION_SKELETON,
-	                                        NULL);
 }
diff --git a/src/vpn/nm-vpn-connection.h b/src/vpn/nm-vpn-connection.h
index 038d0efd..3046634d 100644
--- a/src/vpn/nm-vpn-connection.h
+++ b/src/vpn/nm-vpn-connection.h
@@ -42,7 +42,6 @@
 #define NM_VPN_CONNECTION_BANNER "banner"
 
 /* Signals */
-/* not exported: includes old reason code */
 #define NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED       "internal-state-changed"
 #define NM_VPN_CONNECTION_INTERNAL_RETRY_AFTER_FAILURE "internal-retry-after-failure"
 
@@ -53,6 +52,7 @@ GType nm_vpn_connection_get_type (void);
 NMVpnConnection * nm_vpn_connection_new (NMSettingsConnection *settings_connection,
                                          NMDevice *parent_device,
                                          const char *specific_object,
+                                         NMActivationReason activation_reason,
                                          NMAuthSubject *subject);
 
 void                 nm_vpn_connection_activate        (NMVpnConnection *self,