summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2019-03-26 23:25:23 +0100
committerMichael Biebl <biebl@debian.org>2019-03-26 23:25:23 +0100
commit9a6dcbf895f9da01768e64b73cec88c16157d91e (patch)
treea359958930d731e9f1b59344642e10754419fe84 /src
parent964ae8cc391520440cf5aa13e2b9cc34850ea6c2 (diff)
New upstream version 1.16.0 upstream/1.16.0
Diffstat (limited to 'src')
-rw-r--r--src/NetworkManagerUtils.c226
-rw-r--r--src/NetworkManagerUtils.h11
-rw-r--r--src/devices/adsl/meson.build6
-rw-r--r--src/devices/adsl/nm-atm-manager.c1
-rw-r--r--src/devices/adsl/nm-device-adsl.c41
-rw-r--r--src/devices/bluetooth/meson.build6
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c182
-rw-r--r--src/devices/bluetooth/nm-bluez-device.h15
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c1
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.c2
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.c1
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.c40
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.h3
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c1
-rw-r--r--src/devices/bluetooth/nm-device-bt.c116
-rw-r--r--src/devices/nm-acd-manager.c419
-rw-r--r--src/devices/nm-acd-manager.h24
-rw-r--r--src/devices/nm-device-6lowpan.c8
-rw-r--r--src/devices/nm-device-bond.c17
-rw-r--r--src/devices/nm-device-bridge.c16
-rw-r--r--src/devices/nm-device-dummy.c9
-rw-r--r--src/devices/nm-device-ethernet.c75
-rw-r--r--src/devices/nm-device-factory.c2
-rw-r--r--src/devices/nm-device-infiniband.c19
-rw-r--r--src/devices/nm-device-ip-tunnel.c51
-rw-r--r--src/devices/nm-device-macsec.c25
-rw-r--r--src/devices/nm-device-macvlan.c9
-rw-r--r--src/devices/nm-device-ppp.c47
-rw-r--r--src/devices/nm-device-private.h90
-rw-r--r--src/devices/nm-device-tun.c20
-rw-r--r--src/devices/nm-device-veth.c2
-rw-r--r--src/devices/nm-device-vlan.c20
-rw-r--r--src/devices/nm-device-vxlan.c28
-rw-r--r--src/devices/nm-device-wireguard.c1501
-rw-r--r--src/devices/nm-device-wpan.c19
-rw-r--r--src/devices/nm-device.c2559
-rw-r--r--src/devices/nm-device.h73
-rw-r--r--src/devices/nm-lldp-listener.c13
-rw-r--r--src/devices/ovs/meson.build6
-rw-r--r--src/devices/ovs/nm-device-ovs-bridge.c18
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c39
-rw-r--r--src/devices/ovs/nm-device-ovs-port.c18
-rw-r--r--src/devices/ovs/nm-ovsdb.c3
-rw-r--r--src/devices/team/meson.build6
-rw-r--r--src/devices/team/nm-device-team.c34
-rw-r--r--src/devices/team/nm-team-factory.c1
-rw-r--r--src/devices/tests/meson.build7
-rw-r--r--src/devices/tests/test-acd.c88
-rw-r--r--src/devices/wifi/meson.build14
-rw-r--r--src/devices/wifi/nm-device-iwd.c1139
-rw-r--r--src/devices/wifi/nm-device-iwd.h2
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c11
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.c1316
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.h50
-rw-r--r--src/devices/wifi/nm-device-wifi.c346
-rw-r--r--src/devices/wifi/nm-device-wifi.h1
-rw-r--r--src/devices/wifi/nm-iwd-manager.c134
-rw-r--r--src/devices/wifi/nm-wifi-ap.c6
-rw-r--r--src/devices/wifi/nm-wifi-factory.c69
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.c796
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.h114
-rw-r--r--src/devices/wifi/nm-wifi-utils.c7
-rw-r--r--src/devices/wifi/tests/meson.build5
-rw-r--r--src/devices/wifi/tests/test-general.c3
-rw-r--r--src/devices/wwan/libnm-wwan.ver1
-rw-r--r--src/devices/wwan/meson.build14
-rw-r--r--src/devices/wwan/nm-device-modem.c115
-rw-r--r--src/devices/wwan/nm-modem-broadband.c118
-rw-r--r--src/devices/wwan/nm-modem-manager.c1
-rw-r--r--src/devices/wwan/nm-modem-ofono.c102
-rw-r--r--src/devices/wwan/nm-modem.c232
-rw-r--r--src/devices/wwan/nm-modem.h22
-rw-r--r--src/devices/wwan/nm-wwan-factory.c1
-rw-r--r--src/dhcp/meson.build2
-rw-r--r--src/dhcp/nm-dhcp-client.c25
-rw-r--r--src/dhcp/nm-dhcp-client.h1
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c5
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c14
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c9
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c9
-rw-r--r--src/dhcp/nm-dhcp-helper.c3
-rw-r--r--src/dhcp/nm-dhcp-listener.c2
-rw-r--r--src/dhcp/nm-dhcp-manager.c4
-rw-r--r--src/dhcp/nm-dhcp-systemd.c625
-rw-r--r--src/dhcp/nm-dhcp-utils.c17
-rw-r--r--src/dhcp/tests/meson.build5
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c5
-rw-r--r--src/dhcp/tests/test-dhcp-utils.c1
-rw-r--r--src/dns/nm-dns-manager.c198
-rw-r--r--src/dns/nm-dns-manager.h2
-rw-r--r--src/dns/nm-dns-plugin.c1
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.c172
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.h1
-rw-r--r--src/dnsmasq/nm-dnsmasq-utils.c1
-rw-r--r--src/dnsmasq/tests/meson.build2
-rw-r--r--src/initrd/meson.build2
-rw-r--r--src/initrd/nm-initrd-generator.c60
-rw-r--r--src/initrd/nm-initrd-generator.h21
-rw-r--r--src/initrd/nmi-cmdline-reader.c76
-rw-r--r--src/initrd/nmi-ibft-reader.c23
-rw-r--r--src/initrd/tests/meson.build4
-rw-r--r--src/initrd/tests/test-cmdline-reader.c13
-rw-r--r--src/initrd/tests/test-ibft-reader.c22
-rw-r--r--src/main-utils.c26
-rw-r--r--src/main.c22
-rw-r--r--src/meson.build47
-rw-r--r--src/ndisc/nm-fake-ndisc.c1
-rw-r--r--src/ndisc/nm-lndp-ndisc.c37
-rw-r--r--src/ndisc/nm-ndisc.c10
-rw-r--r--src/ndisc/tests/meson.build3
-rw-r--r--src/ndisc/tests/test-ndisc-fake.c1
-rw-r--r--src/ndisc/tests/test-ndisc-linux.c1
-rw-r--r--src/nm-act-request.c9
-rw-r--r--src/nm-act-request.h3
-rw-r--r--src/nm-active-connection.c94
-rw-r--r--src/nm-active-connection.h9
-rw-r--r--src/nm-audit-manager.c12
-rw-r--r--src/nm-auth-subject.c1
-rw-r--r--src/nm-auth-utils.c5
-rw-r--r--src/nm-checkpoint.c8
-rw-r--r--src/nm-config-data.c110
-rw-r--r--src/nm-config-data.h16
-rw-r--r--src/nm-config.c286
-rw-r--r--src/nm-config.h49
-rw-r--r--src/nm-connectivity.c677
-rw-r--r--src/nm-connectivity.h2
-rw-r--r--src/nm-core-utils.c389
-rw-r--r--src/nm-core-utils.h26
-rw-r--r--src/nm-dbus-manager.c12
-rw-r--r--src/nm-dbus-manager.h2
-rw-r--r--src/nm-dcb.c1
-rw-r--r--src/nm-dhcp4-config.c2
-rw-r--r--src/nm-dhcp6-config.c2
-rw-r--r--src/nm-dispatcher.c5
-rw-r--r--src/nm-firewall-manager.c2
-rw-r--r--src/nm-hostname-manager.c2
-rw-r--r--src/nm-iface-helper.c55
-rw-r--r--src/nm-ip4-config.c101
-rw-r--r--src/nm-ip4-config.h7
-rw-r--r--src/nm-ip6-config.c93
-rw-r--r--src/nm-ip6-config.h2
-rw-r--r--src/nm-keep-alive.c526
-rw-r--r--src/nm-keep-alive.h59
-rw-r--r--src/nm-logging.c634
-rw-r--r--src/nm-logging.h130
-rw-r--r--src/nm-manager.c486
-rw-r--r--src/nm-manager.h29
-rw-r--r--src/nm-pacrunner-manager.c12
-rw-r--r--src/nm-policy.c137
-rw-r--r--src/nm-rfkill-manager.c3
-rw-r--r--src/nm-session-monitor.c6
-rw-r--r--src/nm-sleep-monitor.c2
-rw-r--r--src/nm-types.h3
-rw-r--r--src/org.freedesktop.NetworkManager.conf4
-rw-r--r--src/platform/nm-fake-platform.c33
-rw-r--r--src/platform/nm-linux-platform.c1429
-rw-r--r--src/platform/nm-netlink.c433
-rw-r--r--src/platform/nm-netlink.h326
-rw-r--r--src/platform/nm-platform-utils.c42
-rw-r--r--src/platform/nm-platform.c1164
-rw-r--r--src/platform/nm-platform.h346
-rw-r--r--src/platform/nmp-netns.c239
-rw-r--r--src/platform/nmp-object.c271
-rw-r--r--src/platform/nmp-object.h66
-rw-r--r--src/platform/tests/meson.build22
-rw-r--r--src/platform/tests/test-address.c2
-rw-r--r--src/platform/tests/test-cleanup.c5
-rw-r--r--src/platform/tests/test-common.c127
-rw-r--r--src/platform/tests/test-common.h16
-rw-r--r--src/platform/tests/test-link.c425
-rw-r--r--src/platform/tests/test-route.c16
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.c395
-rw-r--r--src/platform/wifi/nm-wifi-utils-wext.c44
-rw-r--r--src/platform/wifi/nm-wifi-utils.c1
-rw-r--r--src/platform/wifi/nm-wifi-utils.h2
-rw-r--r--src/platform/wpan/nm-wpan-utils.c86
-rw-r--r--src/platform/wpan/nm-wpan-utils.h2
-rw-r--r--src/ppp/meson.build8
-rw-r--r--src/ppp/nm-ppp-manager-call.c4
-rw-r--r--src/ppp/nm-ppp-manager-call.h1
-rw-r--r--src/ppp/nm-ppp-manager.c316
-rw-r--r--src/ppp/nm-ppp-plugin-api.h1
-rw-r--r--src/ppp/nm-pppd-plugin.c3
-rw-r--r--src/settings/nm-agent-manager.c47
-rw-r--r--src/settings/nm-settings-connection.c208
-rw-r--r--src/settings/nm-settings.c43
-rw-r--r--src/settings/nm-settings.h2
-rw-r--r--src/settings/plugins/ibft/meson.build6
-rw-r--r--src/settings/plugins/ibft/nms-ibft-connection.c1
-rw-r--r--src/settings/plugins/ibft/nms-ibft-plugin.c2
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.c2
-rw-r--r--src/settings/plugins/ibft/tests/meson.build4
-rw-r--r--src/settings/plugins/ibft/tests/test-ibft.c1
-rw-r--r--src/settings/plugins/ifcfg-rh/meson.build12
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-inotify-helper.c8
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c1
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c11
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c67
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c1
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c6
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c32
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/meson.build4
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae5
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-42
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c63
-rw-r--r--src/settings/plugins/ifupdown/meson.build10
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-connection.c1
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c1
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-parser.c6
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.c1
-rw-r--r--src/settings/plugins/ifupdown/tests/meson.build4
-rw-r--r--src/settings/plugins/ifupdown/tests/test-ifupdown.c2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-connection.c1
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c71
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.c10
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.c328
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.h43
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c77
-rw-r--r--src/settings/plugins/keyfile/tests/meson.build3
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile.c131
-rw-r--r--src/supplicant/nm-supplicant-config.c101
-rw-r--r--src/supplicant/nm-supplicant-interface.c965
-rw-r--r--src/supplicant/nm-supplicant-interface.h42
-rw-r--r--src/supplicant/nm-supplicant-manager.c159
-rw-r--r--src/supplicant/nm-supplicant-manager.h5
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c20
-rw-r--r--src/supplicant/tests/meson.build3
-rw-r--r--src/supplicant/tests/test-supplicant-config.c72
-rw-r--r--src/systemd/meson.build117
-rw-r--r--src/systemd/nm-sd-utils-core.c (renamed from src/systemd/nm-sd-utils.c)25
-rw-r--r--src/systemd/nm-sd-utils-core.h (renamed from src/systemd/nm-sd-utils.h)14
-rw-r--r--src/systemd/nm-sd.c6
-rw-r--r--src/systemd/sd-adapt-core/condition.h (renamed from src/systemd/sd-adapt/condition.h)0
-rw-r--r--src/systemd/sd-adapt-core/conf-parser.h (renamed from src/systemd/sd-adapt/conf-parser.h)0
-rw-r--r--src/systemd/sd-adapt-core/device-util.h (renamed from src/systemd/sd-adapt/architecture.h)0
-rw-r--r--src/systemd/sd-adapt-core/khash.h (renamed from src/systemd/sd-adapt/khash.h)0
-rw-r--r--src/systemd/sd-adapt-core/nm-sd-adapt-core.c (renamed from src/systemd/sd-adapt/nm-sd-adapt.c)2
-rw-r--r--src/systemd/sd-adapt-core/nm-sd-adapt-core.h101
-rw-r--r--src/systemd/sd-adapt-core/sd-daemon.h (renamed from src/systemd/sd-adapt/sd-daemon.h)0
-rw-r--r--src/systemd/sd-adapt-core/sd-device.h (renamed from src/systemd/sd-adapt/sd-device.h)0
-rw-r--r--src/systemd/sd-adapt-core/stat-util.h (renamed from src/systemd/sd-adapt/stat-util.h)0
-rw-r--r--src/systemd/sd-adapt/btrfs-util.h3
-rw-r--r--src/systemd/sd-adapt/build.h3
-rw-r--r--src/systemd/sd-adapt/cgroup-util.h3
-rw-r--r--src/systemd/sd-adapt/copy.h3
-rw-r--r--src/systemd/sd-adapt/def.h3
-rw-r--r--src/systemd/sd-adapt/device-nodes.h3
-rw-r--r--src/systemd/sd-adapt/dirent-util.h3
-rw-r--r--src/systemd/sd-adapt/errno-list.h3
-rw-r--r--src/systemd/sd-adapt/format-util.h3
-rw-r--r--src/systemd/sd-adapt/glob-util.h3
-rw-r--r--src/systemd/sd-adapt/gunicode.h3
-rw-r--r--src/systemd/sd-adapt/ioprio.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/missing.h3
-rw-r--r--src/systemd/sd-adapt/mkdir.h3
-rw-r--r--src/systemd/sd-adapt/nm-sd-adapt.h191
-rw-r--r--src/systemd/sd-adapt/procfs-util.h3
-rw-r--r--src/systemd/sd-adapt/raw-clone.h3
-rw-r--r--src/systemd/sd-adapt/terminal-util.h3
-rw-r--r--src/systemd/sd-adapt/unaligned.h3
-rw-r--r--src/systemd/sd-adapt/user-util.h3
-rw-r--r--src/systemd/sd-adapt/virt.h3
-rw-r--r--src/systemd/src/basic/alloc-util.c83
-rw-r--r--src/systemd/src/basic/alloc-util.h130
-rw-r--r--src/systemd/src/basic/async.h7
-rw-r--r--src/systemd/src/basic/env-util.c791
-rw-r--r--src/systemd/src/basic/env-util.h50
-rw-r--r--src/systemd/src/basic/escape.c509
-rw-r--r--src/systemd/src/basic/escape.h55
-rw-r--r--src/systemd/src/basic/ether-addr-util.c119
-rw-r--r--src/systemd/src/basic/ether-addr-util.h28
-rw-r--r--src/systemd/src/basic/extract-word.c289
-rw-r--r--src/systemd/src/basic/extract-word.h17
-rw-r--r--src/systemd/src/basic/fd-util.c959
-rw-r--r--src/systemd/src/basic/fd-util.h110
-rw-r--r--src/systemd/src/basic/fileio.c1671
-rw-r--r--src/systemd/src/basic/fileio.h96
-rw-r--r--src/systemd/src/basic/fs-util.c1256
-rw-r--r--src/systemd/src/basic/fs-util.h109
-rw-r--r--src/systemd/src/basic/hash-funcs.c120
-rw-r--r--src/systemd/src/basic/hash-funcs.h45
-rw-r--r--src/systemd/src/basic/hashmap.c1988
-rw-r--r--src/systemd/src/basic/hashmap.h393
-rw-r--r--src/systemd/src/basic/hexdecoct.c821
-rw-r--r--src/systemd/src/basic/hexdecoct.h38
-rw-r--r--src/systemd/src/basic/hostname-util.c295
-rw-r--r--src/systemd/src/basic/hostname-util.h27
-rw-r--r--src/systemd/src/basic/in-addr-util.c602
-rw-r--r--src/systemd/src/basic/in-addr-util.h60
-rw-r--r--src/systemd/src/basic/io-util.c256
-rw-r--r--src/systemd/src/basic/io-util.h73
-rw-r--r--src/systemd/src/basic/list.h169
-rw-r--r--src/systemd/src/basic/log.h326
-rw-r--r--src/systemd/src/basic/macro.h476
-rw-r--r--src/systemd/src/basic/mempool.c87
-rw-r--r--src/systemd/src/basic/mempool.h27
-rw-r--r--src/systemd/src/basic/parse-util.c749
-rw-r--r--src/systemd/src/basic/parse-util.h119
-rw-r--r--src/systemd/src/basic/path-util.c1060
-rw-r--r--src/systemd/src/basic/path-util.h175
-rw-r--r--src/systemd/src/basic/prioq.c303
-rw-r--r--src/systemd/src/basic/prioq.h25
-rw-r--r--src/systemd/src/basic/process-util.c1505
-rw-r--r--src/systemd/src/basic/process-util.h200
-rw-r--r--src/systemd/src/basic/random-util.c220
-rw-r--r--src/systemd/src/basic/random-util.h25
-rw-r--r--src/systemd/src/basic/refcnt.h16
-rw-r--r--src/systemd/src/basic/set.h132
-rw-r--r--src/systemd/src/basic/signal-util.h43
-rw-r--r--src/systemd/src/basic/siphash24.h53
-rw-r--r--src/systemd/src/basic/socket-util.c1254
-rw-r--r--src/systemd/src/basic/socket-util.h187
-rw-r--r--src/systemd/src/basic/sparse-endian.h93
-rw-r--r--src/systemd/src/basic/stat-util.c272
-rw-r--r--src/systemd/src/basic/stat-util.h61
-rw-r--r--src/systemd/src/basic/stdio-util.h60
-rw-r--r--src/systemd/src/basic/string-table.c19
-rw-r--r--src/systemd/src/basic/string-table.h112
-rw-r--r--src/systemd/src/basic/string-util.c1079
-rw-r--r--src/systemd/src/basic/string-util.h230
-rw-r--r--src/systemd/src/basic/strv.c888
-rw-r--r--src/systemd/src/basic/strv.h177
-rw-r--r--src/systemd/src/basic/time-util.c1503
-rw-r--r--src/systemd/src/basic/time-util.h183
-rw-r--r--src/systemd/src/basic/umask-util.h28
-rw-r--r--src/systemd/src/basic/utf8.c453
-rw-r--r--src/systemd/src/basic/utf8.h48
-rw-r--r--src/systemd/src/basic/util.c609
-rw-r--r--src/systemd/src/basic/util.h209
-rw-r--r--src/systemd/src/libsystemd-network/arp-util.c2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.c59
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.h2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-lease-internal.h4
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-network.c42
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-option.c3
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-packet.c85
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-internal.h6
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-lease-internal.h2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-network.c17
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-option.c103
-rw-r--r--src/systemd/src/libsystemd-network/lldp-internal.h3
-rw-r--r--src/systemd/src/libsystemd-network/lldp-neighbor.c45
-rw-r--r--src/systemd/src/libsystemd-network/lldp-neighbor.h3
-rw-r--r--src/systemd/src/libsystemd-network/lldp-network.c3
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.c99
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.h12
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c312
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c121
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-client.c375
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-lease.c59
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4acd.c62
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4ll.c26
-rw-r--r--src/systemd/src/libsystemd-network/sd-lldp.c113
-rw-r--r--src/systemd/src/libsystemd/sd-event/event-source.h206
-rw-r--r--src/systemd/src/libsystemd/sd-event/event-util.c100
-rw-r--r--src/systemd/src/libsystemd/sd-event/event-util.h13
-rw-r--r--src/systemd/src/libsystemd/sd-event/sd-event.c504
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.c14
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.h4
-rw-r--r--src/systemd/src/libsystemd/sd-id128/sd-id128.c47
-rw-r--r--src/systemd/src/shared/dns-domain.c115
-rw-r--r--src/systemd/src/shared/dns-domain.h33
-rw-r--r--src/systemd/src/systemd/_sd-common.h18
-rw-r--r--src/systemd/src/systemd/sd-dhcp-client.h3
-rw-r--r--src/systemd/src/systemd/sd-dhcp-lease.h3
-rw-r--r--src/systemd/src/systemd/sd-dhcp6-client.h10
-rw-r--r--src/systemd/src/systemd/sd-event.h7
-rw-r--r--src/systemd/src/systemd/sd-id128.h17
-rw-r--r--src/systemd/src/systemd/sd-lldp.h30
-rw-r--r--src/systemd/src/systemd/sd-ndisc.h6
-rw-r--r--src/tests/config/NetworkManager-warn.conf26
-rw-r--r--src/tests/config/NetworkManager.conf6
-rw-r--r--src/tests/config/meson.build5
-rw-r--r--src/tests/config/nm-test-device.c3
-rw-r--r--src/tests/config/test-config.c77
-rw-r--r--src/tests/meson.build14
-rw-r--r--src/tests/test-dcb.c2
-rw-r--r--src/tests/test-general-with-expect.c5
-rw-r--r--src/tests/test-general.c210
-rw-r--r--src/tests/test-ip4-config.c1
-rw-r--r--src/tests/test-ip6-config.c3
-rw-r--r--src/tests/test-systemd.c125
-rw-r--r--src/tests/test-utils.c14
-rw-r--r--src/vpn/nm-vpn-connection.c64
-rw-r--r--src/vpn/nm-vpn-connection.h3
-rw-r--r--src/vpn/nm-vpn-manager.c2
397 files changed, 18549 insertions, 33842 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index e3766809..71bfbf7c 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -34,6 +34,7 @@
 
 #include "platform/nm-platform.h"
 #include "nm-auth-utils.h"
+#include "systemd/nm-sd-utils-shared.h"
 
 /*****************************************************************************/
 
@@ -48,11 +49,11 @@ nm_utils_get_shared_wifi_permission (NMConnection *connection)
 {
 	NMSettingWireless *s_wifi;
 	NMSettingWirelessSecurity *s_wsec;
-	const char *method = NULL;
+	const char *method;
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) != 0)
-		return NULL;  /* Not shared */
+	method = nm_utils_get_ip_config_method (connection, AF_INET);
+	if (!nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
+		return NULL;
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	if (s_wifi) {
@@ -160,38 +161,39 @@ next:
 
 const char *
 nm_utils_get_ip_config_method (NMConnection *connection,
-                               GType         ip_setting_type)
+                               int addr_family)
 {
 	NMSettingConnection *s_con;
-	NMSettingIPConfig *s_ip4, *s_ip6;
+	NMSettingIPConfig *s_ip;
 	const char *method;
 
 	s_con = nm_connection_get_setting_connection (connection);
 
-	if (ip_setting_type == NM_TYPE_SETTING_IP4_CONFIG) {
+	if (addr_family == AF_INET) {
 		g_return_val_if_fail (s_con != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
-		s_ip4 = nm_connection_get_setting_ip4_config (connection);
-		if (!s_ip4)
+		s_ip = nm_connection_get_setting_ip4_config (connection);
+		if (!s_ip)
 			return NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
-		method = nm_setting_ip_config_get_method (s_ip4);
-		g_return_val_if_fail (method != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
+		method = nm_setting_ip_config_get_method (s_ip);
+		g_return_val_if_fail (method != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 		return method;
+	}
 
-	} else if (ip_setting_type == NM_TYPE_SETTING_IP6_CONFIG) {
+	if (addr_family == AF_INET6) {
 		g_return_val_if_fail (s_con != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
 
-		s_ip6 = nm_connection_get_setting_ip6_config (connection);
-		if (!s_ip6)
+		s_ip = nm_connection_get_setting_ip6_config (connection);
+		if (!s_ip)
 			return NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
-		method = nm_setting_ip_config_get_method (s_ip6);
-		g_return_val_if_fail (method != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
 
+		method = nm_setting_ip_config_get_method (s_ip);
+		g_return_val_if_fail (method != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
 		return method;
+	}
 
-	} else
-		g_assert_not_reached ();
+	g_return_val_if_reached ("" /* bogus */);
 }
 
 gboolean
@@ -210,10 +212,7 @@ nm_utils_connection_has_default_route (NMConnection *connection,
 	if (!connection)
 		goto out;
 
-	if (addr_family == AF_INET)
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-	else
-		s_ip = nm_connection_get_setting_ip6_config (connection);
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 	if (!s_ip)
 		goto out;
 	if (nm_setting_ip_config_get_never_default (s_ip)) {
@@ -221,16 +220,13 @@ nm_utils_connection_has_default_route (NMConnection *connection,
 		goto out;
 	}
 
+	method = nm_utils_get_ip_config_method (connection, addr_family);
 	if (addr_family == AF_INET) {
-		method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-		if (NM_IN_STRSET (method, NULL,
-		                          NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
+		if (NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
 		                          NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
 			goto out;
 	} else {
-		method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-		if (NM_IN_STRSET (method, NULL,
-		                          NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+		if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
 		                          NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
 			goto out;
 	}
@@ -341,29 +337,28 @@ check_ip6_method (NMConnection *orig,
 	if (!props)
 		return TRUE;
 
-	/* If the generated connection is 'link-local' and the candidate is both 'auto'
-	 * and may-fail=TRUE, then the candidate is OK to use.  may-fail is included
-	 * in the decision because if the candidate is 'auto' but may-fail=FALSE, then
-	 * the connection could not possibly have been previously activated on the
-	 * device if the device has no non-link-local IPv6 address.
-	 */
-	orig_ip6_method = nm_utils_get_ip_config_method (orig, NM_TYPE_SETTING_IP6_CONFIG);
-	candidate_ip6_method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP6_CONFIG);
+	orig_ip6_method = nm_utils_get_ip_config_method (orig, AF_INET6);
+	candidate_ip6_method = nm_utils_get_ip_config_method (candidate, AF_INET6);
 	candidate_ip6 = nm_connection_get_setting_ip6_config (candidate);
 
-	if (   strcmp (orig_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0
-	    && strcmp (candidate_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
-	    && (!candidate_ip6 || nm_setting_ip_config_get_may_fail (candidate_ip6))) {
+	if (   nm_streq (orig_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)
+	    && nm_streq (candidate_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)
+	    && (   !candidate_ip6
+	        || nm_setting_ip_config_get_may_fail (candidate_ip6))) {
+		/* If the generated connection is 'link-local' and the candidate is both 'auto'
+		 * and may-fail=TRUE, then the candidate is OK to use.  may-fail is included
+		 * in the decision because if the candidate is 'auto' but may-fail=FALSE, then
+		 * the connection could not possibly have been previously activated on the
+		 * device if the device has no non-link-local IPv6 address.
+		 */
 		allow = TRUE;
-	}
-
-	/* If the generated connection method is 'link-local' or 'auto' and the candidate
-	 * method is 'ignore' we can take the connection, because NM didn't simply take care
-	 * of IPv6.
-	 */
-	if (  (   strcmp (orig_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0
-	       || strcmp (orig_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0)
-	    && strcmp (candidate_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) {
+	} else if (   NM_IN_STRSET (orig_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
+	                                             NM_SETTING_IP6_CONFIG_METHOD_AUTO)
+	           && nm_streq0 (candidate_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+		/* If the generated connection method is 'link-local' or 'auto' and the candidate
+		 * method is 'ignore' we can take the connection, because NM didn't simply take care
+		 * of IPv6.
+		 */
 		allow = TRUE;
 	}
 
@@ -372,46 +367,43 @@ check_ip6_method (NMConnection *orig,
 		                  NM_SETTING_IP6_CONFIG_SETTING_NAME,
 		                  NM_SETTING_IP_CONFIG_METHOD);
 	}
+
 	return allow;
 }
 
 static int
 route_compare (NMIPRoute *route1, NMIPRoute *route2, gint64 default_metric)
 {
-	gint64 r, metric1, metric2;
+	NMIPAddr a1;
+	NMIPAddr a2;
+	guint64 m1;
+	guint64 m2;
 	int family;
 	guint plen;
-	NMIPAddr a1 = { 0 }, a2 = { 0 };
 
 	family = nm_ip_route_get_family (route1);
-	r = family - nm_ip_route_get_family (route2);
-	if (r)
-		return r > 0 ? 1 : -1;
+	NM_CMP_DIRECT (family, nm_ip_route_get_family (route2));
+
+	nm_assert_addr_family (family);
 
 	plen = nm_ip_route_get_prefix (route1);
-	r = plen - nm_ip_route_get_prefix (route2);
-	if (r)
-		return r > 0 ? 1 : -1;
-
-	metric1 = nm_ip_route_get_metric (route1) == -1 ? default_metric : nm_ip_route_get_metric (route1);
-	metric2 = nm_ip_route_get_metric (route2) == -1 ? default_metric : nm_ip_route_get_metric (route2);
-
-	r = metric1 - metric2;
-	if (r)
-		return r > 0 ? 1 : -1;
-
-	r = g_strcmp0 (nm_ip_route_get_next_hop (route1), nm_ip_route_get_next_hop (route2));
-	if (r)
-		return r;
-
-	/* NMIPRoute validates family and dest. inet_pton() is not expected to fail. */
-	inet_pton (family, nm_ip_route_get_dest (route1), &a1);
-	inet_pton (family, nm_ip_route_get_dest (route2), &a2);
-	nm_utils_ipx_address_clear_host_address (family, &a1, &a1, plen);
-	nm_utils_ipx_address_clear_host_address (family, &a2, &a2, plen);
-	r = memcmp (&a1, &a2, sizeof (a1));
-	if (r)
-		return r;
+	NM_CMP_DIRECT (plen, nm_ip_route_get_prefix (route2));
+
+	m1 = nm_ip_route_get_metric (route1);
+	m2 = nm_ip_route_get_metric (route2);
+	NM_CMP_DIRECT (m1 == -1 ? default_metric : m1,
+	               m2 == -1 ? default_metric : m2);
+
+	NM_CMP_DIRECT_STRCMP0 (nm_ip_route_get_next_hop (route1),
+	                       nm_ip_route_get_next_hop (route2));
+
+	if (!inet_pton (family, nm_ip_route_get_dest (route1), &a1))
+		nm_assert_not_reached ();
+	if (!inet_pton (family, nm_ip_route_get_dest (route2), &a2))
+		nm_assert_not_reached ();
+	nm_utils_ipx_address_clear_host_address (family, &a1, NULL, plen);
+	nm_utils_ipx_address_clear_host_address (family, &a2, NULL, plen);
+	NM_CMP_DIRECT_MEMCMP (&a1, &a2, nm_utils_addr_family_to_size (family));
 
 	return 0;
 }
@@ -519,19 +511,20 @@ check_ip4_method (NMConnection *orig,
 	if (!props)
 		return TRUE;
 
-	/* If the generated connection is 'disabled' (device had no IP addresses)
-	 * but it has no carrier, that most likely means that IP addressing could
-	 * not complete and thus no IP addresses were assigned.  In that case, allow
-	 * matching to the "auto" method.
-	 */
-	orig_ip4_method = nm_utils_get_ip_config_method (orig, NM_TYPE_SETTING_IP4_CONFIG);
-	candidate_ip4_method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP4_CONFIG);
+	orig_ip4_method = nm_utils_get_ip_config_method (orig, AF_INET);
+	candidate_ip4_method = nm_utils_get_ip_config_method (candidate, AF_INET);
 	candidate_ip4 = nm_connection_get_setting_ip4_config (candidate);
 
-	if (   strcmp (orig_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
-	    && strcmp (candidate_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0
-	    && (!candidate_ip4 || nm_setting_ip_config_get_may_fail (candidate_ip4))
-	    && (device_has_carrier == FALSE)) {
+	if (   nm_streq (orig_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)
+	    && nm_streq (candidate_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)
+	    && (   !candidate_ip4
+	        || nm_setting_ip_config_get_may_fail (candidate_ip4))
+	    && !device_has_carrier) {
+		/* If the generated connection is 'disabled' (device had no IP addresses)
+		 * but it has no carrier, that most likely means that IP addressing could
+		 * not complete and thus no IP addresses were assigned.  In that case, allow
+		 * matching to the "auto" method.
+		 */
 		remove_from_hash (settings, props,
 		                  NM_SETTING_IP4_CONFIG_SETTING_NAME,
 		                  NM_SETTING_IP_CONFIG_METHOD);
@@ -1000,3 +993,62 @@ nm_shutdown_wait_obj_unregister (NMShutdownWaitObjHandle *handle)
 	g_object_weak_unref (handle->watched_obj, _shutdown_waitobj_cb, handle);
 	_shutdown_waitobj_unregister (handle);
 }
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_file_is_in_path:
+ * @abs_filename: the absolute filename to test
+ * @abs_path: the absolute path, to check whether filename is in.
+ *
+ * This tests, whether @abs_filename is a file which lies inside @abs_path.
+ * Basically, this checks whether @abs_filename is the same as @abs_path +
+ * basename(@abs_filename). It allows simple normalizations, like coalescing
+ * multiple "//".
+ *
+ * However, beware that this function is purely filename based. That means,
+ * it will reject files that reference the same file (i.e. inode) via
+ * symlinks or bind mounts. Maybe one would like to check for file (inode)
+ * identity, but that is not really possible based on the file name alone.
+ *
+ * This means, that nm_utils_file_is_in_path("/var/run/some-file", "/var/run")
+ * will succeed, but nm_utils_file_is_in_path("/run/some-file", "/var/run")
+ * will not (although, it's well known that they reference the same path).
+ *
+ * Also, note that @abs_filename must not have trailing slashes itself.
+ * So, this will reject nm_utils_file_is_in_path("/usr/lib/", "/usr") as
+ * invalid, because the function searches for file names (and "lib/" is
+ * clearly a directory).
+ *
+ * Returns: if @abs_filename is a file inside @abs_path, returns the
+ *   trailing part of @abs_filename which is the filename. Otherwise
+ *   %NULL.
+ */
+const char *
+nm_utils_file_is_in_path (const char *abs_filename,
+                          const char *abs_path)
+{
+	const char *path;
+
+	g_return_val_if_fail (abs_filename && abs_filename[0] == '/', NULL);
+	g_return_val_if_fail (abs_path && abs_path[0] == '/', NULL);
+
+	path = nm_sd_utils_path_startswith (abs_filename, abs_path);
+	if (!path)
+		return NULL;
+
+	nm_assert (path[0] != '/');
+	nm_assert (path > abs_filename);
+	nm_assert (path <= &abs_filename[strlen (abs_filename)]);
+
+	/* we require a non-empty remainder with no slashes. That is, only a filename.
+	 *
+	 * Note this will reject "/var/run/" as not being in "/var",
+	 * while "/var/run" would pass. The function searches for files
+	 * only, so a trailing slash (indicating a directory) is not allowed).
+	 * This is despite that the function cannot determine whether "/var/run"
+	 * is itself a file or a directory. "*/
+	return path[0] && !strchr (path, '/')
+	       ? path
+	       : NULL;
+}
diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h
index efbd9037..5e701224 100644
--- a/src/NetworkManagerUtils.h
+++ b/src/NetworkManagerUtils.h
@@ -26,6 +26,9 @@
 
 /*****************************************************************************/
 
+const char *nm_utils_get_ip_config_method (NMConnection *connection,
+                                           int addr_family);
+
 const char *nm_utils_get_shared_wifi_permission (NMConnection *connection);
 
 void nm_utils_complete_generic (NMPlatform *platform,
@@ -70,7 +73,7 @@ int nm_match_spec_device_by_pllink (const NMPlatformLink *pllink,
  * away. It iterates the mainloop for another NM_SHUTDOWN_TIMEOUT_MS_EXTRA. This
  * should give time to reap the child process (after SIGKILL).
  *
- * So, the maxiumum time we should wait before sending SIGKILL should be at most
+ * So, the maximum time we should wait before sending SIGKILL should be at most
  * NM_SHUTDOWN_TIMEOUT_MS.
  */
 #define NM_SHUTDOWN_TIMEOUT_MS            1500
@@ -87,4 +90,10 @@ void nm_shutdown_wait_obj_unregister (NMShutdownWaitObjHandle *handle);
 
 /*****************************************************************************/
 
+const char *
+nm_utils_file_is_in_path (const char *abs_filename,
+                          const char *abs_path);
+
+/*****************************************************************************/
+
 #endif /* __NETWORKMANAGER_UTILS_H__ */
diff --git a/src/devices/adsl/meson.build b/src/devices/adsl/meson.build
index 7ac0e123..f92e809c 100644
--- a/src/devices/adsl/meson.build
+++ b/src/devices/adsl/meson.build
@@ -1,11 +1,11 @@
 sources = files(
   'nm-atm-manager.c',
-  'nm-device-adsl.c'
+  'nm-device-adsl.c',
 )
 
 deps = [
   libudev_dep,
-  nm_dep
+  nm_dep,
 ]
 
 libnm_device_plugin_adsl = shared_module(
@@ -15,7 +15,7 @@ libnm_device_plugin_adsl = shared_module(
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_adsl
diff --git a/src/devices/adsl/nm-atm-manager.c b/src/devices/adsl/nm-atm-manager.c
index 0ff4603d..dddb8342 100644
--- a/src/devices/adsl/nm-atm-manager.c
+++ b/src/devices/adsl/nm-atm-manager.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <gmodule.h>
 #include <libudev.h>
 
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c
index c9984f51..b3b87dc7 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/devices/adsl/nm-device-adsl.c
@@ -25,13 +25,12 @@
 #include <sys/socket.h>
 #include <linux/atmdev.h>
 #include <linux/atmbr2684.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <stdlib.h>
-#include <string.h>
 
+#include "nm-ip4-config.h"
 #include "devices/nm-device-private.h"
 #include "platform/nm-platform.h"
 #include "ppp/nm-ppp-manager-call.h"
@@ -259,8 +258,9 @@ pppoe_vcc_config (NMDeviceAdsl *self)
 	NMDevice *device = NM_DEVICE (self);
 	NMSettingAdsl *s_adsl;
 
-	s_adsl = nm_connection_get_setting_adsl (nm_device_get_applied_connection (device));
-	g_assert (s_adsl);
+	s_adsl = nm_device_get_applied_setting (device, NM_TYPE_SETTING_ADSL);
+
+	g_return_val_if_fail (s_adsl, FALSE);
 
 	/* Set up the VCC */
 	if (!br2684_assign_vcc (self, s_adsl))
@@ -370,8 +370,8 @@ br2684_create_iface (NMDeviceAdsl *self,
 			priv->nas_update_id = g_timeout_add (100, nas_update_cb, self);
 			return NM_ACT_STAGE_RETURN_POSTPONE;
 		}
-		if (errno != EEXIST) {
-			errsv = errno;
+		errsv = errno;
+		if (errsv != EEXIST) {
 			_LOGW (LOGD_ADSL, "failed to create br2684 interface (%d)", errsv);
 			break;
 		}
@@ -389,7 +389,8 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMSettingAdsl *s_adsl;
 	const char *protocol;
 
-	s_adsl = nm_connection_get_setting_adsl (nm_device_get_applied_connection (device));
+	s_adsl = nm_device_get_applied_setting (device, NM_TYPE_SETTING_ADSL);
+
 	g_return_val_if_fail (s_adsl, NM_ACT_STAGE_RETURN_FAILURE);
 
 	protocol = nm_setting_adsl_get_protocol (s_adsl);
@@ -447,9 +448,8 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 	NMDevice *device = NM_DEVICE (user_data);
 
 	/* Ignore PPP IP4 events that come in after initial configuration */
-	if (nm_device_activate_ip4_state_in_conf (device)) {
-		nm_device_activate_schedule_ip4_config_result (device, config);
-	}
+	if (nm_device_activate_ip4_state_in_conf (device))
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 }
 
 static NMActStageReturn
@@ -465,8 +465,11 @@ act_stage3_ip4_config_start (NMDevice *device,
 	const char *ppp_iface;
 
 	req = nm_device_get_act_request (device);
+
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
-	s_adsl = (NMSettingAdsl *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_ADSL);
+
+	s_adsl = nm_device_get_applied_setting (device, NM_TYPE_SETTING_ADSL);
+
 	g_return_val_if_fail (s_adsl, NM_ACT_STAGE_RETURN_FAILURE);
 
 	/* PPPoE uses the NAS interface, not the ATM interface */
@@ -515,6 +518,18 @@ act_stage3_ip4_config_start (NMDevice *device,
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
+static NMActStageReturn
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
+{
+	if (addr_family == AF_INET)
+		return act_stage3_ip4_config_start (device, (NMIP4Config **) out_config, out_failure_reason);
+
+	return NM_DEVICE_CLASS (nm_device_adsl_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
+}
+
 static void
 adsl_cleanup (NMDeviceAdsl *self)
 {
@@ -523,7 +538,7 @@ adsl_cleanup (NMDeviceAdsl *self)
 	if (priv->ppp_manager) {
 		g_signal_handlers_disconnect_by_func (priv->ppp_manager, G_CALLBACK (ppp_state_changed), self);
 		g_signal_handlers_disconnect_by_func (priv->ppp_manager, G_CALLBACK (ppp_ip4_config), self);
-		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL);
+		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL, NULL);
 		g_clear_object (&priv->ppp_manager);
 	}
 
@@ -684,7 +699,7 @@ nm_device_adsl_class_init (NMDeviceAdslClass *klass)
 	device_class->complete_connection = complete_connection;
 
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->deactivate = deactivate;
 
 	obj_properties[PROP_ATM_INDEX] =
diff --git a/src/devices/bluetooth/meson.build b/src/devices/bluetooth/meson.build
index 628a3bc8..b2f67ceb 100644
--- a/src/devices/bluetooth/meson.build
+++ b/src/devices/bluetooth/meson.build
@@ -5,12 +5,12 @@ sources = files(
   'nm-bluez4-manager.c',
   'nm-bluez5-manager.c',
   'nm-bt-error.c',
-  'nm-device-bt.c'
+  'nm-device-bt.c',
 )
 
 deps = [
   libnm_wwan_dep,
-  nm_dep
+  nm_dep,
 ]
 
 if enable_bluez5_dun
@@ -27,7 +27,7 @@ libnm_device_plugin_bluetooth = shared_module(
   link_depends: linker_script_devices,
   install: true,
   install_dir: nm_plugindir,
-  install_rpath: nm_plugindir
+  install_rpath: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_bluetooth
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
index b722f692..377ee478 100644
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ b/src/devices/bluetooth/nm-bluez-device.c
@@ -23,8 +23,6 @@
 
 #include "nm-bluez-device.h"
 
-#include <string.h>
-
 #include "nm-core-internal.h"
 #include "nm-bt-error.h"
 #include "nm-bluez-common.h"
@@ -451,6 +449,9 @@ nm_bluez_device_disconnect (NMBluezDevice *self)
 
 	g_return_if_fail (priv->dbus_connection);
 
+	/* FIXME: if we are in the process of connecting and cancel the
+	 * connection attempt, we must complete the pending connect request.
+	 * However, we must also ensure that we don't leave a connected device. */
 	if (priv->connection_bt_type == NM_BT_CAPABILITY_DUN) {
 		if (priv->bluez_version == 4) {
 			/* Can't pass a NULL interface name through dbus to bluez, so just
@@ -496,76 +497,109 @@ out:
 }
 
 static void
-bluez_connect_cb (GDBusConnection *dbus_connection,
+_connect_complete (NMBluezDevice *self,
+                   const char *device,
+                   NMBluezDeviceConnectCallback callback,
+                   gpointer callback_user_data,
+                   GError *error)
+{
+	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
+
+	nm_assert ((device || error) && !(device && error));
+
+	if (   device
+	    && priv->bluez_version == 5) {
+		priv->connected = TRUE;
+		_notify (self, PROP_CONNECTED);
+	}
+
+	if (callback)
+		callback (self, device, error, callback_user_data);
+}
+
+static void
+_connect_cb (GObject *source_object,
                   GAsyncResult *res,
                   gpointer user_data)
 {
-	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data);
-	GObject *result_object = g_async_result_get_source_object (G_ASYNC_RESULT (result));
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (result_object);
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GError *error = NULL;
-	char *device;
-	GVariant *variant;
+	gs_unref_object NMBluezDevice *self = NULL;
+	NMBluezDevicePrivate *priv;
+	NMBluezDeviceConnectCallback callback;
+	gpointer callback_user_data;
+	gs_free_error GError *error = NULL;
+	char *device = NULL;
+	gs_unref_variant GVariant *variant = NULL;
 
-	variant = g_dbus_connection_call_finish (dbus_connection, res, &error);
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
 
-	if (!variant) {
-		g_simple_async_result_take_error (result, error);
-	} else {
-		g_variant_get (variant, "(s)", &device);
+	priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
 
-		g_simple_async_result_set_op_res_gpointer (result,
-		                                           g_strdup (device),
-		                                           g_free);
+	variant = _nm_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, G_VARIANT_TYPE ("(s)"), &error);
+	if (variant) {
+		g_variant_get (variant, "(s)", &device);
 		priv->b4_iface = device;
-		g_variant_unref (variant);
 	}
 
-	g_simple_async_result_complete (result);
-	g_object_unref (result);
-	g_object_unref (result_object);
+	_connect_complete (self, device, callback, callback_user_data, error);
 }
 
 #if WITH_BLUEZ5_DUN
 static void
-bluez5_dun_connect_cb (NMBluez5DunContext *context,
-                   const char *device,
-                   GError *error,
-                   gpointer user_data)
+_connect_cb_bluez5_dun (NMBluez5DunContext *context,
+                        const char *device,
+                        GError *error,
+                        gpointer user_data)
 {
-	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data);
+	gs_unref_object NMBluezDevice *self = NULL;
+	gs_unref_object GCancellable *cancellable = NULL;
+	NMBluezDeviceConnectCallback callback;
+	gpointer callback_user_data;
+	gs_free_error GError *cancelled_error = NULL;
 
-	if (error) {
-		g_simple_async_result_take_error (result, error);
-	} else {
-		g_simple_async_result_set_op_res_gpointer (result,
-		                                           g_strdup (device),
-		                                           g_free);
-	}
+	nm_utils_user_data_unpack (user_data, &self, &cancellable, &callback, &callback_user_data);
+
+	/* FIXME(shutdown): the async operation nm_bluez5_dun_connect() should be cancellable.
+	 * Fake it here. */
+	if (g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error))
+		error = cancelled_error;
 
-	g_simple_async_result_complete (result);
-	g_object_unref (result);
+	_connect_complete (self, device, callback, callback_user_data, error);
 }
-#endif
+#else /* WITH_BLUEZ5_DUN */
+static void
+_connect_cb_bluez5_dun_idle_no_b5 (gpointer user_data,
+                                   GCancellable *cancellable)
+{
+	gs_unref_object NMBluezDevice *self = NULL;
+	NMBluezDeviceConnectCallback callback;
+	gpointer callback_user_data;
+	gs_free_error GError *error = NULL;
+
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
+
+	if (!g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "NetworkManager built without support for Bluez 5");
+	}
+	callback (self, NULL, error, callback_user_data);
+}
+#endif /* WITH_BLUEZ5_DUN */
 
 void
 nm_bluez_device_connect_async (NMBluezDevice *self,
                                NMBluetoothCapabilities connection_bt_type,
-                               GAsyncReadyCallback callback,
-                               gpointer user_data)
+                               GCancellable *cancellable,
+                               NMBluezDeviceConnectCallback callback,
+                               gpointer callback_user_data)
 {
-	GSimpleAsyncResult *simple;
 	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
 	const char *dbus_iface = NULL;
 	const char *connect_type = NULL;
 
 	g_return_if_fail (priv->capabilities & connection_bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP));
 
-	simple = g_simple_async_result_new (G_OBJECT (self),
-	                                    callback,
-	                                    user_data,
-	                                    nm_bluez_device_connect_async);
 	priv->connection_bt_type = connection_bt_type;
 
 	if (connection_bt_type == NM_BT_CAPABILITY_NAP) {
@@ -582,19 +616,29 @@ nm_bluez_device_connect_async (NMBluezDevice *self,
 #if WITH_BLUEZ5_DUN
 			if (priv->b5_dun_context == NULL)
 				priv->b5_dun_context = nm_bluez5_dun_new (priv->adapter_address, priv->address);
-			nm_bluez5_dun_connect (priv->b5_dun_context, bluez5_dun_connect_cb, simple);
+			nm_bluez5_dun_connect (priv->b5_dun_context,
+			                       _connect_cb_bluez5_dun,
+			                       nm_utils_user_data_pack (g_object_ref (self),
+			                                                nm_g_object_ref (cancellable),
+			                                                callback,
+			                                                callback_user_data));
 #else
-			g_simple_async_result_set_error (simple,
-							 NM_BT_ERROR,
-							 NM_BT_ERROR_DUN_CONNECT_FAILED,
-							 "NetworkManager built without support for Bluez 5");
-			g_simple_async_result_complete (simple);
+			if (callback) {
+				nm_utils_invoke_on_idle (_connect_cb_bluez5_dun_idle_no_b5,
+				                         nm_utils_user_data_pack (g_object_ref (self),
+				                                                  callback,
+				                                                  callback_user_data),
+				                         cancellable);
+			}
 #endif
 			return;
 		}
 	} else
-		g_assert_not_reached ();
+		g_return_if_reached ();
 
+	/* FIXME: we need to remember that a connect is in progress.
+	 * So, if the request gets cancelled, that we disconnect the
+	 * connection that was established in the meantime. */
 	g_dbus_connection_call (priv->dbus_connection,
 	                        NM_BLUEZ_SERVICE,
 	                        priv->path,
@@ -604,37 +648,11 @@ nm_bluez_device_connect_async (NMBluezDevice *self,
 	                        NULL,
 	                        G_DBUS_CALL_FLAGS_NONE,
 	                        20000,
-	                        NULL,
-	                        (GAsyncReadyCallback) bluez_connect_cb,
-	                        simple);
-}
-
-const char *
-nm_bluez_device_connect_finish (NMBluezDevice *self,
-                                GAsyncResult *result,
-                                GError **error)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GSimpleAsyncResult *simple;
-	const char *device;
-
-	g_return_val_if_fail (g_simple_async_result_is_valid (result,
-	                                                      G_OBJECT (self),
-	                                                      nm_bluez_device_connect_async),
-	                      NULL);
-
-	simple = (GSimpleAsyncResult *) result;
-
-	if (g_simple_async_result_propagate_error (simple, error))
-		return NULL;
-
-	device = (const char *) g_simple_async_result_get_op_res_gpointer (simple);
-	if (device && priv->bluez_version == 5) {
-		priv->connected = TRUE;
-		_notify (self, PROP_CONNECTED);
-	}
-
-	return device;
+	                        cancellable,
+	                        _connect_cb,
+	                        nm_utils_user_data_pack (g_object_ref (self),
+	                                                 callback,
+	                                                 callback_user_data));
 }
 
 /*****************************************************************************/
diff --git a/src/devices/bluetooth/nm-bluez-device.h b/src/devices/bluetooth/nm-bluez-device.h
index f8a1872f..d2d0beb0 100644
--- a/src/devices/bluetooth/nm-bluez-device.h
+++ b/src/devices/bluetooth/nm-bluez-device.h
@@ -66,16 +66,17 @@ guint32 nm_bluez_device_get_capabilities (NMBluezDevice *self);
 
 gboolean nm_bluez_device_get_connected (NMBluezDevice *self);
 
+typedef void (*NMBluezDeviceConnectCallback) (NMBluezDevice *self,
+                                              const char *device,
+                                              GError *error,
+                                              gpointer user_data);
+
 void
 nm_bluez_device_connect_async (NMBluezDevice *self,
                                NMBluetoothCapabilities connection_bt_type,
-                               GAsyncReadyCallback callback,
-                               gpointer user_data);
-
-const char *
-nm_bluez_device_connect_finish (NMBluezDevice *self,
-                                GAsyncResult *result,
-                                GError **error);
+                               GCancellable *cancellable,
+                               NMBluezDeviceConnectCallback callback,
+                               gpointer callback_user_data);
 
 void
 nm_bluez_device_disconnect (NMBluezDevice *self);
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index 711f2e57..dc85a8b2 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -21,7 +21,6 @@
 #include "nm-default.h"
 
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
 #include <gmodule.h>
 
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c
index c8ef7a27..3a456deb 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.c
+++ b/src/devices/bluetooth/nm-bluez4-adapter.c
@@ -22,8 +22,6 @@
 
 #include "nm-bluez4-adapter.h"
 
-#include <string.h>
-
 #include "nm-dbus-interface.h"
 #include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
diff --git a/src/devices/bluetooth/nm-bluez4-manager.c b/src/devices/bluetooth/nm-bluez4-manager.c
index 1fe02f18..82d995be 100644
--- a/src/devices/bluetooth/nm-bluez4-manager.c
+++ b/src/devices/bluetooth/nm-bluez4-manager.c
@@ -24,7 +24,6 @@
 #include "nm-bluez4-manager.h"
 
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-bluez4-adapter.h"
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c
index ca09b276..ff3a0da9 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/devices/bluetooth/nm-bluez5-dun.c
@@ -27,7 +27,6 @@
 #include <net/ethernet.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
-#include <errno.h>
 #include <fcntl.h>
 
 #include "nm-bluez5-dun.h"
@@ -57,6 +56,7 @@ dun_connect (NMBluez5DunContext *context)
 	char tty[100];
 	const int ttylen = sizeof (tty) - 1;
 	GError *error = NULL;
+	int errsv;
 
 	struct rfcomm_dev_req req = {
 		.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP),
@@ -66,10 +66,10 @@ dun_connect (NMBluez5DunContext *context)
 
 	context->rfcomm_fd = socket (AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC, BTPROTO_RFCOMM);
 	if (context->rfcomm_fd < 0) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to create RFCOMM socket: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 
@@ -78,20 +78,20 @@ dun_connect (NMBluez5DunContext *context)
 	sa.rc_channel = 0;
 	memcpy (&sa.rc_bdaddr, &context->src, ETH_ALEN);
 	if (bind (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof(sa))) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to bind socket: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 
 	sa.rc_channel = context->rfcomm_channel;
 	memcpy (&sa.rc_bdaddr, &context->dst, ETH_ALEN);
 	if (connect (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof (sa)) ) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to connect to remote device: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 
@@ -103,10 +103,10 @@ dun_connect (NMBluez5DunContext *context)
 	memcpy (&req.dst, &context->dst, ETH_ALEN);
 	devid = ioctl (context->rfcomm_fd, RFCOMMCREATEDEV, &req);
 	if (devid < 0) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to create rfcomm device: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 	context->rfcomm_id = devid;
@@ -250,7 +250,8 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da
 	sdp_list_t *search, *attrs;
 	uuid_t svclass;
 	uint16_t attr;
-	int fd, err, fd_err = 0;
+	int fd, fd_err = 0;
+	int err;
 	socklen_t len = sizeof (fd_err);
 	GError *error = NULL;
 
@@ -258,19 +259,19 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da
 
 	fd = g_io_channel_unix_get_fd (channel);
 	if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &fd_err, &len) < 0) {
-		nm_log_dbg (LOGD_BT, "(%s -> %s): getsockopt error=%d",
-		            context->src_str, context->dst_str, errno);
 		err = errno;
+		nm_log_dbg (LOGD_BT, "(%s -> %s): getsockopt error=%d",
+		            context->src_str, context->dst_str, err);
 	} else {
+		err = fd_err;
 		nm_log_dbg (LOGD_BT, "(%s -> %s): SO_ERROR error=%d",
 		            context->src_str, context->dst_str, fd_err);
-		err = fd_err;
 	}
 
 	if (err != 0) {
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Error on Service Discovery socket: (%d) %s",
-		                     err, strerror (err));
+		                     err, nm_strerror_native (err));
 		goto done;
 	}
 
@@ -297,7 +298,7 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da
 		error = g_error_new (NM_BT_ERROR,
 		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Error starting Service Discovery: (%d) %s",
-		                     err, strerror (err));
+		                     err, nm_strerror_native (err));
 	}
 
 	sdp_list_free (attrs, NULL);
@@ -342,13 +343,14 @@ nm_bluez5_dun_connect (NMBluez5DunContext *context,
 
 	if (context->rfcomm_channel != -1) {
 		nm_log_dbg (LOGD_BT, "(%s): channel number on device %s cached: %d",
-			    context->src_str, context->dst_str, context->rfcomm_channel);
+		            context->src_str, context->dst_str, context->rfcomm_channel);
+		/* FIXME: don't invoke the callback synchronously. */
 		dun_connect (context);
 		return;
 	}
 
 	nm_log_dbg (LOGD_BT, "(%s): starting channel number discovery for device %s",
-		    context->src_str, context->dst_str);
+	            context->src_str, context->dst_str);
 
 	context->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING);
 	if (!context->sdp_session) {
@@ -357,11 +359,13 @@ nm_bluez5_dun_connect (NMBluez5DunContext *context,
 
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to connect to the SDP server: (%d) %s",
-		                      err, strerror (err));
+		                      err, nm_strerror_native (err));
+		/* FIXME: don't invoke the callback synchronously. */
 		context->callback (context, NULL, error, context->user_data);
 		return;
 	}
 
+	/* FIXME(shutdown): make connect cancellable. */
 	channel = g_io_channel_unix_new (sdp_get_socket (context->sdp_session));
 	context->sdp_watch_id = g_io_add_watch (channel,
 	                                        G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
diff --git a/src/devices/bluetooth/nm-bluez5-dun.h b/src/devices/bluetooth/nm-bluez5-dun.h
index 124c1a05..b75e4399 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.h
+++ b/src/devices/bluetooth/nm-bluez5-dun.h
@@ -32,7 +32,8 @@ NMBluez5DunContext *nm_bluez5_dun_new (const char *adapter,
                                        const char *remote);
 
 void nm_bluez5_dun_connect (NMBluez5DunContext *context,
-                            NMBluez5DunFunc callback, gpointer user_data);
+                            NMBluez5DunFunc callback,
+                            gpointer user_data);
 
 /* Clean up connection resources */
 void nm_bluez5_dun_cleanup (NMBluez5DunContext *context);
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
index e984212b..be15d824 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ b/src/devices/bluetooth/nm-bluez5-manager.c
@@ -25,7 +25,6 @@
 #include "nm-bluez5-manager.h"
 
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-core-internal.h"
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index f4a1b709..e79251ce 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -23,7 +23,6 @@
 #include "nm-device-bt.h"
 
 #include <stdio.h>
-#include <string.h>
 
 #include "nm-bluez-common.h"
 #include "nm-bluez-device.h"
@@ -39,6 +38,7 @@
 #include "settings/nm-settings-connection.h"
 #include "nm-utils.h"
 #include "nm-bt-error.h"
+#include "nm-ip4-config.h"
 #include "platform/nm-platform.h"
 
 #include "devices/wwan/nm-modem-manager.h"
@@ -78,7 +78,9 @@ typedef struct {
 
 	char *rfcomm_iface;
 	NMModem *modem;
-	guint32 timeout_id;
+	guint timeout_id;
+
+	GCancellable *cancellable;
 
 	guint32 bt_type;  /* BT type of the current connection */
 } NMDeviceBtPrivate;
@@ -318,8 +320,6 @@ complete_connection (NMDevice *device,
 
 		if (s_gsm) {
 			fallback_prefix = _("GSM connection");
-			if (!nm_setting_gsm_get_number (s_gsm))
-				g_object_set (G_OBJECT (s_gsm), NM_SETTING_GSM_NUMBER, "*99#", NULL);
 		} else {
 			fallback_prefix = _("CDMA connection");
 			if (!nm_setting_cdma_get_number (s_cdma))
@@ -398,9 +398,9 @@ ppp_failed (NMModem *modem,
 	case NM_DEVICE_STATE_SECONDARIES:
 	case NM_DEVICE_STATE_ACTIVATED:
 		if (nm_device_activate_ip4_state_in_conf (device))
-			nm_device_activate_schedule_ip4_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET);
 		else if (nm_device_activate_ip6_state_in_conf (device))
-			nm_device_activate_schedule_ip6_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET6);
 		else if (nm_device_activate_ip4_state_done (device)) {
 			nm_device_ip_method_failed (device,
 			                            AF_INET,
@@ -542,7 +542,7 @@ modem_ip4_config_result (NMModem *modem,
 		                            AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 	} else
-		nm_device_activate_schedule_ip4_config_result (device, config);
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 }
 
 static void
@@ -672,6 +672,7 @@ component_added (NMDevice *device, GObject *component)
 
 	/* Got the modem */
 	nm_clear_g_source (&priv->timeout_id);
+	nm_clear_g_cancellable (&priv->cancellable);
 
 	/* Can only accept the modem in stage2, but since the interface matched
 	 * what we were expecting, don't let anything else claim the modem either.
@@ -715,8 +716,11 @@ static gboolean
 modem_find_timeout (gpointer user_data)
 {
 	NMDeviceBt *self = NM_DEVICE_BT (user_data);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+
+	priv->timeout_id = 0;
+	nm_clear_g_cancellable (&priv->cancellable);
 
-	NM_DEVICE_BT_GET_PRIVATE (self)->timeout_id = 0;
 	nm_device_state_changed (NM_DEVICE (self),
 	                         NM_DEVICE_STATE_FAILED,
 	                         NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND);
@@ -738,8 +742,8 @@ check_connect_continue (NMDeviceBt *self)
 	       "Activation: (bluetooth) Stage 2 of 5 (Device Configure) successful. Will connect via %s.",
 	       dun ? "DUN" : (pan ? "PAN" : "unknown"));
 
-	/* Kill the connect timeout since we're connected now */
 	nm_clear_g_source (&priv->timeout_id);
+	nm_clear_g_cancellable (&priv->cancellable);
 
 	if (pan) {
 		/* Bluez says we're connected now.  Start IP config. */
@@ -755,25 +759,25 @@ check_connect_continue (NMDeviceBt *self)
 }
 
 static void
-bluez_connect_cb (GObject *object,
-                  GAsyncResult *res,
-                  void *user_data)
+bluez_connect_cb (NMBluezDevice *bt_device,
+                  const char *device_name,
+                  GError *error,
+                  gpointer user_data)
 {
-	gs_unref_object NMDeviceBt *self = NM_DEVICE_BT (user_data);
+	gs_unref_object NMDeviceBt *self = user_data;
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	GError *error = NULL;
-	const char *device;
 
-	device = nm_bluez_device_connect_finish (NM_BLUEZ_DEVICE (object),
-	                                         res, &error);
+	if (nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	nm_clear_g_source (&priv->timeout_id);
+	g_clear_object (&priv->cancellable);
 
 	if (!nm_device_is_activating (NM_DEVICE (self)))
 		return;
 
-	if (!device) {
+	if (!device_name) {
 		_LOGW (LOGD_BT, "Error connecting with bluez: %s", error->message);
-		g_clear_error (&error);
-
 		nm_device_state_changed (NM_DEVICE (self),
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_BT_FAILED);
@@ -782,10 +786,10 @@ bluez_connect_cb (GObject *object,
 
 	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
 		g_free (priv->rfcomm_iface);
-		priv->rfcomm_iface = g_strdup (device);
+		priv->rfcomm_iface = g_strdup (device_name);
 	} else if (priv->bt_type == NM_BT_CAPABILITY_NAP) {
-		if (!nm_device_set_ip_iface (NM_DEVICE (self), device)) {
-			_LOGW (LOGD_BT, "Error connecting with bluez: cannot find device %s", device);
+		if (!nm_device_set_ip_iface (NM_DEVICE (self), device_name)) {
+			_LOGW (LOGD_BT, "Error connecting with bluez: cannot find device %s", device_name);
 			nm_device_state_changed (NM_DEVICE (self),
 			                         NM_DEVICE_STATE_FAILED,
 			                         NM_DEVICE_STATE_REASON_BT_FAILED);
@@ -843,10 +847,13 @@ static gboolean
 bt_connect_timeout (gpointer user_data)
 {
 	NMDeviceBt *self = NM_DEVICE_BT (user_data);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 
 	_LOGD (LOGD_BT, "initial connection timed out");
 
-	NM_DEVICE_BT_GET_PRIVATE (self)->timeout_id = 0;
+	priv->timeout_id = 0;
+	nm_clear_g_cancellable (&priv->cancellable);
+
 	nm_device_state_changed (NM_DEVICE (self),
 	                         NM_DEVICE_STATE_FAILED,
 	                         NM_DEVICE_STATE_REASON_BT_FAILED);
@@ -876,45 +883,45 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	_LOGD (LOGD_BT, "requesting connection to the device");
 
-	/* Connect to the BT device */
-	nm_bluez_device_connect_async (priv->bt_device,
-	                               priv->bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP),
-	                               bluez_connect_cb, g_object_ref (device));
-
 	nm_clear_g_source (&priv->timeout_id);
+	nm_clear_g_cancellable (&priv->cancellable);
+
 	priv->timeout_id = g_timeout_add_seconds (30, bt_connect_timeout, device);
+	priv->cancellable = g_cancellable_new ();
+
+	nm_bluez_device_connect_async (priv->bt_device,
+	                               priv->bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP),
+	                               priv->cancellable,
+	                               bluez_connect_cb,
+	                               g_object_ref (self));
 
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
 
+	nm_assert_addr_family (addr_family);
+
 	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
-		return nm_modem_stage3_ip4_config_start (priv->modem,
-		                                         device,
-		                                         NM_DEVICE_CLASS (nm_device_bt_parent_class),
-		                                         out_failure_reason);
+		if (addr_family == AF_INET) {
+			return nm_modem_stage3_ip4_config_start (priv->modem,
+			                                         device,
+			                                         NM_DEVICE_CLASS (nm_device_bt_parent_class),
+			                                         out_failure_reason);
+		} else {
+			return nm_modem_stage3_ip6_config_start (priv->modem,
+			                                         device,
+			                                         out_failure_reason);
+		}
 	}
 
-	return NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
-
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN)
-		return nm_modem_stage3_ip6_config_start (priv->modem, device, out_failure_reason);
-
-	return NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static void
@@ -925,6 +932,9 @@ deactivate (NMDevice *device)
 	priv->have_iface = FALSE;
 	priv->connected = FALSE;
 
+	nm_clear_g_source (&priv->timeout_id);
+	nm_clear_g_cancellable (&priv->cancellable);
+
 	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
 		if (priv->modem) {
 			nm_modem_deactivate (priv->modem, device);
@@ -942,8 +952,6 @@ deactivate (NMDevice *device)
 	if (priv->bt_type != NM_BT_CAPABILITY_NONE)
 		nm_bluez_device_disconnect (priv->bt_device);
 
-	nm_clear_g_source (&priv->timeout_id);
-
 	priv->bt_type = NM_BT_CAPABILITY_NONE;
 
 	g_free (priv->rfcomm_iface);
@@ -1128,6 +1136,7 @@ dispose (GObject *object)
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
 
 	nm_clear_g_source (&priv->timeout_id);
+	nm_clear_g_cancellable (&priv->cancellable);
 
 	g_signal_handlers_disconnect_matched (priv->bt_device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object);
 
@@ -1191,8 +1200,7 @@ nm_device_bt_class_init (NMDeviceBtClass *klass)
 	device_class->can_auto_connect = can_auto_connect;
 	device_class->deactivate = deactivate;
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->check_connection_available = check_connection_available;
 	device_class->complete_connection = complete_connection;
diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c
index 035487a3..a8f7a63a 100644
--- a/src/devices/nm-acd-manager.c
+++ b/src/devices/nm-acd-manager.c
@@ -39,40 +39,23 @@ typedef enum {
 typedef struct {
 	in_addr_t address;
 	gboolean duplicate;
-	NMAcdManager *manager;
-	NAcd *acd;
-	GIOChannel *channel;
-	guint event_id;
+	NAcdProbe *probe;
 } AddressInfo;
 
-enum {
-	PROBE_TERMINATED,
-	LAST_SIGNAL,
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
+struct _NMAcdManager {
 	int            ifindex;
 	guint8         hwaddr[ETH_ALEN];
 	State          state;
 	GHashTable    *addresses;
 	guint          completed;
-} NMAcdManagerPrivate;
+	NAcd          *acd;
+	GIOChannel    *channel;
+	guint          event_id;
 
-struct _NMAcdManager {
-	GObject parent;
-	NMAcdManagerPrivate _priv;
+	NMAcdCallbacks callbacks;
+	gpointer user_data;
 };
 
-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
@@ -80,14 +63,13 @@ G_DEFINE_TYPE (NMAcdManager, nm_acd_manager, G_TYPE_OBJECT)
 #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), \
+                self && self->ifindex > 0 ? nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex) : NULL, \
                 NULL, \
                 "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                 _NMLOG_PREFIX_NAME, \
-                self ? nm_sprintf_buf (_sbuf, "[%p,%d]", self, _ifindex) : "" \
+                self ? nm_sprintf_buf (_sbuf, "[%p,%d]", self, self->ifindex) : "" \
                 _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
     } G_STMT_END
 
@@ -111,32 +93,25 @@ _acd_event_to_string (unsigned int event)
 	return NULL;
 }
 
-#define acd_event_to_string(event) NM_UTILS_LOOKUP_STR (_acd_event_to_string, event)
+#define acd_event_to_string_a(event) NM_UTILS_LOOKUP_STR_A (_acd_event_to_string, event)
 
 static const char *
-_acd_error_to_string (int error)
+acd_error_to_string (int error)
 {
 	if (error < 0)
-		return strerror(-error);
+		return nm_strerror_native (-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)
+	g_return_val_if_reached (NULL);
+}
 
 /*****************************************************************************/
 
@@ -152,21 +127,18 @@ _acd_error_to_string (int error)
 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);
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (self->state == STATE_INIT, FALSE);
 
-	if (g_hash_table_lookup (priv->addresses, GUINT_TO_POINTER (address)))
+	if (g_hash_table_lookup (self->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);
+	g_hash_table_insert (self->addresses, GUINT_TO_POINTER (address), info);
 
 	return TRUE;
 }
@@ -174,115 +146,142 @@ nm_acd_manager_add_address (NMAcdManager *self, in_addr_t address)
 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);
+	NMAcdManager *self = data;
 	NAcdEvent *event;
+	AddressInfo *info;
+	gboolean emit_probe_terminated = FALSE;
 	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))
+	if (n_acd_dispatch (self->acd))
 		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));
+	while (   !n_acd_pop_event (self->acd, &event)
+	       && event) {
+		gboolean check_probing_done = FALSE;
+
+		switch (event->event) {
+		case N_ACD_EVENT_READY:
+			n_acd_probe_get_userdata (event->ready.probe, (void **) &info);
+			info->duplicate = FALSE;
+			if (self->state == STATE_ANNOUNCING) {
+				/* fake probe ended, start announcing */
+				r = n_acd_probe_announce (info->probe, N_ACD_DEFEND_ONCE);
+				if (r) {
+					_LOGW ("couldn't announce address %s on interface '%s': %s",
+					       nm_utils_inet4_ntop (info->address, address_str),
+					       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
+					       acd_error_to_string (r));
+				} else {
+					_LOGD ("announcing address %s",
+					       nm_utils_inet4_ntop (info->address, address_str));
+				}
 			}
+			check_probing_done = TRUE;
+			break;
+		case N_ACD_EVENT_USED:
+			n_acd_probe_get_userdata (event->used.probe, (void **) &info);
+			info->duplicate = TRUE;
+			check_probing_done = TRUE;
+			break;
+		case N_ACD_EVENT_DEFENDED:
+			n_acd_probe_get_userdata (event->defended.probe, (void **) &info);
+			_LOGD ("defended address %s from host %s",
+			       nm_utils_inet4_ntop (info->address, address_str),
+			       (hwaddr_str = nm_utils_hwaddr_ntoa (event->defended.sender,
+			                                           event->defended.n_sender)));
+			break;
+		case N_ACD_EVENT_CONFLICT:
+			n_acd_probe_get_userdata (event->conflict.probe, (void **) &info);
+			_LOGW ("conflict for address %s detected with host %s on interface '%s'",
+			       nm_utils_inet4_ntop (info->address, address_str),
+			       (hwaddr_str = nm_utils_hwaddr_ntoa (event->defended.sender,
+			                                           event->defended.n_sender)),
+			       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex));
+			break;
+		default:
+			_LOGD ("unhandled event '%s'", acd_event_to_string_a (event->event));
+			break;
+		}
+
+		if (   check_probing_done
+		    && self->state == STATE_PROBING
+		    && ++self->completed == g_hash_table_size (self->addresses)) {
+			self->state = STATE_PROBE_DONE;
+			emit_probe_terminated = TRUE;
 		}
-		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);
+	if (emit_probe_terminated) {
+		if (self->callbacks.probe_terminated_callback) {
+			self->callbacks.probe_terminated_callback (self,
+			                                           self->user_data);
+		}
 	}
 
 	return G_SOURCE_CONTINUE;
 }
 
 static gboolean
-acd_probe_start (NMAcdManager *self,
-                 AddressInfo *info,
-                 guint64 timeout)
+acd_probe_add (NMAcdManager *self,
+               AddressInfo *info,
+               guint64 timeout)
 {
-	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
-	NAcdConfig *config;
-	int r, fd;
+	NAcdProbeConfig *probe_config;
+	int r;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
-	r = n_acd_new (&info->acd);
+	r = n_acd_probe_config_new (&probe_config);
 	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),
+		_LOGW ("could not create probe config for %s on interface '%s': %s",
+		       nm_utils_inet4_ntop (info->address, sbuf),
+		       nm_platform_link_get_name (NM_PLATFORM_GET, self->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,
-	};
+	n_acd_probe_config_set_ip (probe_config, (struct in_addr) { info->address });
+	n_acd_probe_config_set_timeout (probe_config, timeout);
 
-	r = n_acd_start (info->acd, config);
+	r = n_acd_probe (self->acd, &info->probe, probe_config);
 	if (r) {
 		_LOGW ("could not start probe for %s on interface '%s': %s",
-		       nm_utils_inet4_ntop (info->address, NULL),
-		       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex),
+		       nm_utils_inet4_ntop (info->address, sbuf),
+		       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 		       acd_error_to_string (r));
+		n_acd_probe_config_free (probe_config);
 		return FALSE;
 	}
 
-	if (timeout) {
-		_LOGD ("started probe for %s with timeout %llu",
-		       nm_utils_inet4_ntop (info->address, NULL),
-		       (unsigned long long) timeout);
-	}
+	n_acd_probe_set_userdata (info->probe, info);
+	n_acd_probe_config_free (probe_config);
 
 	return TRUE;
 }
 
+static int
+acd_init (NMAcdManager *self)
+{
+	NAcdConfig *config;
+	int r;
+
+	if (self->acd)
+		return 0;
+
+	r = n_acd_config_new (&config);
+	if (r)
+		return r;
+
+	n_acd_config_set_ifindex (config, self->ifindex);
+	n_acd_config_set_transport (config, N_ACD_TRANSPORT_ETHERNET);
+	n_acd_config_set_mac (config, self->hwaddr, ETH_ALEN);
+
+	r = n_acd_new (&self->acd, config);
+	n_acd_config_free (config);
+	return r;
+}
+
 /**
  * nm_acd_manager_start_probe:
  * @self: a #NMAcdManager
@@ -297,59 +296,36 @@ acd_probe_start (NMAcdManager *self,
 gboolean
 nm_acd_manager_start_probe (NMAcdManager *self, guint timeout)
 {
-	NMAcdManagerPrivate *priv;
 	GHashTableIter iter;
 	AddressInfo *info;
 	gboolean success = FALSE;
+	int fd, r;
 
-	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);
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (self->state == STATE_INIT, FALSE);
+
+	r = acd_init (self);
+	if (r) {
+		_LOGW ("couldn't init ACD for probing on interface '%s': %s",
+		       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
+		       acd_error_to_string (r));
+		return FALSE;
+	}
 
-	priv->completed = 0;
+	self->completed = 0;
 
-	g_hash_table_iter_init (&iter, priv->addresses);
+	g_hash_table_iter_init (&iter, self->addresses);
 	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info))
-		success |= acd_probe_start (self, info, timeout);
+		success |= acd_probe_add (self, info, timeout);
 
 	if (success)
-		priv->state = STATE_PROBING;
+		self->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);
+	n_acd_get_fd (self->acd, &fd);
+	self->channel = g_io_channel_unix_new (fd);
+	self->event_id = g_io_add_watch (self->channel, G_IO_IN, acd_event, self);
 
-	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);
+	return success;
 }
 
 /**
@@ -365,15 +341,12 @@ nm_acd_manager_destroy (NMAcdManager *self)
 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);
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (NM_IN_SET (self->state, STATE_INIT, STATE_PROBE_DONE), FALSE);
 
-	info = g_hash_table_lookup (priv->addresses, GUINT_TO_POINTER (address));
+	info = g_hash_table_lookup (self->addresses, GUINT_TO_POINTER (address));
 	g_return_val_if_fail (info, FALSE);
 
 	return !info->duplicate;
@@ -388,41 +361,43 @@ nm_acd_manager_check_address (NMAcdManager *self, in_addr_t address)
 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) {
+	r = acd_init (self);
+	if (r) {
+		_LOGW ("couldn't init ACD for announcing addresses on interface '%s': %s",
+		       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
+		       acd_error_to_string (r));
+		return;
+	}
+
+	if (self->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);
+		 * the announcement. */
+		g_hash_table_iter_init (&iter, self->addresses);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info))
+			acd_probe_add (self, info, 0);
+		self->state = STATE_ANNOUNCING;
+	} else if (self->state == STATE_ANNOUNCING) {
+		char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
+		g_hash_table_iter_init (&iter, self->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);
+			r = n_acd_probe_announce (info->probe, N_ACD_DEFEND_ONCE);
 			if (r) {
 				_LOGW ("couldn't announce address %s on interface '%s': %s",
-				       nm_utils_inet4_ntop (info->address, NULL),
-				       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex),
+				       nm_utils_inet4_ntop (info->address, sbuf),
+				       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 				       acd_error_to_string (r));
 			} else
-				_LOGD ("announcing address %s", nm_utils_inet4_ntop (info->address, NULL));
+				_LOGD ("announcing address %s", nm_utils_inet4_ntop (info->address, sbuf));
 		}
-	} else
-		nm_assert_not_reached ();
+	}
 }
 
 static void
@@ -430,64 +405,52 @@ 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);
+	n_acd_probe_free (info->probe);
 
 	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)
+nm_acd_manager_new (int ifindex,
+                    const guint8 *hwaddr,
+                    guint hwaddr_len,
+                    const NMAcdCallbacks *callbacks,
+                    gpointer user_data)
 {
 	NMAcdManager *self;
-	NMAcdManagerPrivate *priv;
 
+	g_return_val_if_fail (ifindex > 0, NULL);
 	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);
+	self = g_slice_new0 (NMAcdManager);
+
+	if (callbacks)
+		self->callbacks = *callbacks;
+	self->user_data = user_data;
 
+	self->addresses = g_hash_table_new_full (nm_direct_hash, NULL,
+	                                         NULL, destroy_address_info);
+	self->state = STATE_INIT;
+	self->ifindex = ifindex;
+	memcpy (self->hwaddr, hwaddr, ETH_ALEN);
 	return self;
 }
 
-static void
-dispose (GObject *object)
+void
+nm_acd_manager_free (NMAcdManager *self)
 {
-	NMAcdManager *self = NM_ACD_MANAGER (object);
-	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+	g_return_if_fail (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);
+	if (self->callbacks.user_data_destroy)
+		self->callbacks.user_data_destroy (self->user_data);
 
-	object_class->dispose = dispose;
+	nm_clear_pointer (&self->addresses, g_hash_table_destroy);
+	nm_clear_pointer (&self->channel, g_io_channel_unref);
+	nm_clear_g_source (&self->event_id);
+	nm_clear_pointer (&self->acd, n_acd_unref);
 
-	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);
+	g_slice_free (NMAcdManager, self);
 }
diff --git a/src/devices/nm-acd-manager.h b/src/devices/nm-acd-manager.h
index eeede5da..75884846 100644
--- a/src/devices/nm-acd-manager.h
+++ b/src/devices/nm-acd-manager.h
@@ -19,25 +19,25 @@
 
 #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))
+typedef struct _NMAcdManager NMAcdManager;
 
-#define NM_ACD_MANAGER_PROBE_TERMINATED  "probe-terminated"
+typedef struct {
+	void (*probe_terminated_callback) (NMAcdManager *self,
+	                                   gpointer user_data);
+	GDestroyNotify user_data_destroy;
+} NMAcdCallbacks;
 
-typedef struct _NMAcdManagerClass NMAcdManagerClass;
+NMAcdManager *nm_acd_manager_new (int ifindex,
+                                  const guint8 *hwaddr,
+                                  guint hwaddr_len,
+                                  const NMAcdCallbacks *callbacks,
+                                  gpointer user_data);
 
-GType nm_acd_manager_get_type (void);
+void nm_acd_manager_free (NMAcdManager *self);
 
-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-device-6lowpan.c b/src/devices/nm-device-6lowpan.c
index b6b9157c..40103747 100644
--- a/src/devices/nm-device-6lowpan.c
+++ b/src/devices/nm-device-6lowpan.c
@@ -110,9 +110,9 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
 	NMSetting6Lowpan *s_6lowpan;
 	int parent_ifindex;
+	int r;
 
 	s_6lowpan = NM_SETTING_6LOWPAN (nm_connection_get_setting (connection, NM_TYPE_SETTING_6LOWPAN));
 	g_return_val_if_fail (s_6lowpan, FALSE);
@@ -126,13 +126,13 @@ create_and_realize (NMDevice *device,
 		return FALSE;
 	}
 
-	plerr = nm_platform_link_6lowpan_add (nm_device_get_platform (device), iface, parent_ifindex, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_6lowpan_add (nm_device_get_platform (device), iface, parent_ifindex, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create 6lowpan interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index 6e7e6ffc..6dabdfe8 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -22,7 +22,6 @@
 
 #include "nm-device-bond.h"
 
-#include <errno.h>
 #include <stdlib.h>
 
 #include "NetworkManagerUtils.h"
@@ -220,7 +219,6 @@ static NMActStageReturn
 apply_bonding_config (NMDevice *device)
 {
 	NMDeviceBond *self = NM_DEVICE_BOND (device);
-	NMConnection *connection;
 	NMSettingBond *s_bond;
 	int ifindex = nm_device_get_ifindex (device);
 	const char *mode_str, *value;
@@ -241,10 +239,9 @@ apply_bonding_config (NMDevice *device)
 	 *     arp_interval doesn't require miimon to be 0
 	 */
 
-	connection = nm_device_get_applied_connection (device);
-	g_assert (connection);
-	s_bond = nm_connection_get_setting_bond (connection);
-	g_assert (s_bond);
+	s_bond = nm_device_get_applied_setting (device, NM_TYPE_SETTING_BOND);
+
+	g_return_val_if_fail (s_bond, NM_ACT_STAGE_RETURN_FAILURE);
 
 	mode_str = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MODE);
 	if (!mode_str)
@@ -461,17 +458,17 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
+	int r;
 
 	g_assert (iface);
 
-	plerr = nm_platform_link_bond_add (nm_device_get_platform (device), iface, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_bond_add (nm_device_get_platform (device), iface, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create bond interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 	return TRUE;
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index 68e1ac28..4c8921c0 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -512,7 +512,7 @@ create_and_realize (NMDevice *device,
 	const char *hwaddr;
 	gs_free char *hwaddr_cloned = NULL;
 	guint8 mac_address[NM_UTILS_HWADDR_LEN_MAX];
-	NMPlatformError plerr;
+	int r;
 
 	nm_assert (iface);
 
@@ -539,17 +539,17 @@ create_and_realize (NMDevice *device,
 		}
 	}
 
-	plerr = nm_platform_link_bridge_add (nm_device_get_platform (device),
-	                                     iface,
-	                                     hwaddr ? mac_address : NULL,
-	                                     hwaddr ? ETH_ALEN : 0,
-	                                     out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_bridge_add (nm_device_get_platform (device),
+	                                 iface,
+	                                 hwaddr ? mac_address : NULL,
+	                                 hwaddr ? ETH_ALEN : 0,
+	                                 out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create bridge interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-dummy.c b/src/devices/nm-device-dummy.c
index a9059383..47a45342 100644
--- a/src/devices/nm-device-dummy.c
+++ b/src/devices/nm-device-dummy.c
@@ -17,7 +17,6 @@
 #include "nm-device-dummy.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 
 #include "nm-act-request.h"
@@ -98,19 +97,19 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
 	NMSettingDummy *s_dummy;
+	int r;
 
 	s_dummy = nm_connection_get_setting_dummy (connection);
 	g_assert (s_dummy);
 
-	plerr = nm_platform_link_dummy_add (nm_device_get_platform (device), iface, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_dummy_add (nm_device_get_platform (device), iface, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create dummy interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index e7262683..24c99f76 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -24,10 +24,8 @@
 #include "nm-device-ethernet.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include <errno.h>
 #include <libudev.h>
 
 #include "nm-device-private.h"
@@ -556,7 +554,9 @@ build_supplicant_config (NMDeviceEthernet *self,
 	guint32 mtu;
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
-	g_assert (connection);
+
+	g_return_val_if_fail (connection, NULL);
+
 	con_uuid = nm_connection_get_uuid (connection);
 	mtu = nm_platform_link_get_mtu (nm_device_get_platform (NM_DEVICE (self)),
 	                                nm_device_get_ifindex (NM_DEVICE (self)));
@@ -790,7 +790,7 @@ link_negotiation_set (NMDevice *device)
 	guint32 speed = 0;
 	guint32 link_speed;
 
-	s_wired = (NMSettingWired *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
+	s_wired = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
 	if (s_wired) {
 		autoneg = nm_setting_wired_get_auto_negotiate (s_wired);
 		speed = nm_setting_wired_get_speed (s_wired);
@@ -880,8 +880,8 @@ act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *out_failure_reason)
 			       delay);
 			g_assert (!priv->pppoe_wait_id);
 			priv->pppoe_wait_id = g_timeout_add_seconds (delay,
-								     pppoe_reconnect_delay,
-								     self);
+			                                             pppoe_reconnect_delay,
+			                                             self);
 			return NM_ACT_STAGE_RETURN_POSTPONE;
 		}
 		priv->last_pppoe_time = 0;
@@ -900,6 +900,7 @@ nm_8021x_stage2_config (NMDeviceEthernet *self, NMDeviceStateReason *out_failure
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
 	security = nm_connection_get_setting_802_1x (connection);
@@ -981,9 +982,8 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 	NMDevice *device = NM_DEVICE (user_data);
 
 	/* Ignore PPP IP4 events that come in after initial configuration */
-	if (nm_device_activate_ip4_state_in_conf (device)) {
-		nm_device_activate_schedule_ip4_config_result (device, config);
-	}
+	if (nm_device_activate_ip4_state_in_conf (device))
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 }
 
 static NMActStageReturn
@@ -995,10 +995,12 @@ pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *out_
 	NMActRequest *req;
 	GError *err = NULL;
 
-	req = nm_device_get_act_request (NM_DEVICE (self));
+	req = nm_device_get_act_request (device);
+
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
 
-	s_pppoe = (NMSettingPppoe *) nm_device_get_applied_setting ((NMDevice *) self, NM_TYPE_SETTING_PPPOE);
+	s_pppoe = nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPPOE);
+
 	g_return_val_if_fail (s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
 
 	priv->ppp_manager = nm_ppp_manager_create (nm_device_get_iface (device),
@@ -1069,8 +1071,10 @@ dcb_configure (NMDevice *device)
 
 	nm_clear_g_source (&priv->dcb_timeout_id);
 
-	s_dcb = (NMSettingDcb *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
-	g_assert (s_dcb);
+	s_dcb = nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
+
+	g_return_val_if_fail (s_dcb, FALSE);
+
 	if (!nm_dcb_setup (nm_device_get_iface (device), s_dcb, &error)) {
 		_LOGW (LOGD_DCB, "Activation: (ethernet) failed to enable DCB/FCoE: %s",
 		       error->message);
@@ -1199,7 +1203,8 @@ wake_on_lan_enable (NMDevice *device)
 	NMSettingWired *s_wired;
 	const char *password = NULL;
 
-	s_wired = (NMSettingWired *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
+	s_wired = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
+
 	if (s_wired) {
 		wol = nm_setting_wired_get_wake_on_lan (s_wired);
 		password = nm_setting_wired_get_wake_on_lan_password (s_wired);
@@ -1208,7 +1213,7 @@ wake_on_lan_enable (NMDevice *device)
 	}
 
 	wol = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                   "ethernet.wake-on-lan",
+	                                                   NM_CON_DEFAULT ("ethernet.wake-on-lan"),
 	                                                   device,
 	                                                   NM_SETTING_WIRED_WAKE_ON_LAN_NONE,
 	                                                   G_MAXINT32,
@@ -1240,8 +1245,8 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 	NMSettingDcb *s_dcb;
 
-	s_con = NM_SETTING_CONNECTION (nm_device_get_applied_setting (device,
-	                                                              NM_TYPE_SETTING_CONNECTION));
+	s_con = nm_device_get_applied_setting (device, NM_TYPE_SETTING_CONNECTION);
+
 	g_return_val_if_fail (s_con, NM_ACT_STAGE_RETURN_FAILURE);
 
 	nm_clear_g_source (&priv->dcb_timeout_id);
@@ -1254,8 +1259,8 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	if (!strcmp (connection_type, NM_SETTING_WIRED_SETTING_NAME)) {
 		NMSetting8021x *security;
 
-		security = (NMSetting8021x *) nm_device_get_applied_setting (device,
-		                                                             NM_TYPE_SETTING_802_1X);
+		security = nm_device_get_applied_setting (device, NM_TYPE_SETTING_802_1X);
+
 		if (security) {
 			/* FIXME: for now 802.1x is mutually exclusive with DCB */
 			return nm_8021x_stage2_config (self, out_failure_reason);
@@ -1265,7 +1270,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	wake_on_lan_enable (device);
 
 	/* DCB and FCoE setup */
-	s_dcb = (NMSettingDcb *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
+	s_dcb = nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
 	if (s_dcb) {
 		/* lldpad really really wants the carrier to be up */
 		if (nm_platform_link_is_connected (nm_device_get_platform (device), nm_device_get_ifindex (device))) {
@@ -1288,7 +1293,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	                           NM_SETTING_PPPOE_SETTING_NAME)) {
 		NMSettingPpp *s_ppp;
 
-		s_ppp = (NMSettingPpp *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPP);
+		s_ppp = nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPP);
 		if (s_ppp) {
 			guint32 mtu = 0, mru = 0, mxu;
 
@@ -1309,21 +1314,25 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	NMSettingConnection *s_con;
 	const char *connection_type;
 
-	s_con = NM_SETTING_CONNECTION (nm_device_get_applied_setting (device, NM_TYPE_SETTING_CONNECTION));
-	g_return_val_if_fail (s_con, NM_ACT_STAGE_RETURN_FAILURE);
+	if (addr_family == AF_INET) {
+		s_con = nm_device_get_applied_setting (device, NM_TYPE_SETTING_CONNECTION);
 
-	connection_type = nm_setting_connection_get_connection_type (s_con);
-	if (!strcmp (connection_type, NM_SETTING_PPPOE_SETTING_NAME))
-		return pppoe_stage3_ip4_config_start (NM_DEVICE_ETHERNET (device), out_failure_reason);
+		g_return_val_if_fail (s_con, NM_ACT_STAGE_RETURN_FAILURE);
+
+		connection_type = nm_setting_connection_get_connection_type (s_con);
+		if (!strcmp (connection_type, NM_SETTING_PPPOE_SETTING_NAME))
+			return pppoe_stage3_ip4_config_start (NM_DEVICE_ETHERNET (device), out_failure_reason);
+	}
 
-	return NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static guint32
@@ -1347,7 +1356,7 @@ deactivate (NMDevice *device)
 	nm_clear_g_source (&priv->pppoe_wait_id);
 
 	if (priv->ppp_manager) {
-		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL);
+		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL, NULL);
 		g_clear_object (&priv->ppp_manager);
 	}
 
@@ -1358,7 +1367,7 @@ deactivate (NMDevice *device)
 	priv->dcb_handle_carrier_changes = FALSE;
 
 	/* Tear down DCB/FCoE if it was enabled */
-	s_dcb = (NMSettingDcb *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
+	s_dcb = nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
 	if (s_dcb) {
 		if (!nm_dcb_cleanup (nm_device_get_iface (device), &error)) {
 			_LOGW (LOGD_DEVICE | LOGD_PLATFORM, "failed to disable DCB/FCoE: %s",
@@ -1785,7 +1794,7 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->get_configured_mtu = get_configured_mtu;
 	device_class->deactivate = deactivate;
 	device_class->get_s390_subchannels = get_s390_subchannels;
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index a11ead6e..30aca038 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -24,8 +24,6 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <errno.h>
-#include <string.h>
 #include <gmodule.h>
 
 #include "platform/nm-platform.h"
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index 5138b684..4db7d8a7 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -87,7 +87,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
 		return ret;
 
-	s_infiniband = (NMSettingInfiniband *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_INFINIBAND);
+	s_infiniband = nm_device_get_applied_setting (device, NM_TYPE_SETTING_INFINIBAND);
+
 	g_return_val_if_fail (s_infiniband, NM_ACT_STAGE_RETURN_FAILURE);
 
 	transport_mode = nm_setting_infiniband_get_transport_mode (s_infiniband);
@@ -234,7 +235,7 @@ create_and_realize (NMDevice *device,
 {
 	NMDeviceInfinibandPrivate *priv = NM_DEVICE_INFINIBAND_GET_PRIVATE ((NMDeviceInfiniband *) device);
 	NMSettingInfiniband *s_infiniband;
-	NMPlatformError plerr;
+	int r;
 
 	s_infiniband = nm_connection_get_setting_infiniband (connection);
 	g_assert (s_infiniband);
@@ -268,13 +269,13 @@ create_and_realize (NMDevice *device,
 		return FALSE;
 	}
 
-	plerr = nm_platform_link_infiniband_add (nm_device_get_platform (device), priv->parent_ifindex, priv->p_key, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_infiniband_add (nm_device_get_platform (device), priv->parent_ifindex, priv->p_key, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create InfiniBand P_Key interface '%s' for '%s': %s",
 		             nm_device_get_iface (device),
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
@@ -286,7 +287,7 @@ static gboolean
 unrealize (NMDevice *device, GError **error)
 {
 	NMDeviceInfinibandPrivate *priv;
-	NMPlatformError plerr;
+	int r;
 
 	g_return_val_if_fail (NM_IS_DEVICE_INFINIBAND (device), FALSE);
 
@@ -298,12 +299,12 @@ unrealize (NMDevice *device, GError **error)
 		return FALSE;
 	}
 
-	plerr = nm_platform_link_infiniband_delete (nm_device_get_platform (device), priv->parent_ifindex, priv->p_key);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_infiniband_delete (nm_device_get_platform (device), priv->parent_ifindex, priv->p_key);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to remove InfiniBand P_Key interface '%s': %s",
 		             nm_device_get_iface (device),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index 1c7e6d51..96275947 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -22,7 +22,6 @@
 
 #include "nm-device-ip-tunnel.h"
 
-#include <string.h>
 #include <netinet/in.h>
 #include <linux/if.h>
 #include <linux/ip.h>
@@ -149,9 +148,13 @@ update_properties_from_ifindex (NMDevice *device, int ifindex)
 	NMDeviceIPTunnel *self = NM_DEVICE_IP_TUNNEL (device);
 	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE (self);
 	int parent_ifindex = 0;
-	in_addr_t local4 = 0, remote4 = 0;
-	struct in6_addr local6 = { 0 }, remote6 = { 0 };
-	guint8 ttl = 0, tos = 0, encap_limit = 0;
+	in_addr_t local4 = 0;
+	in_addr_t remote4 = 0;
+	struct in6_addr local6 = IN6ADDR_ANY_INIT;
+	struct in6_addr remote6 = IN6ADDR_ANY_INIT;
+	guint8 ttl = 0;
+	guint8 tos = 0;
+	guint8 encap_limit = 0;
 	gboolean pmtud = FALSE;
 	guint32 flow_label = 0;
 	NMIPTunnelFlags flags = NM_IP_TUNNEL_FLAG_NONE;
@@ -329,28 +332,28 @@ clear:
 		if (!address_equal_pn (AF_INET, priv->local, &local4)) {
 			g_clear_pointer (&priv->local, g_free);
 			if (local4)
-				priv->local = g_strdup (nm_utils_inet4_ntop (local4, NULL));
+				priv->local = nm_utils_inet4_ntop_dup (local4);
 			_notify (self, PROP_LOCAL);
 		}
 
 		if (!address_equal_pn (AF_INET, priv->remote, &remote4)) {
 			g_clear_pointer (&priv->remote, g_free);
 			if (remote4)
-				priv->remote = g_strdup (nm_utils_inet4_ntop (remote4, NULL));
+				priv->remote = nm_utils_inet4_ntop_dup (remote4);
 			_notify (self, PROP_REMOTE);
 		}
 	} else {
 		if (!address_equal_pn (AF_INET6, priv->local, &local6)) {
 			g_clear_pointer (&priv->local, g_free);
 			if (memcmp (&local6, &in6addr_any, sizeof (in6addr_any)))
-				priv->local = g_strdup (nm_utils_inet6_ntop (&local6, NULL));
+				priv->local = nm_utils_inet6_ntop_dup (&local6);
 			_notify (self, PROP_LOCAL);
 		}
 
 		if (!address_equal_pn (AF_INET6, priv->remote, &remote6)) {
 			g_clear_pointer (&priv->remote, g_free);
 			if (memcmp (&remote6, &in6addr_any, sizeof (in6addr_any)))
-				priv->remote = g_strdup (nm_utils_inet6_ntop (&remote6, NULL));
+				priv->remote = nm_utils_inet6_ntop_dup (&remote6);
 			_notify (self, PROP_REMOTE);
 		}
 	}
@@ -660,7 +663,6 @@ create_and_realize (NMDevice *device,
 {
 	const char *iface = nm_device_get_iface (device);
 	NMSettingIPTunnel *s_ip_tunnel;
-	NMPlatformError plerr;
 	NMPlatformLnkGre lnk_gre = { };
 	NMPlatformLnkSit lnk_sit = { };
 	NMPlatformLnkIpIp lnk_ipip = { };
@@ -668,6 +670,7 @@ create_and_realize (NMDevice *device,
 	const char *str;
 	gint64 val;
 	NMIPTunnelMode mode;
+	int r;
 
 	s_ip_tunnel = nm_connection_get_setting_ip_tunnel (connection);
 	g_assert (s_ip_tunnel);
@@ -713,13 +716,13 @@ create_and_realize (NMDevice *device,
 			lnk_gre.output_flags = NM_GRE_KEY;
 		}
 
-		plerr = nm_platform_link_gre_add (nm_device_get_platform (device), iface, &lnk_gre, out_plink);
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+		r = nm_platform_link_gre_add (nm_device_get_platform (device), iface, &lnk_gre, out_plink);
+		if (r < 0) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 			             "Failed to create GRE interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string_a (plerr));
+			             nm_strerror (r));
 			return FALSE;
 		}
 		break;
@@ -739,13 +742,13 @@ create_and_realize (NMDevice *device,
 		lnk_sit.tos = nm_setting_ip_tunnel_get_tos (s_ip_tunnel);
 		lnk_sit.path_mtu_discovery = nm_setting_ip_tunnel_get_path_mtu_discovery (s_ip_tunnel);
 
-		plerr = nm_platform_link_sit_add (nm_device_get_platform (device), iface, &lnk_sit, out_plink);
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+		r = nm_platform_link_sit_add (nm_device_get_platform (device), iface, &lnk_sit, out_plink);
+		if (r < 0) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 			             "Failed to create SIT interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string_a (plerr));
+			             nm_strerror (r));
 			return FALSE;
 		}
 		break;
@@ -765,13 +768,13 @@ create_and_realize (NMDevice *device,
 		lnk_ipip.tos = nm_setting_ip_tunnel_get_tos (s_ip_tunnel);
 		lnk_ipip.path_mtu_discovery = nm_setting_ip_tunnel_get_path_mtu_discovery (s_ip_tunnel);
 
-		plerr = nm_platform_link_ipip_add (nm_device_get_platform (device), iface, &lnk_ipip, out_plink);
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+		r = nm_platform_link_ipip_add (nm_device_get_platform (device), iface, &lnk_ipip, out_plink);
+		if (r < 0) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 			             "Failed to create IPIP interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string_a (plerr));
+			             nm_strerror (r));
 			return FALSE;
 		}
 		break;
@@ -820,21 +823,21 @@ create_and_realize (NMDevice *device,
 			lnk_ip6tnl.is_gre = TRUE;
 			lnk_ip6tnl.is_tap = (mode == NM_IP_TUNNEL_MODE_IP6GRETAP);
 
-			plerr = nm_platform_link_ip6gre_add (nm_device_get_platform (device),
-			                                     iface, &lnk_ip6tnl, out_plink);
+			r = nm_platform_link_ip6gre_add (nm_device_get_platform (device),
+			                                 iface, &lnk_ip6tnl, out_plink);
 		} else {
 			lnk_ip6tnl.proto = nm_setting_ip_tunnel_get_mode (s_ip_tunnel) == NM_IP_TUNNEL_MODE_IPIP6
 			                       ? IPPROTO_IPIP
 			                       : IPPROTO_IPV6;
-			plerr = nm_platform_link_ip6tnl_add (nm_device_get_platform (device),
-			                                     iface, &lnk_ip6tnl, out_plink);
+			r = nm_platform_link_ip6tnl_add (nm_device_get_platform (device),
+			                                 iface, &lnk_ip6tnl, out_plink);
 		}
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+		if (r < 0) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 			             "Failed to create IPv6 tunnel interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string_a (plerr));
+			             nm_strerror (r));
 			return FALSE;
 		}
 		break;
diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index 8ea4c8b5..1a6b64a4 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -210,7 +210,7 @@ update_properties (NMDevice *device)
 static NMSupplicantConfig *
 build_supplicant_config (NMDeviceMacsec *self, GError **error)
 {
-	NMSupplicantConfig *config = NULL;
+	gs_unref_object NMSupplicantConfig *config = NULL;
 	NMSettingMacsec *s_macsec;
 	NMSetting8021x *s_8021x;
 	NMConnection *connection;
@@ -218,19 +218,21 @@ build_supplicant_config (NMDeviceMacsec *self, GError **error)
 	guint32 mtu;
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
-	g_assert (connection);
+
+	g_return_val_if_fail (connection, NULL);
+
 	con_uuid = nm_connection_get_uuid (connection);
 	mtu = nm_platform_link_get_mtu (nm_device_get_platform (NM_DEVICE (self)),
 	                                nm_device_get_ifindex (NM_DEVICE (self)));
 
 	config = nm_supplicant_config_new (FALSE, FALSE);
 
-	s_macsec = (NMSettingMacsec *)
-		nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_MACSEC);
+	s_macsec = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_MACSEC);
+
+	g_return_val_if_fail (s_macsec, NULL);
 
 	if (!nm_supplicant_config_add_setting_macsec (config, s_macsec, error)) {
 		g_prefix_error (error, "macsec-setting: ");
-		g_object_unref (config);
 		return NULL;
 	}
 
@@ -238,11 +240,11 @@ build_supplicant_config (NMDeviceMacsec *self, GError **error)
 		s_8021x = nm_connection_get_setting_802_1x (connection);
 		if (!nm_supplicant_config_add_setting_8021x (config, s_8021x, con_uuid, mtu, TRUE, error)) {
 			g_prefix_error (error, "802-1x-setting: ");
-			g_clear_object (&config);
+			return NULL;
 		}
 	}
 
-	return config;
+	return g_steal_pointer (&config);
 }
 
 static void
@@ -588,6 +590,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	const char *setting_name;
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
 	if (!priv->supplicant.mgr)
@@ -654,7 +657,6 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
 	NMSettingMacsec *s_macsec;
 	NMPlatformLnkMacsec lnk = { };
 	int parent_ifindex;
@@ -666,6 +668,7 @@ create_and_realize (NMDevice *device,
 		} s;
 		guint64 u;
 	} sci;
+	int r;
 
 	s_macsec = nm_connection_get_setting_macsec (connection);
 	g_assert (s_macsec);
@@ -694,13 +697,13 @@ create_and_realize (NMDevice *device,
 	parent_ifindex = nm_device_get_ifindex (parent);
 	g_warn_if_fail (parent_ifindex > 0);
 
-	plerr = nm_platform_link_macsec_add (nm_device_get_platform (device), iface, parent_ifindex, &lnk, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_macsec_add (nm_device_get_platform (device), iface, parent_ifindex, &lnk, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create macsec interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index 2b212154..aa2a0ac0 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -22,7 +22,6 @@
 
 #include "nm-device-macvlan.h"
 
-#include <string.h>
 #include <linux/if_link.h>
 
 #include "nm-device-private.h"
@@ -227,10 +226,10 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
 	NMSettingMacvlan *s_macvlan;
 	NMPlatformLnkMacvlan lnk = { };
 	int parent_ifindex;
+	int r;
 
 	s_macvlan = nm_connection_get_setting_macvlan (connection);
 	g_return_val_if_fail (s_macvlan, FALSE);
@@ -255,14 +254,14 @@ create_and_realize (NMDevice *device,
 	lnk.no_promisc = !nm_setting_macvlan_get_promiscuous (s_macvlan);
 	lnk.tap = nm_setting_macvlan_get_tap (s_macvlan);
 
-	plerr = nm_platform_link_macvlan_add (nm_device_get_platform (device), iface, parent_ifindex, &lnk, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_macvlan_add (nm_device_get_platform (device), iface, parent_ifindex, &lnk, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create %s interface '%s' for '%s': %s",
 		             lnk.tap ? "macvtap" : "macvlan",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
index 74b4d710..3c310146 100644
--- a/src/devices/nm-device-ppp.c
+++ b/src/devices/nm-device-ppp.c
@@ -16,6 +16,7 @@
 
 #include "nm-device-ppp.h"
 
+#include "nm-ip4-config.h"
 #include "nm-act-request.h"
 #include "nm-device-factory.h"
 #include "nm-device-private.h"
@@ -106,7 +107,7 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 
 	if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
 		if (nm_device_activate_ip4_state_in_conf (device)) {
-			nm_device_activate_schedule_ip4_config_result (device, config);
+			nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 			return;
 		}
 	} else {
@@ -125,10 +126,12 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMActRequest *req;
 	GError *error = NULL;
 
-	req = nm_device_get_act_request (NM_DEVICE (self));
+	req = nm_device_get_act_request (device);
+
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
 
-	s_pppoe = (NMSettingPppoe *) nm_device_get_applied_setting ((NMDevice *) self, NM_TYPE_SETTING_PPPOE);
+	s_pppoe = nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPPOE);
+
 	g_return_val_if_fail (s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
 
 	g_clear_object (&priv->ip4_config);
@@ -170,23 +173,31 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
-	NMDevicePpp *self = NM_DEVICE_PPP (device);
-	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+	if (addr_family == AF_INET) {
+		NMDevicePpp *self = NM_DEVICE_PPP (device);
+		NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+
+		if (priv->ip4_config) {
+			if (out_config)
+				*out_config = g_steal_pointer (&priv->ip4_config);
+			else
+				g_clear_object (&priv->ip4_config);
+			return NM_ACT_STAGE_RETURN_SUCCESS;
+		}
 
-	if (priv->ip4_config) {
-		if (out_config)
-			*out_config = g_steal_pointer (&priv->ip4_config);
-		else
-			g_clear_object (&priv->ip4_config);
-		return NM_ACT_STAGE_RETURN_SUCCESS;
+		/* Wait IPCP termination */
+		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
-	/* Wait IPCP termination */
-	return NM_ACT_STAGE_RETURN_POSTPONE;
+	return NM_DEVICE_CLASS (nm_device_ppp_parent_class)->act_stage3_ip_config_start (device,
+	                                                                                 addr_family,
+	                                                                                 out_config,
+	                                                                                 out_failure_reason);
 }
 
 static gboolean
@@ -221,7 +232,7 @@ deactivate (NMDevice *device)
 	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
 
 	if (priv->ppp_manager) {
-		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL);
+		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL, NULL);
 		g_clear_object (&priv->ppp_manager);
 	}
 }
@@ -268,7 +279,7 @@ nm_device_ppp_class_init (NMDevicePppClass *klass)
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_PPP);
 
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->create_and_realize = create_and_realize;
 	device_class->deactivate = deactivate;
 	device_class->get_generic_capabilities = get_generic_capabilities;
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 66c715de..6e2372ab 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -26,6 +26,14 @@
 
 /* This file should only be used by subclasses of NMDevice */
 
+typedef enum {
+	NM_DEVICE_IP_STATE_NONE,
+	NM_DEVICE_IP_STATE_WAIT,
+	NM_DEVICE_IP_STATE_CONF,
+	NM_DEVICE_IP_STATE_DONE,
+	NM_DEVICE_IP_STATE_FAIL,
+} NMDeviceIPState;
+
 enum NMActStageReturn {
 	NM_ACT_STAGE_RETURN_FAILURE = 0, /* Hard failure of activation */
 	NM_ACT_STAGE_RETURN_SUCCESS,     /* Activation stage done */
@@ -34,7 +42,7 @@ enum NMActStageReturn {
 	NM_ACT_STAGE_RETURN_IP_DONE,     /* IP config stage is done (state IP_DONE),
 	                                    For the ip-config stage, this is similar to
 	                                    NM_ACT_STAGE_RETURN_SUCCESS, except that no
-	                                    IP config should be commited. */
+	                                    IP config should be committed. */
 	NM_ACT_STAGE_RETURN_IP_FAIL,     /* IP config stage failed (state IP_FAIL), activation may proceed */
 };
 
@@ -75,19 +83,51 @@ void nm_device_set_firmware_missing (NMDevice *self, gboolean missing);
 void nm_device_activate_schedule_stage1_device_prepare (NMDevice *device);
 void nm_device_activate_schedule_stage2_device_config (NMDevice *device);
 
-void nm_device_activate_schedule_ip4_config_result(NMDevice *device, NMIP4Config *config);
-void nm_device_activate_schedule_ip4_config_timeout (NMDevice *device);
-
-void nm_device_activate_schedule_ip6_config_result (NMDevice *device);
-void nm_device_activate_schedule_ip6_config_timeout (NMDevice *device);
-
-gboolean nm_device_activate_ip4_state_in_conf (NMDevice *device);
-gboolean nm_device_activate_ip4_state_in_wait (NMDevice *device);
-gboolean nm_device_activate_ip4_state_done (NMDevice *device);
-
-gboolean nm_device_activate_ip6_state_in_conf (NMDevice *device);
-gboolean nm_device_activate_ip6_state_in_wait (NMDevice *device);
-gboolean nm_device_activate_ip6_state_done (NMDevice *device);
+void nm_device_activate_schedule_ip_config_result (NMDevice *device,
+                                                   int addr_family,
+                                                   NMIPConfig *config);
+
+void nm_device_activate_schedule_ip_config_timeout (NMDevice *device,
+                                                    int addr_family);
+
+NMDeviceIPState nm_device_activate_get_ip_state (NMDevice *self,
+                                                 int addr_family);
+
+static inline gboolean
+nm_device_activate_ip4_state_in_conf (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET) == NM_DEVICE_IP_STATE_CONF;
+}
+
+static inline gboolean
+nm_device_activate_ip4_state_in_wait (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET) == NM_DEVICE_IP_STATE_WAIT;
+}
+
+static inline gboolean
+nm_device_activate_ip4_state_done (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET) == NM_DEVICE_IP_STATE_DONE;
+}
+
+static inline gboolean
+nm_device_activate_ip6_state_in_conf (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET6) == NM_DEVICE_IP_STATE_CONF;
+}
+
+static inline gboolean
+nm_device_activate_ip6_state_in_wait (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET6) == NM_DEVICE_IP_STATE_WAIT;
+}
+
+static inline gboolean
+nm_device_activate_ip6_state_done (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET6) == NM_DEVICE_IP_STATE_DONE;
+}
 
 void nm_device_set_dhcp_anycast_address (NMDevice *device, const char *addr);
 
@@ -106,14 +146,24 @@ void nm_device_queue_recheck_available (NMDevice *device,
                                         NMDeviceStateReason available_reason,
                                         NMDeviceStateReason unavailable_reason);
 
-void nm_device_set_wwan_ip4_config (NMDevice *device, NMIP4Config *config);
-void nm_device_set_wwan_ip6_config (NMDevice *device, NMIP6Config *config);
+void nm_device_set_dev2_ip_config (NMDevice *device,
+                                   int addr_family,
+                                   NMIPConfig *config);
 
 gboolean nm_device_hw_addr_is_explict (NMDevice *device);
 
-void nm_device_ip_method_failed (NMDevice *self, int family, NMDeviceStateReason reason);
+void nm_device_ip_method_failed (NMDevice *self, int addr_family, NMDeviceStateReason reason);
+
+gboolean nm_device_sysctl_ip_conf_set (NMDevice *self,
+                                       int addr_family,
+                                       const char *property,
+                                       const char *value);
+
+NMIP4Config *nm_device_ip4_config_new (NMDevice *self);
+
+NMIP6Config *nm_device_ip6_config_new (NMDevice *self);
 
-gboolean nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value);
+NMIPConfig *nm_device_ip_config_new (NMDevice *self, int addr_family);
 
 /*****************************************************************************/
 
@@ -147,9 +197,9 @@ void nm_device_commit_mtu (NMDevice *self);
 	)
 
 gboolean _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
-                                             GError **error, const char **whitelist);
+                                             GError **error, const char *const*whitelist);
 #define nm_device_hash_check_invalid_keys(hash, setting_name, error, ...) \
-	_nm_device_hash_check_invalid_keys (hash, setting_name, error, ((const char *[]) { __VA_ARGS__, NULL }))
+	_nm_device_hash_check_invalid_keys (hash, setting_name, error, NM_MAKE_STRV (__VA_ARGS__))
 
 gboolean nm_device_match_parent (NMDevice *device, const char *parent);
 gboolean nm_device_match_parent_hwaddr (NMDevice *device,
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index 0f76b23a..90360c9e 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -23,7 +23,6 @@
 #include "nm-device-tun.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <linux/if_tun.h>
 
@@ -231,9 +230,10 @@ create_and_realize (NMDevice *device,
 {
 	const char *iface = nm_device_get_iface (device);
 	NMPlatformLnkTun props = { };
-	NMPlatformError plerr;
 	NMSettingTun *s_tun;
-	gint64 owner, group;
+	gint64 owner;
+	gint64 group;
+	int r;
 
 	s_tun = nm_connection_get_setting_tun (connection);
 	g_return_val_if_fail (s_tun, FALSE);
@@ -261,17 +261,17 @@ create_and_realize (NMDevice *device,
 	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) {
+	r = nm_platform_link_tun_add (nm_device_get_platform (device),
+	                              iface,
+	                              &props,
+	                              out_plink,
+	                              NULL);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create TUN/TAP interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-veth.c b/src/devices/nm-device-veth.c
index 6f90758c..d7a59bae 100644
--- a/src/devices/nm-device-veth.c
+++ b/src/devices/nm-device-veth.c
@@ -20,9 +20,7 @@
 
 #include "nm-default.h"
 
-#include <errno.h>
 #include <stdlib.h>
-#include <string.h>
 
 #include "nm-device-veth.h"
 #include "nm-device-private.h"
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index b7f0c4e7..ace6a24b 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -241,7 +241,7 @@ create_and_realize (NMDevice *device,
 	NMSettingVlan *s_vlan;
 	int parent_ifindex;
 	guint vlan_id;
-	NMPlatformError plerr;
+	int r;
 
 	s_vlan = nm_connection_get_setting_vlan (connection);
 	g_assert (s_vlan);
@@ -271,18 +271,18 @@ create_and_realize (NMDevice *device,
 
 	vlan_id = nm_setting_vlan_get_id (s_vlan);
 
-	plerr = nm_platform_link_vlan_add (nm_device_get_platform (device),
-	                                   iface,
-	                                   parent_ifindex,
-	                                   vlan_id,
-	                                   nm_setting_vlan_get_flags (s_vlan),
-	                                   out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_vlan_add (nm_device_get_platform (device),
+	                               iface,
+	                               parent_ifindex,
+	                               vlan_id,
+	                               nm_setting_vlan_get_flags (s_vlan),
+	                               out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create VLAN interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
@@ -493,7 +493,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		parent_mtu_maybe_changed (parent_device, NULL, device);
 	}
 
-	s_vlan = (NMSettingVlan *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_VLAN);
+	s_vlan = nm_device_get_applied_setting (device, NM_TYPE_SETTING_VLAN);
 	if (s_vlan) {
 		gs_free NMVlanQosMapping *ingress_map = NULL;
 		gs_free NMVlanQosMapping *egress_map = NULL;
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index c34f4142..fc73c099 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -22,8 +22,6 @@
 
 #include "nm-device-vxlan.h"
 
-#include <string.h>
-
 #include "nm-device-private.h"
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
@@ -34,6 +32,7 @@
 #include "settings/nm-settings.h"
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
+#include "nm-core-internal.h"
 
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceVxlan);
@@ -170,11 +169,11 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
 	NMPlatformLnkVxlan props = { };
 	NMSettingVxlan *s_vxlan;
 	const char *str;
 	int ret;
+	int r;
 
 	s_vxlan = nm_connection_get_setting_vxlan (connection);
 	g_assert (s_vxlan);
@@ -213,13 +212,13 @@ create_and_realize (NMDevice *device,
 	props.l2miss = nm_setting_vxlan_get_l2_miss (s_vxlan);
 	props.l3miss = nm_setting_vxlan_get_l3_miss (s_vxlan);
 
-	plerr = nm_platform_link_vxlan_add (nm_device_get_platform (device), iface, &props, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_vxlan_add (nm_device_get_platform (device), iface, &props, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create VXLAN interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
@@ -386,6 +385,7 @@ update_connection (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE ((NMDeviceVxlan *) device);
 	NMSettingVxlan *s_vxlan = nm_connection_get_setting_vxlan (connection);
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	if (!s_vxlan) {
 		s_vxlan = (NMSettingVxlan *) nm_setting_vxlan_new ();
@@ -404,11 +404,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 	if (!address_matches (nm_setting_vxlan_get_remote (s_vxlan), priv->props.group, &priv->props.group6)) {
 		if (priv->props.group) {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_REMOTE,
-			              nm_utils_inet4_ntop (priv->props.group, NULL),
+			              nm_utils_inet4_ntop (priv->props.group, sbuf),
 			              NULL);
 		} else {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_REMOTE,
-			              nm_utils_inet6_ntop (&priv->props.group6, NULL),
+			              nm_utils_inet6_ntop (&priv->props.group6, sbuf),
 			              NULL);
 		}
 	}
@@ -416,11 +416,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 	if (!address_matches (nm_setting_vxlan_get_local (s_vxlan), priv->props.local, &priv->props.local6)) {
 		if (priv->props.local) {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_LOCAL,
-			              nm_utils_inet4_ntop (priv->props.local, NULL),
+			              nm_utils_inet4_ntop (priv->props.local, sbuf),
 			              NULL);
 		} else if (memcmp (&priv->props.local6, &in6addr_any, sizeof (in6addr_any))) {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_LOCAL,
-			              nm_utils_inet6_ntop (&priv->props.local6, NULL),
+			              nm_utils_inet6_ntop (&priv->props.local6, sbuf),
 			              NULL);
 		}
 	}
@@ -510,15 +510,15 @@ get_property (GObject *object, guint prop_id,
 		break;
 	case PROP_GROUP:
 		if (priv->props.group)
-			g_value_set_string (value, nm_utils_inet4_ntop (priv->props.group, NULL));
+			g_value_take_string (value, nm_utils_inet4_ntop_dup (priv->props.group));
 		else if (!IN6_IS_ADDR_UNSPECIFIED (&priv->props.group6))
-			g_value_set_string (value, nm_utils_inet6_ntop (&priv->props.group6, NULL));
+			g_value_take_string (value, nm_utils_inet6_ntop_dup (&priv->props.group6));
 		break;
 	case PROP_LOCAL:
 		if (priv->props.local)
-			g_value_set_string (value, nm_utils_inet4_ntop (priv->props.local, NULL));
+			g_value_take_string (value, nm_utils_inet4_ntop_dup (priv->props.local));
 		else if (!IN6_IS_ADDR_UNSPECIFIED (&priv->props.local6))
-			g_value_set_string (value, nm_utils_inet6_ntop (&priv->props.local6, NULL));
+			g_value_take_string (value, nm_utils_inet6_ntop_dup (&priv->props.local6));
 		break;
 	case PROP_TOS:
 		g_value_set_uchar (value, priv->props.tos);
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
index 62ec0274..a9eb1ab4 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/devices/nm-device-wireguard.c
@@ -21,24 +21,128 @@
 
 #include "nm-device-wireguard.h"
 
+#include "nm-setting-wireguard.h"
+#include "nm-core-internal.h"
+#include "nm-utils/nm-secret-utils.h"
 #include "nm-device-private.h"
 #include "platform/nm-platform.h"
+#include "platform/nmp-object.h"
 #include "nm-device-factory.h"
+#include "nm-active-connection.h"
+#include "nm-act-request.h"
+#include "dns/nm-dns-manager.h"
 
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceWireGuard);
 
 /*****************************************************************************/
 
+/* TODO: activate profile with peer preshared-key-flags=2. On first activation, the secret is
+ *   requested (good). Enter it and connect. Reactivate the profile, now there is no password
+ *   prompt, as the secret is cached (good??). */
+
+/* TODO: unlike for other VPNs, we don't inject a direct route to the peers. That means,
+ *   you might get a routing sceneraio where the peer (VPN server) is reachable via the VPN.
+ *   How we handle adding routes to external gateway for other peers, has severe issues
+ *   as well. We may use policy-routing like wg-quick does. See also disussions at
+ *   https://www.wireguard.com/netns/#improving-the-classic-solutions */
+
+/* TODO: honor the TTL of DNS to determine when to retry resolving endpoints. */
+
+/* TODO: when we get multiple IP addresses when resolving a peer endpoint. We currently
+ *   just take the first from GAI. We should only accept AAAA/IPv6 if we also have a suitable
+ *   IPv6 address. The problem is, that we have to recheck that when IP addressing on other
+ *   interfaces changes. This makes it almost too cumbersome to implement. */
+
+/*****************************************************************************/
+
+G_STATIC_ASSERT (NM_WIREGUARD_PUBLIC_KEY_LEN   == NMP_WIREGUARD_PUBLIC_KEY_LEN);
+G_STATIC_ASSERT (NM_WIREGUARD_SYMMETRIC_KEY_LEN == NMP_WIREGUARD_SYMMETRIC_KEY_LEN);
+
+/*****************************************************************************/
+
+#define LINK_CONFIG_RATE_LIMIT_NSEC (50 * NM_UTILS_NS_PER_MSEC)
+
+/* a special @next_try_at_nsec timestamp indicating that we should try again as soon as possible. */
+#define NEXT_TRY_AT_NSEC_ASAP ((gint64) G_MAXINT64)
+
+/* a special @next_try_at_nsec timestamp that is
+ *  - positive (indicating resolve-checks are enabled)
+ *  - already in the past (we use the absolute timestamp of 1nsec for that). */
+#define NEXT_TRY_AT_NSEC_PAST ((gint64) 1)
+
+/* like %NEXT_TRY_AT_NSEC_ASAP, but used for indicating to retry ASAP for a @retry_in_msec value.
+ * That is a relative time duraction, contrary to @next_try_at_nsec which is an absolute
+ * timestamp. */
+#define RETRY_IN_MSEC_ASAP ((gint64) G_MAXINT64)
+
+#define RETRY_IN_MSEC_MAX ((gint64) (30 * 60 * 1000))
+
+typedef enum {
+	LINK_CONFIG_MODE_FULL,
+	LINK_CONFIG_MODE_REAPPLY,
+	LINK_CONFIG_MODE_ASSUME,
+	LINK_CONFIG_MODE_ENDPOINTS,
+} LinkConfigMode;
+
+typedef struct {
+	GCancellable *cancellable;
+
+	NMSockAddrUnion sockaddr;
+
+	/* the timestamp (in nm_utils_get_monotonic_timestamp_ns() scale) when we want
+	 * to retry resolving the endpoint (again).
+	 *
+	 * It may be set to %NEXT_TRY_AT_NSEC_ASAP to indicate to re-resolve as soon as possible.
+	 *
+	 * A @sockaddr is either fixed or it has
+	 *   - @cancellable set to indicate an ongoing request
+	 *   - @next_try_at_nsec set to a positive value, indicating when
+	 *     we ought to retry. */
+	gint64 next_try_at_nsec;
+
+	guint resolv_fail_count;
+} PeerEndpointResolveData;
+
+typedef struct {
+	NMWireGuardPeer *peer;
+
+	NMDeviceWireGuard *self;
+
+	CList lst_peers;
+
+	PeerEndpointResolveData ep_resolv;
+
+	/* dirty flag used during _peers_update_all(). */
+	bool dirty_update_all:1;
+} PeerData;
+
 NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWireGuard,
 	PROP_PUBLIC_KEY,
 	PROP_LISTEN_PORT,
 	PROP_FWMARK,
 );
 
+typedef struct {
+
+	NMDnsManager *dns_manager;
+
+	NMPlatformLnkWireGuard lnk_curr;
+	NMActRequestGetSecretsCallId *secrets_call_id;
+
+	CList lst_peers_head;
+	GHashTable *peers;
+
+	gint64 resolve_next_try_at;
+	guint  resolve_next_try_id;
+
+	gint64 link_config_last_at;
+	guint  link_config_delayed_id;
+} NMDeviceWireGuardPrivate;
+
 struct _NMDeviceWireGuard {
 	NMDevice parent;
-	NMPlatformLnkWireGuard props;
+	NMDeviceWireGuardPrivate _priv;
 };
 
 struct _NMDeviceWireGuardClass {
@@ -47,25 +151,710 @@ struct _NMDeviceWireGuardClass {
 
 G_DEFINE_TYPE (NMDeviceWireGuard, nm_device_wireguard, NM_TYPE_DEVICE)
 
-/******************************************************************/
+#define NM_DEVICE_WIREGUARD_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceWireGuard, NM_IS_DEVICE_WIREGUARD, NMDevice)
+
+/*****************************************************************************/
+
+static void _peers_resolve_start (NMDeviceWireGuard *self,
+                                  PeerData *peer_data);
+
+static void _peers_resolve_retry_reschedule (NMDeviceWireGuard *self,
+                                             gint64 new_next_try_at_nsec);
+
+static gboolean link_config_delayed_resolver_cb (gpointer user_data);
+
+static gboolean link_config_delayed_ratelimit_cb (gpointer user_data);
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_link_config_mode_to_string, LinkConfigMode,
+	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (NULL),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_FULL,      "full"),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_REAPPLY,   "reapply"),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_ASSUME,    "assume"),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_ENDPOINTS, "endpoints"),
+);
+
+/*****************************************************************************/
+
+static gboolean
+_peer_data_equal (gconstpointer ptr_a, gconstpointer ptr_b)
+{
+	const PeerData *peer_data_a = ptr_a;
+	const PeerData *peer_data_b = ptr_b;
+
+	return nm_streq (nm_wireguard_peer_get_public_key (peer_data_a->peer),
+	                 nm_wireguard_peer_get_public_key (peer_data_b->peer));
+}
+
+static guint
+_peer_data_hash (gconstpointer ptr)
+{
+	const PeerData *peer_data = ptr;
+
+	return nm_hash_str (nm_wireguard_peer_get_public_key (peer_data->peer));
+}
+
+static PeerData *
+_peers_find (NMDeviceWireGuardPrivate *priv,
+             NMWireGuardPeer *peer)
+{
+	nm_assert (peer);
+
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (PeerData, peer) == 0);
+
+	return g_hash_table_lookup (priv->peers, &peer);
+}
+
+static void
+_peers_remove (NMDeviceWireGuardPrivate *priv,
+               PeerData *peer_data)
+{
+	nm_assert (peer_data);
+	nm_assert (g_hash_table_lookup (priv->peers, peer_data) == peer_data);
+
+	if (!g_hash_table_remove (priv->peers, peer_data))
+		nm_assert_not_reached ();
+
+	c_list_unlink_stale (&peer_data->lst_peers);
+	nm_wireguard_peer_unref (peer_data->peer);
+	nm_clear_g_cancellable (&peer_data->ep_resolv.cancellable);
+	g_slice_free (PeerData, peer_data);
+
+	if (c_list_is_empty (&peer_data->lst_peers)) {
+		nm_clear_g_source (&priv->resolve_next_try_id);
+		nm_clear_g_source (&priv->link_config_delayed_id);
+	}
+}
+
+static PeerData *
+_peers_add (NMDeviceWireGuard *self,
+            NMWireGuardPeer *peer)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data;
+
+	nm_assert (peer);
+	nm_assert (nm_wireguard_peer_is_sealed (peer));
+	nm_assert (!_peers_find (priv, peer));
+
+	peer_data = g_slice_new (PeerData);
+	*peer_data = (PeerData) {
+		.self = self,
+		.peer = nm_wireguard_peer_ref (peer),
+		.ep_resolv = {
+			.sockaddr = NM_SOCK_ADDR_UNION_INIT_UNSPEC,
+		},
+	};
+
+	c_list_link_tail (&priv->lst_peers_head, &peer_data->lst_peers);
+	if (!nm_g_hash_table_add (priv->peers, peer_data))
+		nm_assert_not_reached ();
+	return peer_data;
+}
+
+static gboolean
+_peers_resolve_retry_timeout (gpointer user_data)
+{
+	NMDeviceWireGuard *self = user_data;
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data;
+	gint64 now;
+	gint64 next;
+
+	priv->resolve_next_try_id = 0;
+
+	_LOGT (LOGD_DEVICE, "wireguard-peers: rechecking peer endpoints...");
+
+	now = nm_utils_get_monotonic_timestamp_ns ();
+	next = G_MAXINT64;
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+		if (peer_data->ep_resolv.next_try_at_nsec <= 0)
+			continue;
+
+		if (peer_data->ep_resolv.cancellable) {
+			/* we are currently resolving a name. We don't need the global
+			 * watchdog to guard this peer. No need to adjust @next for
+			 * this one, when the currently ongoing resolving completes, we
+			 * may reschedule. Skip. */
+			continue;
+		}
+
+		if (   peer_data->ep_resolv.next_try_at_nsec == NEXT_TRY_AT_NSEC_ASAP
+		    || now >= peer_data->ep_resolv.next_try_at_nsec) {
+			_peers_resolve_start (self, peer_data);
+			/* same here. Now we are resolving. We don't need the global
+			 * watchdog. Skip w.r.t. finding @next. */
+			continue;
+		}
+
+		if (next > peer_data->ep_resolv.next_try_at_nsec)
+			next = peer_data->ep_resolv.next_try_at_nsec;
+	}
+	if (next < G_MAXINT64)
+		_peers_resolve_retry_reschedule (self, next);
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_peers_resolve_retry_reschedule (NMDeviceWireGuard *self,
+                                 gint64 new_next_try_at_nsec)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	guint32 interval_ms;
+	gint64 now;
+
+	nm_assert (new_next_try_at_nsec > 0);
+	nm_assert (new_next_try_at_nsec != NEXT_TRY_AT_NSEC_ASAP);
+
+	if (   priv->resolve_next_try_id
+	    && priv->resolve_next_try_at <= new_next_try_at_nsec) {
+		/* we already have an earlier timeout scheduled (possibly for
+		 * another peer that expires sooner). Don't reschedule now.
+		 * Even if the scheduled timeout expires too early, we will
+		 * compute the right next-timeout and reschedule then. */
+		return;
+	}
+
+	now = nm_utils_get_monotonic_timestamp_ns ();
+
+	/* schedule at most one day ahead. No problem if we expire earlier
+	 * than expected. Also, rate-limit to 500 msec. */
+	interval_ms = NM_CLAMP ((new_next_try_at_nsec - now) / NM_UTILS_NS_PER_MSEC,
+	                        (gint64) 500,
+	                        (gint64) (24*60*60*1000));
+
+	_LOGT (LOGD_DEVICE, "wireguard-peers: schedule rechecking peer endpoints in %u msec",
+	       interval_ms);
+
+	nm_clear_g_source (&priv->resolve_next_try_id);
+	priv->resolve_next_try_at = new_next_try_at_nsec;
+	priv->resolve_next_try_id = g_timeout_add (interval_ms,
+	                                           _peers_resolve_retry_timeout,
+	                                           self);
+}
+
+static void
+_peers_resolve_retry_reschedule_for_peer (NMDeviceWireGuard *self,
+                                          PeerData *peer_data,
+                                          gint64 retry_in_msec)
+{
+	nm_assert (retry_in_msec >= 0);
+
+	if (retry_in_msec == RETRY_IN_MSEC_ASAP) {
+		_peers_resolve_start (self, peer_data);
+		return;
+	}
+
+	peer_data->ep_resolv.next_try_at_nsec =   nm_utils_get_monotonic_timestamp_ns ()
+	                                        + (retry_in_msec * NM_UTILS_NS_PER_MSEC);
+	_peers_resolve_retry_reschedule (self, peer_data->ep_resolv.next_try_at_nsec);
+}
+
+static gint64
+_peers_retry_in_msec (PeerData *peer_data,
+                      gboolean after_failure)
+{
+	if (peer_data->ep_resolv.next_try_at_nsec == NEXT_TRY_AT_NSEC_ASAP) {
+		peer_data->ep_resolv.resolv_fail_count = 0;
+		return RETRY_IN_MSEC_ASAP;
+	}
+
+	if (after_failure) {
+		if (peer_data->ep_resolv.resolv_fail_count < G_MAXUINT)
+			peer_data->ep_resolv.resolv_fail_count++;
+	} else
+		peer_data->ep_resolv.resolv_fail_count = 0;
+
+	if (!after_failure)
+		return RETRY_IN_MSEC_MAX;
 
-static GVariant *
-get_public_key_as_variant (const NMDeviceWireGuard *self)
+	if (peer_data->ep_resolv.resolv_fail_count > 20)
+		return RETRY_IN_MSEC_MAX;
+
+	/* double the retry-time, starting with one second. */
+	return NM_MIN (RETRY_IN_MSEC_MAX,
+	               (1u << peer_data->ep_resolv.resolv_fail_count) * 500);
+}
+
+static void
+_peers_resolve_cb (GObject *source_object,
+                   GAsyncResult *res,
+                   gpointer user_data)
+{
+	NMDeviceWireGuard *self;
+	PeerData *peer_data;
+	gs_free_error GError *resolv_error = NULL;
+	GList *list;
+	gboolean changed = FALSE;
+	NMSockAddrUnion sockaddr;
+	gint64 retry_in_msec;
+	char s_sockaddr[100];
+	char s_retry[100];
+
+	list = g_resolver_lookup_by_name_finish (G_RESOLVER (source_object), res, &resolv_error);
+
+	if (nm_utils_error_is_cancelled (resolv_error, FALSE))
+		return;
+
+	peer_data = user_data;
+	self = peer_data->self;
+
+	g_clear_object (&peer_data->ep_resolv.cancellable);
+
+	nm_assert ((!resolv_error) != (!list));
+
+#define _retry_in_msec_to_string(retry_in_msec, s_retry) \
+	({ \
+		gint64 _retry_in_msec = (retry_in_msec); \
+		\
+		  _retry_in_msec == RETRY_IN_MSEC_ASAP \
+		? "right away" \
+		: nm_sprintf_buf (s_retry, "in %"G_GINT64_FORMAT" msec", _retry_in_msec); \
+	})
+
+	if (   resolv_error
+	    && !g_error_matches (resolv_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND)) {
+		retry_in_msec = _peers_retry_in_msec (peer_data, TRUE);
+
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: failure to resolve endpoint \"%s\": %s (retry %s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_wireguard_peer_get_endpoint (peer_data->peer),
+		       resolv_error->message,
+		       _retry_in_msec_to_string (retry_in_msec, s_retry));
+
+		_peers_resolve_retry_reschedule_for_peer (self, peer_data, retry_in_msec);
+		return;
+	}
+
+	sockaddr = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+
+	if (!resolv_error) {
+		GList *iter;
+
+		for (iter = list; iter; iter = iter->next) {
+			GInetAddress *a = iter->data;
+			GSocketFamily f = g_inet_address_get_family (a);
+
+			if (f == G_SOCKET_FAMILY_IPV4) {
+				nm_assert (g_inet_address_get_native_size (a) == sizeof (struct in_addr));
+				sockaddr.in = (struct sockaddr_in) {
+					.sin_family = AF_INET,
+					.sin_port   = htons (nm_sock_addr_endpoint_get_port (_nm_wireguard_peer_get_endpoint (peer_data->peer))),
+				};
+				memcpy (&sockaddr.in.sin_addr, g_inet_address_to_bytes (a), sizeof (struct in_addr));
+				break;
+			}
+			if (f == G_SOCKET_FAMILY_IPV6) {
+				nm_assert (g_inet_address_get_native_size (a) == sizeof (struct in6_addr));
+				sockaddr.in6 = (struct sockaddr_in6) {
+					.sin6_family   = AF_INET6,
+					.sin6_port     = htons (nm_sock_addr_endpoint_get_port (_nm_wireguard_peer_get_endpoint (peer_data->peer))),
+					.sin6_scope_id = 0,
+					.sin6_flowinfo = 0,
+				};
+				memcpy (&sockaddr.in6.sin6_addr, g_inet_address_to_bytes (a), sizeof (struct in6_addr));
+				break;
+			}
+		}
+
+		g_list_free_full (list, g_object_unref);
+	}
+
+	if (sockaddr.sa.sa_family == AF_UNSPEC) {
+		/* we failed to resolve the name. There is no need to reset the previous
+		 * sockaddr. Either it was already AF_UNSPEC, or we had a good name
+		 * from resolving before. In that case, we don't want to throw away
+		 * a possibly good IP address, since WireGuard supports automatic roaming
+		 * anyway. Either the IP address is still good (and we would wrongly
+		 * reject it), or it isn't -- in which case it does not hurt much. */
+	} else {
+		if (nm_sock_addr_union_cmp (&peer_data->ep_resolv.sockaddr, &sockaddr) != 0)
+			changed = TRUE;
+		peer_data->ep_resolv.sockaddr = sockaddr;
+	}
+
+	if (   resolv_error
+	    || peer_data->ep_resolv.sockaddr.sa.sa_family == AF_UNSPEC) {
+		/* while it technically did not fail, something is probably odd. Retry frequently to
+		 * resolve the name, like we would do for normal failures. */
+		retry_in_msec = _peers_retry_in_msec (peer_data, TRUE);
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: no %sresults for endpoint \"%s\" (retry %s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       resolv_error ? "" : "suitable ",
+		       nm_wireguard_peer_get_endpoint (peer_data->peer),
+		       _retry_in_msec_to_string (retry_in_msec, s_retry));
+	} else {
+		retry_in_msec = _peers_retry_in_msec (peer_data, FALSE);
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: endpoint \"%s\" resolved to %s (retry %s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_wireguard_peer_get_endpoint (peer_data->peer),
+		       nm_sock_addr_union_to_string (&peer_data->ep_resolv.sockaddr, s_sockaddr, sizeof (s_sockaddr)),
+		       _retry_in_msec_to_string (retry_in_msec, s_retry));
+	}
+
+	_peers_resolve_retry_reschedule_for_peer (self, peer_data, retry_in_msec);
+
+	if (changed) {
+		NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+		/* schedule the job in the background, to give multiple resolve events time
+		 * to complete. */
+		nm_clear_g_source (&priv->link_config_delayed_id);
+		priv->link_config_delayed_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE + 1,
+		                                                link_config_delayed_resolver_cb,
+		                                                self,
+		                                                NULL);
+	}
+}
+
+static void
+_peers_resolve_start (NMDeviceWireGuard *self,
+                      PeerData *peer_data)
+{
+	gs_unref_object GResolver *resolver = NULL;
+	const char *host;
+
+	resolver = g_resolver_get_default ();
+
+	nm_assert (!peer_data->ep_resolv.cancellable);
+
+	peer_data->ep_resolv.cancellable = g_cancellable_new ();
+
+	/* set a special next-try timestamp. It is positive, and indicates
+	 * that we are in the process of trying.
+	 * This timestamp however already lies in the past, but that is correct,
+	 * because we are currently in the process of trying. We will determine
+	 * a next-try timestamp once the try completes. */
+	peer_data->ep_resolv.next_try_at_nsec = NEXT_TRY_AT_NSEC_PAST;
+
+	host = nm_sock_addr_endpoint_get_host (_nm_wireguard_peer_get_endpoint (peer_data->peer));
+
+	g_resolver_lookup_by_name_async (resolver,
+	                                 host,
+	                                 peer_data->ep_resolv.cancellable,
+	                                 _peers_resolve_cb,
+	                                 peer_data);
+
+	_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: resolving name \"%s\" for endpoint \"%s\"...",
+	       nm_wireguard_peer_get_public_key (peer_data->peer),
+	       host,
+	       nm_wireguard_peer_get_endpoint (peer_data->peer));
+}
+
+static void
+_peers_resolve_reresolve_all (NMDeviceWireGuard *self)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data;
+
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+		if (peer_data->ep_resolv.cancellable) {
+			/* remember to retry when the currently ongoing request completes. */
+			peer_data->ep_resolv.next_try_at_nsec = NEXT_TRY_AT_NSEC_ASAP;
+		} else if (peer_data->ep_resolv.next_try_at_nsec <= 0) {
+			/* this peer does not require resolving the name. Skip it. */
+		} else {
+			/* we have a next-try scheduled. Restart right away. */
+			peer_data->ep_resolv.resolv_fail_count = 0;
+			_peers_resolve_start (self, peer_data);
+		}
+	}
+}
+
+static gboolean
+_peers_update (NMDeviceWireGuard *self,
+               PeerData *peer_data,
+               NMWireGuardPeer *peer,
+               gboolean force_update)
+{
+	nm_auto_unref_wgpeer NMWireGuardPeer *old_peer = NULL;
+	NMSockAddrEndpoint *old_endpoint;
+	NMSockAddrEndpoint *endpoint;
+	gboolean endpoint_changed = FALSE;
+	gboolean changed;
+	NMSockAddrUnion sockaddr;
+	gboolean sockaddr_fixed;
+	char sockaddr_sbuf[100];
+
+	nm_assert (peer);
+	nm_assert (nm_wireguard_peer_is_sealed (peer));
+
+	if (   peer == peer_data->peer
+	    && !force_update)
+		return FALSE;
+
+	changed = (nm_wireguard_peer_cmp (peer,
+	                                  peer_data->peer,
+	                                  NM_SETTING_COMPARE_FLAG_EXACT) != 0);
+
+	old_peer = peer_data->peer;
+	peer_data->peer = nm_wireguard_peer_ref (peer);
+
+	old_endpoint = old_peer ? _nm_wireguard_peer_get_endpoint (old_peer) : NULL;
+	endpoint     = peer     ? _nm_wireguard_peer_get_endpoint (peer)     : NULL;
+
+	endpoint_changed = (   endpoint != old_endpoint
+	                    && (   !old_endpoint
+	                        || !endpoint
+	                        || !nm_streq (nm_sock_addr_endpoint_get_endpoint (old_endpoint),
+	                                      nm_sock_addr_endpoint_get_endpoint (endpoint))));
+
+	if (   !force_update
+	    && !endpoint_changed) {
+		/* nothing to do. */
+		return changed;
+	}
+
+	sockaddr = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+	sockaddr_fixed = TRUE;
+	if (   endpoint
+	    && nm_sock_addr_endpoint_get_host (endpoint)) {
+		if (!nm_sock_addr_endpoint_get_fixed_sockaddr (endpoint, &sockaddr)) {
+			/* we have an endpoint, but it's not a static IP address. We need to resolve
+			 * the names. */
+			sockaddr_fixed = FALSE;
+		}
+	}
+
+	if (nm_sock_addr_union_cmp (&peer_data->ep_resolv.sockaddr, &sockaddr) != 0)
+		changed = TRUE;
+
+	nm_clear_g_cancellable (&peer_data->ep_resolv.cancellable);
+
+	peer_data->ep_resolv = (PeerEndpointResolveData) {
+		.sockaddr          = sockaddr,
+		.resolv_fail_count = 0,
+		.cancellable       = NULL,
+		.next_try_at_nsec  = 0,
+	};
+
+	if (!endpoint) {
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: no endpoint configured",
+		       nm_wireguard_peer_get_public_key (peer_data->peer));
+	} else if (!nm_sock_addr_endpoint_get_host (endpoint)) {
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: invalid endpoint \"%s\"",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_sock_addr_endpoint_get_endpoint (endpoint));
+	} else if (sockaddr_fixed) {
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: fixed endpoint \"%s\" (%s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_sock_addr_endpoint_get_endpoint (endpoint),
+		       nm_sock_addr_union_to_string (&peer_data->ep_resolv.sockaddr, sockaddr_sbuf, sizeof (sockaddr_sbuf)));
+	} else
+		_peers_resolve_start (self, peer_data);
+
+	return changed;
+}
+
+static void
+_peers_remove_all (NMDeviceWireGuardPrivate *priv)
 {
-	return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-	                                  self->props.public_key, sizeof (self->props.public_key), 1);
+	PeerData *peer_data;
+
+	while ((peer_data = c_list_first_entry (&priv->lst_peers_head, PeerData, lst_peers)))
+		_peers_remove (priv, peer_data);
 }
 
 static void
+_peers_update_all (NMDeviceWireGuard *self,
+                   NMSettingWireGuard *s_wg,
+                   gboolean *out_peers_removed)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data_safe;
+	PeerData *peer_data;
+	guint i, n;
+	gboolean peers_removed = FALSE;
+
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers)
+		peer_data->dirty_update_all = TRUE;
+
+	n = nm_setting_wireguard_get_peers_len (s_wg);
+	for (i = 0; i < n; i++) {
+		NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i);
+		gboolean added = FALSE;
+
+		peer_data = _peers_find (priv, peer);
+		if (!peer_data) {
+			peer_data = _peers_add (self, peer);
+			added = TRUE;
+		}
+		_peers_update (self, peer_data, peer, added);
+		peer_data->dirty_update_all = FALSE;
+	}
+
+	c_list_for_each_entry_safe (peer_data, peer_data_safe, &priv->lst_peers_head, lst_peers) {
+		if (peer_data->dirty_update_all) {
+			_peers_remove (priv, peer_data);
+			peers_removed = TRUE;
+		}
+	}
+
+	NM_SET_OUT (out_peers_removed, peers_removed);
+}
+
+static void
+_peers_get_platform_list (NMDeviceWireGuardPrivate *priv,
+                          LinkConfigMode config_mode,
+                          NMPWireGuardPeer **out_peers,
+                          NMPlatformWireGuardChangePeerFlags **out_peer_flags,
+                          guint *out_len,
+                          GArray **out_allowed_ips_data)
+{
+	gs_free NMPWireGuardPeer *plpeers = NULL;
+	gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL;
+	gs_unref_array GArray *allowed_ips = NULL;
+	PeerData *peer_data;
+	guint i_good;
+	guint n_aip;
+	guint i_aip;
+	guint len;
+	guint i;
+
+	nm_assert (out_peers && !*out_peers);
+	nm_assert (out_peer_flags && !*out_peer_flags);
+	nm_assert (out_len && *out_len == 0);
+	nm_assert (out_allowed_ips_data && !*out_allowed_ips_data);
+
+	len = g_hash_table_size (priv->peers);
+
+	nm_assert (len == c_list_length (&priv->lst_peers_head));
+
+	if (len == 0)
+		return;
+
+	plpeers = g_new0 (NMPWireGuardPeer, len);
+	plpeer_flags = g_new0 (NMPlatformWireGuardChangePeerFlags, len);
+
+	i_good = 0;
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+		NMPlatformWireGuardChangePeerFlags *plf = &plpeer_flags[i_good];
+		NMPWireGuardPeer *plp = &plpeers[i_good];
+		NMSettingSecretFlags psk_secret_flags;
+
+		if (!nm_utils_base64secret_decode (nm_wireguard_peer_get_public_key (peer_data->peer),
+		                                   sizeof (plp->public_key),
+		                                   plp->public_key))
+			continue;
+
+		*plf = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE;
+
+		plp->persistent_keepalive_interval = nm_wireguard_peer_get_persistent_keepalive (peer_data->peer);
+		if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+		                            LINK_CONFIG_MODE_REAPPLY))
+			*plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL;
+
+		/* if the peer has an endpoint but it is not yet resolved (not ready),
+		 * we still configure it and leave the endpoint unspecified. Later,
+		 * when we can resolve the endpoint, we will update. */
+		plp->endpoint = peer_data->ep_resolv.sockaddr;
+		if (plp->endpoint.sa.sa_family == AF_UNSPEC) {
+			/* we don't actually ever clear endpoints, if we don't have better information. */
+		} else
+			*plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT;
+
+		if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+		                            LINK_CONFIG_MODE_REAPPLY)) {
+			psk_secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer_data->peer);
+			if (!NM_FLAGS_HAS (psk_secret_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
+				if (   !nm_utils_base64secret_decode (nm_wireguard_peer_get_preshared_key (peer_data->peer),
+				                                      sizeof (plp->preshared_key),
+				                                      plp->preshared_key)
+				    && config_mode == LINK_CONFIG_MODE_FULL)
+					goto skip;
+			}
+			*plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY;
+		}
+
+		if (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+		                               LINK_CONFIG_MODE_REAPPLY)
+		    && ((n_aip = nm_wireguard_peer_get_allowed_ips_len (peer_data->peer)) > 0)) {
+			if (!allowed_ips)
+				allowed_ips = g_array_new (FALSE, FALSE, sizeof (NMPWireGuardAllowedIP));
+
+			*plf |=   NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS
+			        | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS;
+
+			plp->_construct_idx_start = allowed_ips->len;
+			for (i_aip = 0; i_aip < n_aip; i_aip++) {
+				const char *aip;
+				NMIPAddr addrbin = { };
+				int addr_family;
+				gboolean valid;
+				int prefix;
+
+				aip = nm_wireguard_peer_get_allowed_ip (peer_data->peer, i_aip, &valid);
+				if (   !valid
+				    || !nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC,
+				                                          aip,
+				                                          &addr_family,
+				                                          &addrbin,
+				                                          &prefix)) {
+					/* the address is really not expected to be invalid, because then
+					 * the connection would not verify. Anyway, silently skip it. */
+					continue;
+				}
+
+				if (prefix == -1)
+					prefix = addr_family == AF_INET ? 32 : 128;
+
+				g_array_append_val (allowed_ips,
+				                    ((NMPWireGuardAllowedIP) {
+				                        .family = addr_family,
+				                        .mask = prefix,
+				                        .addr = addrbin,
+				                    }));
+			}
+			plp->_construct_idx_end = allowed_ips->len;
+		}
+
+		i_good++;
+		continue;
+
+skip:
+		memset (plp, 0, sizeof (*plp));
+	}
+
+	if (i_good == 0)
+		return;
+
+	for (i = 0; i < i_good; i++) {
+		NMPWireGuardPeer *plp = &plpeers[i];
+		guint l;
+
+		if (plp->_construct_idx_end == 0) {
+			nm_assert (plp->_construct_idx_start == 0);
+			plp->allowed_ips = NULL;
+			plp->allowed_ips_len = 0;
+		} else {
+			nm_assert (plp->_construct_idx_start < plp->_construct_idx_end);
+			l = plp->_construct_idx_end - plp->_construct_idx_start;
+			plp->allowed_ips = &g_array_index (allowed_ips, NMPWireGuardAllowedIP, plp->_construct_idx_start);
+			plp->allowed_ips_len = l;
+		}
+	}
+	*out_peers            = g_steal_pointer (&plpeers);
+	*out_peer_flags       = g_steal_pointer (&plpeer_flags);;
+	*out_len              = i_good;
+	*out_allowed_ips_data = g_steal_pointer (&allowed_ips);
+}
+
+/*****************************************************************************/
+
+static void
 update_properties (NMDevice *device)
 {
 	NMDeviceWireGuard *self;
+	NMDeviceWireGuardPrivate *priv;
 	const NMPlatformLink *plink;
 	const NMPlatformLnkWireGuard *props = NULL;
 	int ifindex;
 
 	g_return_if_fail (NM_IS_DEVICE_WIREGUARD (device));
 	self = NM_DEVICE_WIREGUARD (device);
+	priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
 
 	ifindex = nm_device_get_ifindex (device);
 	props = nm_platform_link_get_lnk_wireguard (nm_device_get_platform (device), ifindex, &plink);
@@ -78,16 +867,16 @@ update_properties (NMDevice *device)
 
 #define CHECK_PROPERTY_CHANGED(field, prop) \
 	G_STMT_START { \
-		if (self->props.field != props->field) { \
-			self->props.field = props->field; \
+		if (priv->lnk_curr.field != props->field) { \
+			priv->lnk_curr.field = props->field; \
 			_notify (self, prop); \
 		} \
 	} G_STMT_END
 
 #define CHECK_PROPERTY_CHANGED_ARRAY(field, prop) \
 	G_STMT_START { \
-		if (memcmp (&self->props.field, &props->field, sizeof (props->field)) != 0) { \
-			memcpy (&self->props.field, &props->field, sizeof (props->field)); \
+		if (memcmp (&priv->lnk_curr.field, &props->field, sizeof (priv->lnk_curr.field)) != 0) { \
+			memcpy (&priv->lnk_curr.field, &props->field, sizeof (priv->lnk_curr.field)); \
 			_notify (self, prop); \
 		} \
 	} G_STMT_END
@@ -107,24 +896,655 @@ link_changed (NMDevice *device,
 	update_properties (device);
 }
 
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *dev)
+{
+	return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	const char *iface = nm_device_get_iface (device);
+	int r;
+
+	g_return_val_if_fail (iface, FALSE);
+
+	r = nm_platform_link_wireguard_add (nm_device_get_platform (device), iface, out_plink);
+	if (r < 0) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
+		             "Failed to create WireGuard interface '%s' for '%s': %s",
+		             iface,
+		             nm_connection_get_id (connection),
+		             nm_strerror (r));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+_secrets_cancel (NMDeviceWireGuard *self)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	if (priv->secrets_call_id)
+		nm_act_request_cancel_secrets (NULL, priv->secrets_call_id);
+	nm_assert (!priv->secrets_call_id);
+}
+
+static void
+_secrets_cb (NMActRequest *req,
+             NMActRequestGetSecretsCallId *call_id,
+             NMSettingsConnection *connection,
+             GError *error,
+             gpointer user_data)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (user_data);
+	NMDevice *device = NM_DEVICE (self);
+	NMDeviceWireGuardPrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE_WIREGUARD (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->secrets_call_id == call_id);
+
+	priv->secrets_call_id = NULL;
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	g_return_if_fail (req == nm_device_get_act_request (device));
+	g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
+	g_return_if_fail (nm_act_request_get_settings_connection (req) == connection);
+
+	if (error) {
+		_LOGW (LOGD_ETHER, "%s", error->message);
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+	} else
+		nm_device_activate_schedule_stage1_device_prepare (device);
+}
+
+static void
+_secrets_get_secrets (NMDeviceWireGuard *self,
+                      const char *setting_name,
+                      NMSecretAgentGetSecretsFlags flags,
+                      const char *const*hints)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	NMActRequest *req;
+
+	_secrets_cancel (self);
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	priv->secrets_call_id = nm_act_request_get_secrets (req,
+	                                                    TRUE,
+	                                                    setting_name,
+	                                                    flags,
+	                                                    hints,
+	                                                    _secrets_cb,
+	                                                    self);
+	g_return_if_fail (priv->secrets_call_id);
+}
+
+static NMActStageReturn
+_secrets_handle_auth_or_fail (NMDeviceWireGuard *self,
+                              NMActRequest *req,
+                              gboolean new_secrets)
+{
+	NMConnection *applied_connection;
+	const char *setting_name;
+	gs_unref_ptrarray GPtrArray *hints = NULL;
+
+	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
+		return NM_ACT_STAGE_RETURN_FAILURE;
+
+	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
+
+	nm_active_connection_clear_secrets (NM_ACTIVE_CONNECTION (req));
+
+	applied_connection = nm_act_request_get_applied_connection (req);
+	setting_name = nm_connection_need_secrets (applied_connection, &hints);
+	if (!setting_name) {
+		_LOGI (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	if (hints)
+		g_ptr_array_add (hints, NULL);
+
+	_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),
+	                      (  hints
+	                       ? (const char *const*) hints->pdata
+	                       : NULL));
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+/*****************************************************************************/
+
+static void
+_dns_config_changed (NMDnsManager *dns_manager, NMDeviceWireGuard *self)
+{
+	/* when the DNS configuration changes, we re-resolve the peer addresses.
+	 *
+	 * Possibly, we should also do that when the default-route changes, but it's
+	 * hard to figure out when that happens. */
+	_peers_resolve_reresolve_all (self);
+}
+
+/*****************************************************************************/
+
+static NMActStageReturn
+link_config (NMDeviceWireGuard *self,
+             const char *reason,
+             LinkConfigMode config_mode,
+             NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	nm_auto_bzero_secret_ptr NMSecretPtr wg_lnk_clear_private_key = NM_SECRET_PTR_INIT ();
+	NMSettingWireGuard *s_wg;
+	NMConnection *connection;
+	NMActStageReturn ret;
+	gs_unref_array GArray *allowed_ips_data = NULL;
+	NMPlatformLnkWireGuard wg_lnk;
+	gs_free NMPWireGuardPeer *plpeers = NULL;
+	gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL;
+	guint plpeers_len = 0;
+	const char *setting_name;
+	gboolean peers_removed;
+	NMPlatformWireGuardChangeFlags wg_change_flags;
+	int ifindex;
+	int r;
+
+	NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+
+	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+	s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+	g_return_val_if_fail (s_wg, NM_ACT_STAGE_RETURN_FAILURE);
+
+	priv->link_config_last_at = nm_utils_get_monotonic_timestamp_ns ();
+
+	_LOGT (LOGD_DEVICE, "wireguard link config (%s, %s)...",
+	       reason, _link_config_mode_to_string (config_mode));
+
+	if (!priv->dns_manager) {
+		priv->dns_manager = g_object_ref (nm_dns_manager_get ());
+		g_signal_connect (priv->dns_manager, NM_DNS_MANAGER_CONFIG_CHANGED, G_CALLBACK (_dns_config_changed), self);
+	}
+
+	if (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)
+	    && (setting_name = nm_connection_need_secrets (connection, NULL))) {
+		NMActRequest *req = nm_device_get_act_request (NM_DEVICE (self));
+
+		_LOGD (LOGD_DEVICE,
+		       "Activation: connection '%s' has security, but secrets are required.",
+		       nm_connection_get_id (connection));
+
+		ret = _secrets_handle_auth_or_fail (self, req, FALSE);
+		if (ret != NM_ACT_STAGE_RETURN_SUCCESS) {
+			if (ret != NM_ACT_STAGE_RETURN_POSTPONE) {
+				nm_assert (ret == NM_ACT_STAGE_RETURN_FAILURE);
+				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+			}
+			return ret;
+		}
+	}
+
+	ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self));
+	if (ifindex <= 0) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	_peers_update_all (self, s_wg, &peers_removed);
+
+	wg_lnk = (NMPlatformLnkWireGuard) { };
 
-/******************************************************************/
+	wg_change_flags = NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE;
+
+	if (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)
+	    || (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_REAPPLY)
+	        && peers_removed))
+		wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS;
+
+	if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+	                            LINK_CONFIG_MODE_REAPPLY)) {
+
+		wg_lnk.listen_port = nm_setting_wireguard_get_listen_port (s_wg),
+		wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT;
+
+		wg_lnk.fwmark = nm_setting_wireguard_get_fwmark (s_wg),
+		wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK;
+
+		if (nm_utils_base64secret_decode (nm_setting_wireguard_get_private_key (s_wg),
+		                                  sizeof (wg_lnk.private_key),
+		                                  wg_lnk.private_key)) {
+			wg_lnk_clear_private_key = NM_SECRET_PTR_ARRAY (wg_lnk.private_key);
+			wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY;
+		} else {
+			if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)) {
+				_LOGD (LOGD_DEVICE, "the provided private-key is invalid");
+				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+				return NM_ACT_STAGE_RETURN_FAILURE;
+			}
+		}
+	}
+
+	_peers_get_platform_list (priv,
+	                          config_mode,
+	                          &plpeers,
+	                          &plpeer_flags,
+	                          &plpeers_len,
+	                          &allowed_ips_data);
+
+	r = nm_platform_link_wireguard_change (nm_device_get_platform (NM_DEVICE (self)),
+	                                       ifindex,
+	                                       &wg_lnk,
+	                                       plpeers,
+	                                       plpeer_flags,
+	                                       plpeers_len,
+	                                       wg_change_flags);
+
+	nm_explicit_bzero (plpeers, sizeof (plpeers) * plpeers_len);
+
+	if (r < 0) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+link_config_delayed (NMDeviceWireGuard *self,
+                     const char *reason)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	gint64 now;
+
+	priv->link_config_delayed_id = 0;
+
+	if (priv->link_config_last_at != 0) {
+		now = nm_utils_get_monotonic_timestamp_ns ();
+		if (now < priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC) {
+			/* we ratelimit calls to link_config(), because we call this whenever a resolver
+			 * completes. */
+			_LOGT (LOGD_DEVICE, "wireguard link config (%s) (postponed)", reason);
+			priv->link_config_delayed_id = g_timeout_add (NM_MAX ((priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC - now) / NM_UTILS_NS_PER_MSEC,
+			                                                      (gint64) 1),
+			                                              link_config_delayed_ratelimit_cb,
+			                                              self);
+			return;
+		}
+	}
+
+	link_config (self, reason, LINK_CONFIG_MODE_ENDPOINTS, NULL);
+}
+
+static gboolean
+link_config_delayed_ratelimit_cb (gpointer user_data)
+{
+	link_config_delayed (user_data, "after-ratelimiting");
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+link_config_delayed_resolver_cb (gpointer user_data)
+{
+	link_config_delayed (user_data, "resolver-update");
+	return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device,
+                   NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceSysIfaceState sys_iface_state;
+	NMDeviceStateReason failure_reason;
+	NMActStageReturn ret;
+
+	sys_iface_state = nm_device_sys_iface_state_get (device);
+
+	if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_EXTERNAL) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	ret = link_config (NM_DEVICE_WIREGUARD (device),
+	                                        "configure",
+	                                          (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME)
+	                                        ? LINK_CONFIG_MODE_ASSUME
+	                                        : LINK_CONFIG_MODE_FULL,
+	                                        &failure_reason);
+
+	if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME) {
+		/* this never fails. */
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	if (ret != NM_ACT_STAGE_RETURN_FAILURE) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+		return ret;
+	}
+
+	nm_device_state_changed (device,
+	                         NM_DEVICE_STATE_FAILED,
+	                         failure_reason);
+	NM_SET_OUT (out_failure_reason, failure_reason);
+	return NM_ACT_STAGE_RETURN_FAILURE;
+}
+
+static NMIPConfig *
+_get_dev2_ip_config (NMDeviceWireGuard *self,
+                     int addr_family)
+{
+	gs_unref_object NMIPConfig *ip_config = NULL;
+	NMConnection *connection;
+	NMSettingWireGuard *s_wg;
+	guint n_peers;
+	guint i;
+	int ip_ifindex;
+	guint32 route_metric;
+	guint32 route_table_coerced;
+
+	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
+	s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+
+	/* Differences to `wg-quick`.
+	 *
+	 * `wg-quick` supports the "Table" setting with 3 modes:
+	 *
+	 * a1) "off": this is what we do with "peer-routes" disabled.
+	 *
+	 * a2) an explicit routing table. This is our behavior with "peer-routes" on. In this case
+	 *   we honor the "ipv4.route-table" and "ipv6.route-table" settings. One difference is that
+	 *   `wg-quick` would resolve table names from /etc/iproute2/rt_tables. Our connection profiles
+	 *   only contain table numbers, so that conversion from name to table must have happened
+	 *   before already.
+	 *
+	 * a3) "auto" (the default). In this case, `wg-quick` would only add the route to the
+	 *   main table, if the AllowedIP range is not yet reachable on the link. With "peer-routes"
+	 *   enabled, we don't check for that and always add the routes to the main-table
+	 *   (with 'ipv4.route-table' and 'ipv6.route-table' set to zero or RT_TABLE_MAIN (254)).
+	 *
+	 *   Also, in "auto" mode, `wg-quick` would add special handling for /0 routes and pick
+	 *   an empty table to configure policy routing to avoid routing loops. This handling
+	 *   of routing-loops via policy routing is not yet done, and requires a separate solution
+	 *   from constructing the peer-routes here.
+	 */
+	if (!nm_setting_wireguard_get_peer_routes (s_wg))
+		return NULL;
+
+	ip_ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self));
+
+	if (ip_ifindex <= 0)
+		return NULL;
+
+	route_metric = nm_device_get_route_metric (NM_DEVICE (self), addr_family);
+
+	route_table_coerced = nm_platform_route_table_coerce (nm_device_get_route_table (NM_DEVICE (self), addr_family, TRUE));
+
+	n_peers = nm_setting_wireguard_get_peers_len (s_wg);
+	for (i = 0; i < n_peers; i++) {
+		NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i);
+		guint n_aips;
+		guint j;
+
+		n_aips = nm_wireguard_peer_get_allowed_ips_len (peer);
+		for (j = 0; j < n_aips; j++) {
+			NMPlatformIPXRoute rt;
+			NMIPAddr addrbin;
+			const char *aip;
+			gboolean valid;
+			int prefix;
+
+			aip = nm_wireguard_peer_get_allowed_ip (peer, j, &valid);
+
+			if (   !valid
+			    || !nm_utils_parse_inaddr_prefix_bin (addr_family,
+			                                          aip,
+			                                          NULL,
+			                                          &addrbin,
+			                                          &prefix))
+				continue;
+
+			if (prefix < 0)
+				prefix = (addr_family == AF_INET) ? 32 : 128;
+
+			if (!ip_config)
+				ip_config = nm_device_ip_config_new (NM_DEVICE (self), addr_family);
+
+			nm_utils_ipx_address_clear_host_address (addr_family, &addrbin, NULL, prefix);
+
+			if (addr_family == AF_INET) {
+				rt.r4 = (NMPlatformIP4Route) {
+					.network       = addrbin.addr4,
+					.plen          = prefix,
+					.ifindex       = ip_ifindex,
+					.rt_source     = NM_IP_CONFIG_SOURCE_USER,
+					.table_coerced = route_table_coerced,
+					.metric        = route_metric,
+				};
+			} else {
+				rt.r6 = (NMPlatformIP6Route) {
+					.network       = addrbin.addr6,
+					.plen          = prefix,
+					.ifindex       = ip_ifindex,
+					.rt_source     = NM_IP_CONFIG_SOURCE_USER,
+					.table_coerced = route_table_coerced,
+					.metric        = route_metric,
+				};
+			}
+
+			nm_ip_config_add_route (ip_config, &rt.rx, NULL);
+		}
+	}
+
+	return g_steal_pointer (&ip_config);
+}
+
+static NMActStageReturn
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
+{
+	gs_unref_object NMIPConfig *ip_config = NULL;
+
+	ip_config = _get_dev2_ip_config (NM_DEVICE_WIREGUARD (device), addr_family);
+
+	nm_device_set_dev2_ip_config (device, addr_family, ip_config);
+
+	return NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
+}
+
+static guint32
+get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+{
+	/* When "MTU" for `wg-quick up` is unset, it calls `ip route get` for
+	 * each configured endpoint, to determine the suitable MTU how to reach
+	 * each endpoint.
+	 * For `wg-quick` this works very well, because whenever the script runs it
+	 * determines the best setting at that point in time. It's simply not concerned
+	 * with what happens later (and it's not around anyway).
+	 *
+	 * NetworkManager sticks around, so the right MTU would need to be re-determined
+	 * whenever anything relevant changes. Which basically means, to re-evaluate whenever
+	 * something related to addresses or routing changes (which happens all the time).
+	 *
+	 * The correct MTU indeed depends on the MTU setting of other interfaces (or routes).
+	 * But it's still odd, that activating/deactivating a seemingly unrelated interface
+	 * would trigger an MTU change. It's odd to explain/document and odd to implemented
+	 * -- despite this being the reality.
+	 *
+	 * For now, only support configuring an explicit MTU, or leave the setting untouched.
+	 * The same limitation also applies to other "ip-tunnel" types, where we could use
+	 * similar smarts for autodetecting the MTU.
+	 */
+	return nm_device_get_configured_mtu_from_connection (device,
+	                                                     NM_TYPE_SETTING_WIREGUARD,
+	                                                     out_source);
+}
+
+static void
+device_state_changed (NMDevice *device,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason)
+{
+	NMDeviceWireGuardPrivate *priv;
+
+	if (new_state <= NM_DEVICE_STATE_ACTIVATED)
+		return;
+
+	priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (device);
+
+	_peers_remove_all (priv);
+	_secrets_cancel (NM_DEVICE_WIREGUARD (device));
+}
+
+/*****************************************************************************/
+
+static gboolean
+can_reapply_change (NMDevice *device,
+                    const char *setting_name,
+                    NMSetting *s_old,
+                    NMSetting *s_new,
+                    GHashTable *diffs,
+                    GError **error)
+{
+	if (nm_streq (setting_name, NM_SETTING_WIREGUARD_SETTING_NAME)) {
+		/* Most, but not all WireGuard settings can be reapplied. Whitelist.
+		 *
+		 * MTU cannot be reapplied. */
+		return nm_device_hash_check_invalid_keys (diffs,
+		                                          NM_SETTING_WIREGUARD_SETTING_NAME,
+		                                          error,
+		                                          NM_SETTING_WIREGUARD_FWMARK,
+		                                          NM_SETTING_WIREGUARD_LISTEN_PORT,
+		                                          NM_SETTING_WIREGUARD_PEERS,
+		                                          NM_SETTING_WIREGUARD_PEER_ROUTES,
+		                                          NM_SETTING_WIREGUARD_PRIVATE_KEY,
+		                                          NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS);
+	}
+
+	return NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->can_reapply_change (device,
+	                                                                               setting_name,
+	                                                                               s_old,
+	                                                                               s_new,
+	                                                                               diffs,
+	                                                                               error);
+}
+
+static void
+reapply_connection (NMDevice *device,
+                    NMConnection *con_old,
+                    NMConnection *con_new)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (device);
+	gs_unref_object NMIPConfig *ip4_config = NULL;
+	gs_unref_object NMIPConfig *ip6_config = NULL;
+
+	ip4_config = _get_dev2_ip_config (self, AF_INET);
+	ip6_config = _get_dev2_ip_config (self, AF_INET6);
+
+	nm_device_set_dev2_ip_config (device, AF_INET, ip4_config);
+	nm_device_set_dev2_ip_config (device, AF_INET6, ip6_config);
+
+	NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->reapply_connection (device,
+	                                                                        con_old,
+	                                                                        con_new);
+
+	link_config (NM_DEVICE_WIREGUARD (device),
+	             "reapply",
+	             LINK_CONFIG_MODE_REAPPLY,
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static void
+update_connection (NMDevice *device, NMConnection *connection)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (device);
+	NMSettingWireGuard *s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+	const NMPObject *obj_wg;
+	const NMPObjectLnkWireGuard *olnk_wg;
+	guint i;
+
+	if (!s_wg) {
+		s_wg = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ());
+		nm_connection_add_setting (connection, NM_SETTING (s_wg));
+	}
+
+	g_object_set (s_wg,
+	              NM_SETTING_WIREGUARD_FWMARK,
+	              (guint) priv->lnk_curr.fwmark,
+	              NM_SETTING_WIREGUARD_LISTEN_PORT,
+	              (guint) priv->lnk_curr.listen_port,
+	              NULL);
+
+	obj_wg = NMP_OBJECT_UP_CAST (nm_platform_link_get_lnk_wireguard (nm_device_get_platform (device),
+	                                                                 nm_device_get_ip_ifindex (device),
+	                                                                 NULL));
+	if (!obj_wg)
+		return;
+
+	olnk_wg = &obj_wg->_lnk_wireguard;
+
+	for (i = 0; i < olnk_wg->peers_len; i++) {
+		nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+		const NMPWireGuardPeer *ppeer = &olnk_wg->peers[i];
+
+		peer = nm_wireguard_peer_new ();
+
+		_nm_wireguard_peer_set_public_key_bin (peer, ppeer->public_key);
+
+		nm_setting_wireguard_append_peer (s_wg, peer);
+	}
+}
+
+/*****************************************************************************/
 
 static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
 	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
 
 	switch (prop_id) {
 	case PROP_PUBLIC_KEY:
-		g_value_take_variant (value, get_public_key_as_variant (self));
+		g_value_take_variant (value,
+		                      g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                                 priv->lnk_curr.public_key,
+		                                                 sizeof (priv->lnk_curr.public_key),
+		                                                 1));
 		break;
 	case PROP_LISTEN_PORT:
-		g_value_set_uint (value, self->props.listen_port);
+		g_value_set_uint (value, priv->lnk_curr.listen_port);
 		break;
 	case PROP_FWMARK:
-		g_value_set_uint (value, self->props.fwmark);
+		g_value_set_uint (value, priv->lnk_curr.fwmark);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -132,9 +1552,44 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
+/*****************************************************************************/
+
 static void
 nm_device_wireguard_init (NMDeviceWireGuard *self)
 {
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	c_list_init (&priv->lst_peers_head);
+	priv->peers = g_hash_table_new (_peer_data_hash, _peer_data_equal);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	_secrets_cancel (self);
+
+	_peers_remove_all (priv);
+
+	G_OBJECT_CLASS (nm_device_wireguard_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	nm_explicit_bzero (priv->lnk_curr.private_key, sizeof (priv->lnk_curr.private_key));
+
+	if (priv->dns_manager) {
+		g_signal_handlers_disconnect_by_func (priv->dns_manager, _dns_config_changed, self);
+		g_object_unref (priv->dns_manager);
+	}
+
+	G_OBJECT_CLASS (nm_device_wireguard_parent_class)->finalize (object);
 }
 
 static const NMDBusInterfaceInfoExtended interface_info_device_wireguard = {
@@ -156,13 +1611,26 @@ nm_device_wireguard_class_init (NMDeviceWireGuardClass *klass)
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->get_property = get_property;
+	object_class->dispose = dispose;
+	object_class->finalize = finalize;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_wireguard);
 
-	device_class->connection_type_supported = NULL;
+	device_class->connection_type_supported = NM_SETTING_WIREGUARD_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_WIREGUARD_SETTING_NAME;
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD);
 
+	device_class->state_changed = device_state_changed;
+	device_class->create_and_realize = create_and_realize;
+	device_class->act_stage2_config = act_stage2_config;
+	device_class->act_stage2_config_also_for_external_or_assume = TRUE;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
+	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->link_changed = link_changed;
+	device_class->update_connection = update_connection;
+	device_class->can_reapply_change = can_reapply_change;
+	device_class->reapply_connection = reapply_connection;
+	device_class->get_configured_mtu = get_configured_mtu;
 
 	obj_properties[PROP_PUBLIC_KEY] =
 	    g_param_spec_variant (NM_DEVICE_WIREGUARD_PUBLIC_KEY,
@@ -207,6 +1675,7 @@ create_device (NMDeviceFactory *factory,
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL (WIREGUARD, WireGuard, wireguard,
-	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD),
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD)
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_WIREGUARD_SETTING_NAME),
 	factory_class->create_device = create_device;
 )
diff --git a/src/devices/nm-device-wpan.c b/src/devices/nm-device-wpan.c
index 05b507d5..cdfd1f70 100644
--- a/src/devices/nm-device-wpan.c
+++ b/src/devices/nm-device-wpan.c
@@ -23,7 +23,6 @@
 #include "nm-device-wpan.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <linux/if.h>
 
@@ -119,11 +118,11 @@ static NMActStageReturn
 act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceWpan *self = NM_DEVICE_WPAN (device);
-	NMConnection *connection;
 	NMSettingWpan *s_wpan;
 	NMPlatform *platform;
 	guint16 pan_id;
 	guint16 short_address;
+	gint16 page, channel;
 	int ifindex;
 	const guint8 *hwaddr;
 	gsize hwaddr_len = 0;
@@ -139,12 +138,11 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	g_return_val_if_fail (platform, NM_ACT_STAGE_RETURN_FAILURE);
 
 	ifindex = nm_device_get_ifindex (device);
+
 	g_return_val_if_fail (ifindex > 0, NM_ACT_STAGE_RETURN_FAILURE);
 
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+	s_wpan = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WPAN);
 
-	s_wpan = NM_SETTING_WPAN (nm_connection_get_setting (connection, NM_TYPE_SETTING_WPAN));
 	g_return_val_if_fail (s_wpan, NM_ACT_STAGE_RETURN_FAILURE);
 
 	hwaddr = nm_platform_link_get_address (platform, ifindex, &hwaddr_len);
@@ -183,9 +181,18 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		}
 	}
 
+	channel = nm_setting_wpan_get_channel (s_wpan);
+	if (channel != NM_SETTING_WPAN_CHANNEL_DEFAULT) {
+		page = nm_setting_wpan_get_page (s_wpan);
+		if (!nm_platform_wpan_set_channel (platform, ifindex, page, channel)) {
+			_LOGW (LOGD_DEVICE, "unable to set the channel");
+			goto out;
+		}
+	}
+
 	ret = NM_ACT_STAGE_RETURN_SUCCESS;
 out:
-        nm_device_bring_up (device, TRUE, NULL);
+	nm_device_bring_up (device, TRUE, NULL);
 
 	if (lowpan_device)
 		nm_device_bring_up (lowpan_device, TRUE, NULL);
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 89e1cc51..7514fa78 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -24,9 +24,7 @@
 #include "nm-device.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <signal.h>
 #include <sys/types.h>
@@ -67,6 +65,7 @@
 #include "settings/nm-settings.h"
 #include "nm-setting-ethtool.h"
 #include "nm-auth-utils.h"
+#include "nm-keep-alive.h"
 #include "nm-netns.h"
 #include "nm-dispatcher.h"
 #include "nm-config.h"
@@ -114,14 +113,6 @@ typedef enum {
 	CLEANUP_TYPE_DECONFIGURE,
 } CleanupType;
 
-typedef enum {
-	IP_NONE = 0,
-	IP_WAIT,
-	IP_CONF,
-	IP_DONE,
-	IP_FAIL
-} IpState;
-
 typedef struct {
 	CList lst_slave;
 	NMDevice *slave;
@@ -174,6 +165,7 @@ struct _NMDeviceConnectivityHandle {
 	bool is_periodic:1;
 	bool is_periodic_bump:1;
 	bool is_periodic_bump_on_complete:1;
+	int addr_family;
 };
 
 typedef struct {
@@ -195,7 +187,6 @@ enum {
 	REMOVED,
 	RECHECK_AUTO_ACTIVATE,
 	RECHECK_ASSUME,
-	CONNECTIVITY_CHANGED,
 	LAST_SIGNAL,
 };
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -241,7 +232,8 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDevice,
 	PROP_REFRESH_RATE_MS,
 	PROP_TX_BYTES,
 	PROP_RX_BYTES,
-	PROP_CONNECTIVITY,
+	PROP_IP4_CONNECTIVITY,
+	PROP_IP6_CONNECTIVITY,
 );
 
 typedef struct _NMDevicePrivate {
@@ -392,8 +384,11 @@ typedef struct _NMDevicePrivate {
 
 	bool            ipv6ll_handle:1; /* TRUE if NM handles the device's IPv6LL address */
 	bool            ipv6ll_has:1;
+	bool            ndisc_started:1;
 	bool            device_link_changed_down:1;
 
+	bool            concheck_rp_filter_checked:1;
+
 	/* Generic DHCP stuff */
 	char *          dhcp_anycast_address;
 
@@ -413,11 +408,8 @@ typedef struct _NMDevicePrivate {
 		NMIPConfig *ip_config_x[2];
 	};
 
-	union {
-		const IpState   ip4_state;
-		IpState         ip4_state_;
-	};
-	AppliedConfig   dev_ip4_config; /* Config from DHCP, PPP, LLv4, etc */
+	/* Config from DHCP, PPP, LLv4, etc */
+	AppliedConfig  dev_ip_config_4;
 
 	/* config from the setting */
 	union {
@@ -446,18 +438,16 @@ typedef struct _NMDevicePrivate {
 		GSList *vpn_configs_x[2];
 	};
 
-	/* WWAN configuration */
+	/* Extra device configuration, injected by the subclass of NMDevice.
+	 * This is used for example by NMDeviceModem for WWAN configuration. */
 	union {
 		struct {
-			AppliedConfig  wwan_ip_config_6;
-			AppliedConfig  wwan_ip_config_4;
+			AppliedConfig dev2_ip_config_6;
+			AppliedConfig dev2_ip_config_4;
 		};
-		AppliedConfig wwan_ip_config_x[2];
+		AppliedConfig dev2_ip_config_x[2];
 	};
 
-	bool v4_has_shadowed_routes;
-	const char *ip4_rp_filter;
-
 	/* DHCPv4 tracking */
 	struct {
 		NMDhcpClient *  client;
@@ -500,9 +490,16 @@ typedef struct _NMDevicePrivate {
 	} acd;
 
 	union {
-		const IpState   ip6_state;
-		IpState         ip6_state_;
+		struct {
+			const NMDeviceIPState   ip_state_6;
+			const NMDeviceIPState   ip_state_4;
+		};
+		union {
+			const NMDeviceIPState   ip_state_x[2];
+			NMDeviceIPState         ip_state_x_[2];
+		};
 	};
+
 	AppliedConfig  ac_ip6_config;  /* config from IPv6 autoconfiguration */
 	NMIP6Config *  ext_ip6_config_captured; /* Configuration captured from platform. */
 	NMIP6Config *  dad6_ip6_config;
@@ -555,24 +552,24 @@ typedef struct _NMDevicePrivate {
 	NMLldpListener *lldp_listener;
 
 	NMConnectivity *concheck_mgr;
+	CList concheck_lst_head;
+	struct {
+		/* if periodic checks are enabled, this is the source id for the next check. */
+		guint p_cur_id;
 
-	/* 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 currently configured max periodic interval. */
+		guint p_max_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;
+		/* the current interval. If we are probing, the interval might be lower
+		 * then the configured max interval. */
+		guint p_cur_interval;
 
-	NMConnectivityState connectivity_state;
+		/* the timestamp, when we last scheduled the timer p_cur_id with current interval
+		 * p_cur_interval. */
+		gint64 p_cur_basetime_ns;
 
-	CList concheck_lst_head;
+		NMConnectivityState state;
+	} concheck_x[2];
 
 	guint check_delete_unrealized_id;
 
@@ -643,7 +640,26 @@ static void _set_mtu (NMDevice *self, guint32 mtu);
 static void _commit_mtu (NMDevice *self, const NMIP4Config *config);
 static void _cancel_activation (NMDevice *self);
 
-static void concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean is_periodic);
+static void concheck_update_state (NMDevice *self,
+                                   int addr_family,
+                                   NMConnectivityState state,
+                                   gboolean is_periodic);
+
+static void activate_stage4_ip_config_timeout_4 (NMDevice *self);
+static void activate_stage4_ip_config_timeout_6 (NMDevice *self);
+
+static void (*const activate_stage4_ip_config_timeout_x[2]) (NMDevice *self) = {
+	activate_stage4_ip_config_timeout_6,
+	activate_stage4_ip_config_timeout_4,
+};
+
+static void activate_stage5_ip_config_result_4 (NMDevice *self);
+static void activate_stage5_ip_config_result_6 (NMDevice *self);
+
+static void (*const activate_stage5_ip_config_result_x[2]) (NMDevice *self) = {
+	activate_stage5_ip_config_result_6,
+	activate_stage5_ip_config_result_4,
+};
 
 /*****************************************************************************/
 
@@ -739,10 +755,10 @@ NM_UTILS_LOOKUP_STR_DEFINE (nm_device_state_reason_to_str, NMDeviceStateReason,
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE,           "ip-address-duplicate"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED,          "ip-method-unsupported"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED,     "sriov-configuration-failed"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_PEER_NOT_FOUND,                 "peer-not-found"),
 );
 
-#define reason_to_string(reason) \
-	NM_UTILS_LOOKUP_STR (nm_device_state_reason_to_str, reason)
+#define reason_to_string_a(reason) NM_UTILS_LOOKUP_STR_A (nm_device_state_reason_to_str, reason)
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (mtu_source_to_str, NMDeviceMtuSource,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
@@ -868,28 +884,28 @@ concheck_get_mgr (NMDevice *self)
 	return priv->concheck_mgr;
 }
 
-static NMIP4Config *
-_ip4_config_new (NMDevice *self)
+NMIP4Config *
+nm_device_ip4_config_new (NMDevice *self)
 {
 	return nm_ip4_config_new (nm_device_get_multi_index (self),
 	                          nm_device_get_ip_ifindex (self));
 }
 
-static NMIP6Config *
-_ip6_config_new (NMDevice *self)
+NMIP6Config *
+nm_device_ip6_config_new (NMDevice *self)
 {
 	return nm_ip6_config_new (nm_device_get_multi_index (self),
 	                          nm_device_get_ip_ifindex (self));
 }
 
-static NMIPConfig *
-_ip_config_new (NMDevice *self, int addr_family)
+NMIPConfig *
+nm_device_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);
+	       ? (gpointer) nm_device_ip4_config_new (self)
+	       : (gpointer) nm_device_ip6_config_new (self);
 }
 
 static void
@@ -902,6 +918,12 @@ applied_config_clear (AppliedConfig *config)
 static void
 applied_config_init (AppliedConfig *config, gpointer ip_config)
 {
+	nm_assert (   !ip_config
+	           || (!config->orig && !config->current)
+	           || nm_ip_config_get_addr_family (ip_config) == nm_ip_config_get_addr_family (config->orig ?: config->current));
+	nm_assert (   !ip_config
+	           || NM_IS_IP_CONFIG (ip_config, AF_UNSPEC));
+
 	nm_g_object_ref (ip_config);
 	applied_config_clear (config);
 	config->orig = ip_config;
@@ -910,7 +932,7 @@ applied_config_init (AppliedConfig *config, gpointer 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);
+	gs_unref_object NMIPConfig *c = nm_device_ip_config_new (self, addr_family);
 
 	applied_config_init (config, c);
 }
@@ -1116,8 +1138,8 @@ init_ip_config_dns_priority (NMDevice *self, NMIPConfig *config)
 	int priority;
 
 	property = (nm_ip_config_get_addr_family (config) == AF_INET)
-	             ? "ipv4.dns-priority"
-	             : "ipv6.dns-priority";
+	             ? NM_CON_DEFAULT ("ipv4.dns-priority")
+	             : NM_CON_DEFAULT ("ipv6.dns-priority");
 
 	priority = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
 	                                                        property,
@@ -1130,96 +1152,85 @@ init_ip_config_dns_priority (NMDevice *self, NMIPConfig *config)
 
 /*****************************************************************************/
 
-static gboolean
-nm_device_ipv4_sysctl_set (NMDevice *self, const char *property, const char *value)
+static char *
+nm_device_sysctl_ip_conf_get (NMDevice *self,
+                              int addr_family,
+                              const char *property)
 {
-	NMPlatform *platform = nm_device_get_platform (self);
-	gs_free char *value_to_free = NULL;
-	const char *value_to_set;
-	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-	if (!nm_device_get_ip_ifindex (self))
-		return FALSE;
+	const char *ifname;
 
-	if (value) {
-		value_to_set = value;
-	} else {
-		/* Set to a default value when we've got a NULL @value. */
-		value_to_free = nm_platform_sysctl_get (platform,
-		                                        NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, "default", property)));
-		value_to_set = value_to_free;
-	}
+	nm_assert_addr_family (addr_family);
 
-	return nm_platform_sysctl_set (platform,
-	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, nm_device_get_ip_iface (self), property)),
-	                               value_to_set);
+	ifname = nm_device_get_ip_iface_from_platform (self);
+	if (!ifname)
+		return NULL;
+	return nm_platform_sysctl_ip_conf_get (nm_device_get_platform (self), addr_family, ifname, property);
 }
 
-static guint32
-nm_device_ipv4_sysctl_get_effective_uint32 (NMDevice *self, const char *property, guint32 fallback)
+static gint64
+nm_device_sysctl_ip_conf_get_int_checked (NMDevice *self,
+                                          int addr_family,
+                                          const char *property,
+                                          guint base,
+                                          gint64 min,
+                                          gint64 max,
+                                          gint64 fallback)
 {
-	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-	gint64 v, v_all;
+	const char *ifname;
 
-	if (!nm_device_get_ip_ifindex (self))
-		return fallback;
+	nm_assert_addr_family (addr_family);
 
-	/* 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;
+	ifname = nm_device_get_ip_iface_from_platform (self);
+	if (!ifname) {
+		errno = EINVAL;
+		return fallback;
+	}
+	return nm_platform_sysctl_ip_conf_get_int_checked (nm_device_get_platform (self),
+	                                                   addr_family,
+	                                                   ifname,
+	                                                   property,
+	                                                   base,
+	                                                   min,
+	                                                   max,
+	                                                   fallback);
 }
 
 gboolean
-nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value)
+nm_device_sysctl_ip_conf_set (NMDevice *self,
+                              int addr_family,
+                              const char *property,
+                              const char *value)
 {
-	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-	if (!nm_device_get_ip_ifindex (self))
-		return FALSE;
+	NMPlatform *platform = nm_device_get_platform (self);
+	gs_free char *value_to_free = NULL;
+	const char *ifname;
 
-	return nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), property)), value);
-}
+	nm_assert_addr_family (addr_family);
 
-static guint32
-nm_device_ipv6_sysctl_get_uint32 (NMDevice *self, const char *property, guint32 fallback)
-{
-	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+	ifname = nm_device_get_ip_iface_from_platform (self);
+	if (!ifname)
+		return FALSE;
 
-	if (!nm_device_get_ip_ifindex (self))
-		return fallback;
+	if (!value) {
+		/* Set to a default value when we've got a NULL @value. */
+		value_to_free = nm_platform_sysctl_ip_conf_get (platform,
+		                                                addr_family,
+		                                                "default",
+		                                                property);
+		value = value_to_free;
+		if (!value)
+			return FALSE;
+	}
 
-	return nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
-	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), property)),
-	                                           10,
-	                                           0,
-	                                           G_MAXUINT32,
-	                                           fallback);
+	return nm_platform_sysctl_ip_conf_set (platform,
+	                                       addr_family,
+	                                       ifname,
+	                                       property,
+	                                       value);
 }
 
+/*****************************************************************************/
+
 gboolean
 nm_device_has_capability (NMDevice *self, NMDeviceCapabilities caps)
 {
@@ -1275,7 +1286,7 @@ _get_stable_id (NMDevice *self,
 
 		if (!stable_id) {
 			default_id = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-			                                                    "connection.stable-id",
+			                                                    NM_CON_DEFAULT ("connection.stable-id"),
 			                                                    self);
 			stable_id = default_id;
 		}
@@ -1283,7 +1294,7 @@ _get_stable_id (NMDevice *self,
 		uuid = nm_connection_get_uuid (connection);
 
 		/* the cloned-mac-address may be generated based on the stable-id.
-		 * Thus, at this point, we can only use the permanant MAC address
+		 * Thus, at this point, we can only use the permanent MAC address
 		 * as seed. */
 		hwaddr = nm_device_get_permanent_hw_address_full (self, TRUE, &hwaddr_is_fake);
 
@@ -1324,44 +1335,42 @@ _get_stable_id (NMDevice *self,
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_ip_state_to_string, IpState,
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_ip_state_to_string, NMDeviceIPState,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("unknown"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_NONE, "none"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_WAIT, "wait"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_CONF, "conf"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_DONE, "done"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_FAIL, "fail"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_NONE, "none"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_WAIT, "wait"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_CONF, "conf"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_DONE, "done"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_FAIL, "fail"),
 );
 
 static void
-_set_ip_state (NMDevice *self, int addr_family, IpState new_state)
+_set_ip_state (NMDevice *self, int addr_family, NMDeviceIPState new_state)
 {
-	IpState *p;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	nm_assert_addr_family (addr_family);
 
-	p =   (addr_family == AF_INET)
-	    ? &priv->ip4_state_
-	    : &priv->ip6_state_;
+	if (priv->ip_state_x[IS_IPv4] == new_state)
+		return;
 
-	if (*p != new_state) {
-		_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)",
-		       nm_utils_addr_family_to_char (addr_family),
-		       (int) new_state,
-		       _ip_state_to_string (new_state));
-		*p = new_state;
+	_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)",
+	       nm_utils_addr_family_to_char (addr_family),
+	       (int) new_state,
+	       _ip_state_to_string (new_state));
 
-		if (new_state == IP_DONE) {
-			/* we only set the IPx_READY flag once we reach IP_DONE state. We don't
-			 * ever clear it, even if we later enter IP_FAIL state.
-			 *
-			 * This is not documented/guaranteed behavior, but seems to make sense for now. */
-			_active_connection_set_state_flags (self,
-			                                    addr_family == AF_INET
-			                                      ? NM_ACTIVATION_STATE_FLAG_IP4_READY
-			                                      : NM_ACTIVATION_STATE_FLAG_IP6_READY);
-		}
+	priv->ip_state_x_[IS_IPv4] = new_state;
+
+	if (new_state == NM_DEVICE_IP_STATE_DONE) {
+		/* we only set the IPx_READY flag once we reach NM_DEVICE_IP_STATE_DONE state. We don't
+		 * ever clear it, even if we later enter NM_DEVICE_IP_STATE_FAIL state.
+		 *
+		 * This is not documented/guaranteed behavior, but seems to make sense for now. */
+		_active_connection_set_state_flags (self,
+		                                    addr_family == AF_INET
+		                                      ? NM_ACTIVATION_STATE_FLAG_IP4_READY
+		                                      : NM_ACTIVATION_STATE_FLAG_IP6_READY);
 	}
 }
 
@@ -1473,6 +1482,18 @@ nm_device_get_ip_iface (NMDevice *self)
 	return priv->ip_iface ?: priv->iface;
 }
 
+const char *
+nm_device_get_ip_iface_from_platform (NMDevice *self)
+{
+	int ifindex;
+
+	ifindex = nm_device_get_ip_ifindex (self);
+	if (ifindex <= 0)
+		return NULL;
+
+	return nm_platform_link_get_name (nm_device_get_platform (self), ifindex);
+}
+
 int
 nm_device_get_ip_ifindex (const NMDevice *self)
 {
@@ -1957,9 +1978,10 @@ nm_device_get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *iid, gboo
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
 	if (!ignore_token) {
-		s_ip6 = (NMSettingIP6Config *)
-		    nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
+		s_ip6 = nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
+
 		g_return_val_if_fail (s_ip6, FALSE);
+
 		token = nm_setting_ip6_config_get_token (s_ip6);
 	}
 	if (token)
@@ -2037,9 +2059,14 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 	 */
 
 	switch (device_type) {
-	/* 50 is also used for VPN plugins (NM_VPN_ROUTE_METRIC_DEFAULT) */
+
+	/* 50 is also used for VPN plugins (NM_VPN_ROUTE_METRIC_DEFAULT).
+	 *
+	 * Note that returning 50 from this function means that this device-type is
+	 * in some aspects a VPN. */
 	case NM_DEVICE_TYPE_WIREGUARD:
-		return 50;
+		return NM_VPN_ROUTE_METRIC_DEFAULT;
+
 	case NM_DEVICE_TYPE_ETHERNET:
 	case NM_DEVICE_TYPE_VETH:
 		return 100;
@@ -2087,6 +2114,7 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 		return 800;
 	case NM_DEVICE_TYPE_WPAN:
 		return 850;
+	case NM_DEVICE_TYPE_WIFI_P2P:
 	case NM_DEVICE_TYPE_GENERIC:
 		return 950;
 	case NM_DEVICE_TYPE_UNKNOWN:
@@ -2100,13 +2128,14 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 }
 
 static gboolean
-default_route_metric_penalty_detect (NMDevice *self)
+default_route_metric_penalty_detect (NMDevice *self, int addr_family)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	/* currently we don't differentiate between IPv4 and IPv6 when detecting
 	 * connectivity. */
-	if (   priv->connectivity_state != NM_CONNECTIVITY_FULL
+	if (   priv->concheck_x[IS_IPv4].state != NM_CONNECTIVITY_FULL
 	    && nm_connectivity_check_enabled (concheck_get_mgr (self)))
 		return TRUE;
 
@@ -2141,9 +2170,7 @@ nm_device_get_route_metric (NMDevice *self,
 
 	connection = nm_device_get_applied_connection (self);
 	if (connection) {
-		s_ip = addr_family == AF_INET
-		       ? nm_connection_get_setting_ip4_config (connection)
-		       : nm_connection_get_setting_ip6_config (connection);
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 
 		/* Slave interfaces don't have IP settings, but we may get here when
 		 * external changes are made or when noticing IP changes when starting
@@ -2159,7 +2186,9 @@ nm_device_get_route_metric (NMDevice *self,
 	/* use the current NMConfigData, which makes this configuration reloadable.
 	 * Note that that means that the route-metric might change between SIGHUP.
 	 * You must cache the returned value if that is a problem. */
-	property = addr_family == AF_INET ? "ipv4.route-metric" : "ipv6.route-metric";
+	property = addr_family == AF_INET
+	    ? NM_CON_DEFAULT ("ipv4.route-metric")
+	    : NM_CON_DEFAULT ("ipv6.route-metric");
 	route_metric = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
 	                                                            property,
 	                                                            self,
@@ -2189,7 +2218,7 @@ _get_mdns (NMDevice *self)
 		return mdns;
 
 	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                    "connection.mdns",
+	                                                    NM_CON_DEFAULT ("connection.mdns"),
 	                                                    self,
 	                                                    NM_SETTING_CONNECTION_MDNS_NO,
 	                                                    NM_SETTING_CONNECTION_MDNS_YES,
@@ -2211,7 +2240,7 @@ _get_llmnr (NMDevice *self)
 		return llmnr;
 
 	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                    "connection.llmnr",
+	                                                    NM_CON_DEFAULT ("connection.llmnr"),
 	                                                    self,
 	                                                    NM_SETTING_CONNECTION_LLMNR_NO,
 	                                                    NM_SETTING_CONNECTION_LLMNR_YES,
@@ -2246,11 +2275,7 @@ nm_device_get_route_table (NMDevice *self,
 
 	connection = nm_device_get_applied_connection (self);
 	if (connection) {
-		if (addr_family == AF_INET)
-			s_ip = nm_connection_get_setting_ip4_config (connection);
-		else
-			s_ip = nm_connection_get_setting_ip6_config (connection);
-
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 		if (s_ip)
 			route_table = nm_setting_ip_config_get_route_table (s_ip);
 
@@ -2260,7 +2285,9 @@ nm_device_get_route_table (NMDevice *self,
 		if (route_table == 0) {
 			const char *property;
 
-			property = addr_family == AF_INET ? "ipv4.route-table" : "ipv6.route-table";
+			property = addr_family == AF_INET
+			    ? NM_CON_DEFAULT ("ipv4.route-table")
+			    : NM_CON_DEFAULT ("ipv6.route-table");
 			route_table = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
 			                                                           property,
 			                                                           self,
@@ -2373,10 +2400,27 @@ nm_device_get_act_request (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->act_request.obj;
 }
 
+NMActivationStateFlags
+nm_device_get_activation_state_flags (NMDevice *self)
+{
+	NMActRequest *ac;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_ACTIVATION_STATE_FLAG_NONE);
+
+	ac = NM_DEVICE_GET_PRIVATE (self)->act_request.obj;
+	if (!ac)
+		return NM_ACTIVATION_STATE_FLAG_NONE;
+	return nm_active_connection_get_state_flags (NM_ACTIVE_CONNECTION (ac));
+}
+
 NMSettingsConnection *
 nm_device_get_settings_connection (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	return priv->act_request.obj ? nm_act_request_get_settings_connection (priv->act_request.obj) : NULL;
 }
@@ -2420,7 +2464,7 @@ nm_device_has_unmodified_applied_connection (NMDevice *self, NMSettingCompareFla
 	return nm_active_connection_has_unmodified_applied_connection ((NMActiveConnection *) priv->act_request.obj, compare_flags);
 }
 
-NMSetting *
+gpointer
 nm_device_get_applied_setting (NMDevice *self, GType setting_type)
 {
 	NMConnection *connection;
@@ -2456,24 +2500,37 @@ typedef enum {
 } ConcheckScheduleMode;
 
 static NMDeviceConnectivityHandle *concheck_start (NMDevice *self,
+                                                   int addr_family,
                                                    NMDeviceConnectivityCallback callback,
                                                    gpointer user_data,
                                                    gboolean is_periodic);
 
 static void concheck_periodic_schedule_set (NMDevice *self,
+                                            int addr_family,
                                             ConcheckScheduleMode mode);
 
 static gboolean
-concheck_periodic_timeout_cb (gpointer user_data)
+_concheck_periodic_timeout_cb (NMDevice *self, int addr_family)
 {
-	NMDevice *self = user_data;
-
-	_LOGt (LOGD_CONCHECK, "connectivity: periodic timeout");
-	concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_CHECK_PERIODIC);
+	_LOGt (LOGD_CONCHECK, "connectivity: [IPv%c] periodic timeout",
+	       nm_utils_addr_family_to_char (addr_family));
+	concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_CHECK_PERIODIC);
 	return G_SOURCE_REMOVE;
 }
 
 static gboolean
+concheck_ip4_periodic_timeout_cb (gpointer user_data)
+{
+	return _concheck_periodic_timeout_cb (user_data, AF_INET);
+}
+
+static gboolean
+concheck_ip6_periodic_timeout_cb (gpointer user_data)
+{
+	return _concheck_periodic_timeout_cb (user_data, AF_INET6);
+}
+
+static gboolean
 concheck_is_possible (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -2491,17 +2548,18 @@ concheck_is_possible (NMDevice *self)
 }
 
 static gboolean
-concheck_periodic_schedule_do (NMDevice *self, gint64 now_ns)
+concheck_periodic_schedule_do (NMDevice *self, int addr_family, gint64 now_ns)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean periodic_check_disabled = FALSE;
 	gint64 expiry, tdiff;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	/* we always cancel whatever was pending. */
-	if (nm_clear_g_source (&priv->concheck_p_cur_id))
+	if (nm_clear_g_source (&priv->concheck_x[IS_IPv4].p_cur_id))
 		periodic_check_disabled = TRUE;
 
-	if (priv->concheck_p_max_interval == 0) {
+	if (priv->concheck_x[IS_IPv4].p_max_interval == 0) {
 		/* periodic checks are disabled */
 		goto out;
 	}
@@ -2510,46 +2568,50 @@ concheck_periodic_schedule_do (NMDevice *self, gint64 now_ns)
 		goto out;
 
 	nm_assert (now_ns > 0);
-	nm_assert (priv->concheck_p_cur_interval > 0);
+	nm_assert (priv->concheck_x[IS_IPv4].p_cur_interval > 0);
 
 	/* we schedule the timeout based on our current settings cur-interval and cur-basetime.
 	 * Before calling concheck_periodic_schedule_do(), make sure that these properties are
 	 * correct. */
 
-	expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
 	tdiff = expiry - now_ns;
 
-	_LOGT (LOGD_CONCHECK, "connectivity: periodic-check: %sscheduled in %lld milliseconds (%u seconds interval)",
+	_LOGT (LOGD_CONCHECK, "connectivity: [IPv%c] periodic-check: %sscheduled in %lld milliseconds (%u seconds interval)",
+	       nm_utils_addr_family_to_char (addr_family),
 	       periodic_check_disabled ? "re-" : "",
 	       (long long) (tdiff / NM_UTILS_NS_PER_MSEC),
-	       priv->concheck_p_cur_interval);
+	       priv->concheck_x[IS_IPv4].p_cur_interval);
 
-	priv->concheck_p_cur_id = g_timeout_add (NM_MAX ((gint64) 0, tdiff) / NM_UTILS_NS_PER_MSEC,
-	                                         concheck_periodic_timeout_cb,
-	                                         self);
+	priv->concheck_x[IS_IPv4].p_cur_id =
+		g_timeout_add (NM_MAX ((gint64) 0, tdiff) / NM_UTILS_NS_PER_MSEC,
+	                       IS_IPv4 ? concheck_ip4_periodic_timeout_cb : concheck_ip6_periodic_timeout_cb,
+	                       self);
 	return TRUE;
 out:
-	if (periodic_check_disabled)
-		_LOGT (LOGD_CONCHECK, "connectivity: periodic-check: unscheduled");
+	if (periodic_check_disabled) {
+		_LOGT (LOGD_CONCHECK, "connectivity: [IPv%c] periodic-check: unscheduled",
+		       nm_utils_addr_family_to_char (addr_family));
+	}
 	return FALSE;
 }
 
 #define CONCHECK_P_PROBE_INTERVAL 1
 
 static void
-concheck_periodic_schedule_set (NMDevice *self,
-                                ConcheckScheduleMode mode)
+concheck_periodic_schedule_set (NMDevice *self, int addr_family, ConcheckScheduleMode mode)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gint64 new_expiry, exp_expiry, cur_expiry, tdiff;
 	gint64 now_ns = 0;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-	if (priv->concheck_p_max_interval == 0) {
+	if (priv->concheck_x[IS_IPv4].p_max_interval == 0) {
 		/* periodic check is disabled. Nothing to do. */
 		return;
 	}
 
-	if (!priv->concheck_p_cur_id) {
+	if (!priv->concheck_x[IS_IPv4].p_cur_id) {
 		/* we currently don't have a timeout scheduled. No need to reschedule
 		 * another one... */
 		if (NM_IN_SET (mode, CONCHECK_SCHEDULE_UPDATE_INTERVAL,
@@ -2563,19 +2625,19 @@ concheck_periodic_schedule_set (NMDevice *self,
 
 	switch (mode) {
 	case CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART:
-		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, now_ns))
-			concheck_start (self, NULL, NULL, TRUE);
+		priv->concheck_x[IS_IPv4].p_cur_interval = NM_MIN (priv->concheck_x[IS_IPv4].p_max_interval, CONCHECK_P_PROBE_INTERVAL);
+		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+		if (concheck_periodic_schedule_do (self, addr_family, now_ns))
+			concheck_start (self, addr_family, NULL, NULL, TRUE);
 		return;
 
 	case CONCHECK_SCHEDULE_UPDATE_INTERVAL:
-		/* called with "UPDATE_INTERVAL" and already have a concheck_p_cur_id scheduled. */
+		/* called with "UPDATE_INTERVAL" and already have a p_cur_id scheduled. */
 
-		nm_assert (priv->concheck_p_max_interval > 0);
-		nm_assert (priv->concheck_p_cur_interval > 0);
+		nm_assert (priv->concheck_x[IS_IPv4].p_max_interval > 0);
+		nm_assert (priv->concheck_x[IS_IPv4].p_cur_interval > 0);
 
-		if (priv->concheck_p_cur_interval <= priv->concheck_p_max_interval) {
+		if (priv->concheck_x[IS_IPv4].p_cur_interval <= priv->concheck_x[IS_IPv4].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.
 			 *
@@ -2584,17 +2646,17 @@ concheck_periodic_schedule_set (NMDevice *self,
 			return;
 		}
 
-		cur_expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_max_interval * NM_UTILS_NS_PER_SECOND);
+		cur_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_max_interval * NM_UTILS_NS_PER_SECOND);
 		nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
 
-		priv->concheck_p_cur_interval = priv->concheck_p_max_interval;
+		priv->concheck_x[IS_IPv4].p_cur_interval = priv->concheck_x[IS_IPv4].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, now_ns))
-				concheck_start (self, NULL, NULL, TRUE);
+			priv->concheck_x[IS_IPv4].p_cur_basetime_ns = now_ns;
+			if (concheck_periodic_schedule_do (self, addr_family, now_ns))
+				concheck_start (self, addr_family, NULL, NULL, TRUE);
 		} else {
 			/* we are reducing the max-interval to a shorter interval that we have currently
 			 * scheduled (with cur_interval).
@@ -2602,24 +2664,26 @@ concheck_periodic_schedule_set (NMDevice *self,
 			 * 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, now_ns);
+			concheck_periodic_schedule_do (self, addr_family, 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, now_ns);
+		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+		concheck_periodic_schedule_do (self, addr_family, now_ns);
 		return;
 
 	case CONCHECK_SCHEDULE_CHECK_PERIODIC:
 	{
 		gboolean any_periodic_pending;
 		NMDeviceConnectivityHandle *handle;
-		guint old_interval = priv->concheck_p_cur_interval;
+		guint old_interval = priv->concheck_x[IS_IPv4].p_cur_interval;
 
 		any_periodic_pending = FALSE;
 		c_list_for_each_entry (handle, &priv->concheck_lst_head, concheck_lst) {
+			if (handle->addr_family != addr_family)
+				continue;
 			if (handle->is_periodic_bump) {
 				handle->is_periodic_bump = FALSE;
 				handle->is_periodic_bump_on_complete = FALSE;
@@ -2630,7 +2694,7 @@ concheck_periodic_schedule_set (NMDevice *self,
 			/* 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);
+			priv->concheck_x[IS_IPv4].p_cur_interval = NM_MIN (old_interval * 2, priv->concheck_x[IS_IPv4].p_max_interval);
 		}
 
 		/* we just reached a timeout. The expected expiry (exp_expiry) should be
@@ -2638,13 +2702,13 @@ concheck_periodic_schedule_set (NMDevice *self,
 		 *
 		 * 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);
+		exp_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (old_interval * NM_UTILS_NS_PER_SECOND);
+		new_expiry = exp_expiry + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
 		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);
-		if (concheck_periodic_schedule_do (self, now_ns)) {
-			handle = concheck_start (self, NULL, NULL, TRUE);
-			if (old_interval != priv->concheck_p_cur_interval) {
+		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = (now_ns + tdiff) - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
+		if (concheck_periodic_schedule_do (self, addr_family, now_ns)) {
+			handle = concheck_start (self, addr_family, NULL, NULL, TRUE);
+			if (old_interval != priv->concheck_x[IS_IPv4].p_cur_interval) {
 				/* we just bumped the interval already when scheduling this check.
 				 * When the handle returns, don't bump a second time.
 				 *
@@ -2659,54 +2723,57 @@ concheck_periodic_schedule_set (NMDevice *self,
 	/* 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);
+		priv->concheck_x[IS_IPv4].p_cur_interval = NM_MIN (priv->concheck_x[IS_IPv4].p_max_interval, CONCHECK_P_PROBE_INTERVAL);
 		break;
 	case CONCHECK_SCHEDULE_RETURNED_MAX:
-		priv->concheck_p_cur_interval = priv->concheck_p_max_interval;
+		priv->concheck_x[IS_IPv4].p_cur_interval = priv->concheck_x[IS_IPv4].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);
+		priv->concheck_x[IS_IPv4].p_cur_interval = NM_MIN (priv->concheck_x[IS_IPv4].p_cur_interval * 2, priv->concheck_x[IS_IPv4].p_max_interval);
 		break;
 	}
 
 	/* 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
+	 * last check, instead of counting from now. The reason is that we want that the times
 	 * when we schedule checks be at precise intervals, without including the time it took for
 	 * the connectivity check. */
-	new_expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	new_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_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, now_ns);
+	priv->concheck_x[IS_IPv4].p_cur_basetime_ns = now_ns + tdiff - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	concheck_periodic_schedule_do (self, addr_family, now_ns);
 }
 
 static void
-concheck_update_interval (NMDevice *self, gboolean check_now)
+concheck_update_interval (NMDevice *self, int addr_family, gboolean check_now)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	guint new_interval;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	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 != priv->concheck_x[IS_IPv4].p_max_interval) {
+		_LOGT (LOGD_CONCHECK, "connectivity: [IPv%c] periodic-check: set interval to %u seconds",
+		       nm_utils_addr_family_to_char (addr_family), new_interval);
+		priv->concheck_x[IS_IPv4].p_max_interval = new_interval;
 	}
 
 	if (!new_interval) {
 		/* this will cancel any potentially pending timeout because max-interval is zero.
 		 * But it logs a nice message... */
-		concheck_periodic_schedule_do (self, 0);
+		concheck_periodic_schedule_do (self, addr_family, 0);
 
 		/* also update the fake connectivity state. */
-		concheck_update_state (self, NM_CONNECTIVITY_FAKE, TRUE);
+		concheck_update_state (self, addr_family, NM_CONNECTIVITY_FAKE, TRUE);
 		return;
 	}
 
 	concheck_periodic_schedule_set (self,
+	                                addr_family,
 	                                check_now
 	                                  ? CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART
 	                                  : CONCHECK_SCHEDULE_UPDATE_INTERVAL);
@@ -2715,13 +2782,16 @@ concheck_update_interval (NMDevice *self, gboolean check_now)
 void
 nm_device_check_connectivity_update_interval (NMDevice *self)
 {
-	concheck_update_interval (self, FALSE);
+	concheck_update_interval (self, AF_INET, TRUE);
+	concheck_update_interval (self, AF_INET6, TRUE);
 }
 
 static void
-concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow_periodic_bump)
+concheck_update_state (NMDevice *self, int addr_family,
+                       NMConnectivityState state, gboolean allow_periodic_bump)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	/* @state is a result of the connectivity check. We only expect a precise
 	 * number of possible values. */
@@ -2734,7 +2804,7 @@ concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow
 	if (state == NM_CONNECTIVITY_ERROR) {
 		/* on error, we don't change the current connectivity state,
 		 * except making UNKNOWN to NONE. */
-		state = priv->connectivity_state;
+		state = priv->concheck_x[IS_IPv4].state;
 		if (state == NM_CONNECTIVITY_UNKNOWN)
 			state = NM_CONNECTIVITY_NONE;
 	} else if (state == NM_CONNECTIVITY_FAKE) {
@@ -2753,11 +2823,11 @@ concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow
 			state = NM_CONNECTIVITY_NONE;
 	}
 
-	if (priv->connectivity_state == state) {
+	if (priv->concheck_x[IS_IPv4].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);
+			concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_RETURNED_BUMP);
 		return;
 	}
 	/* we need to update the probe interval before emitting signals. Emitting
@@ -2766,23 +2836,22 @@ concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow
 	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) {
+		concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_RETURNED_MAX);
+	} else if (priv->concheck_x[IS_IPv4].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);
+		concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_RETURNED_MIN);
 	} else {
 		if (allow_periodic_bump)
-			concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_BUMP);
+			concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_RETURNED_BUMP);
 	}
 
 	_LOGD (LOGD_CONCHECK, "connectivity state changed from %s to %s",
-	       nm_connectivity_state_to_string (priv->connectivity_state),
+	       nm_connectivity_state_to_string (priv->concheck_x[IS_IPv4].state),
 	       nm_connectivity_state_to_string (state));
-	priv->connectivity_state = state;
+	priv->concheck_x[IS_IPv4].state = state;
 
-	_notify (self, PROP_CONNECTIVITY);
-	g_signal_emit (self, signals[CONNECTIVITY_CHANGED], 0);
+	_notify (self, IS_IPv4 ? PROP_IP4_CONNECTIVITY : PROP_IP6_CONNECTIVITY);
 
 	if (   priv->state == NM_DEVICE_STATE_ACTIVATED
 	    && !nm_device_sys_iface_state_is_external (self)) {
@@ -2795,10 +2864,39 @@ concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow
 	}
 }
 
+static const char *
+nm_device_get_effective_ip_config_method (NMDevice *self,
+                                          int addr_family)
+{
+	NMDeviceClass *klass;
+	NMConnection *connection = nm_device_get_applied_connection (self);
+	const char *method;
+
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), "" /* bogus */);
+	nm_assert_addr_family (addr_family);
+
+	method = nm_utils_get_ip_config_method (connection, addr_family);
+
+	if (   (addr_family == AF_INET  && nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
+	    || (addr_family == AF_INET6 && nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO))) {
+		klass = NM_DEVICE_GET_CLASS (self);
+		if (klass->get_auto_ip_config_method) {
+			const char *auto_method;
+
+			auto_method = klass->get_auto_ip_config_method (self, addr_family);
+			if (auto_method)
+				return auto_method;
+		}
+	}
+
+	return method;
+}
+
 static void
-concheck_handle_complete (NMDeviceConnectivityHandle *handle,
-                          GError *error)
+concheck_handle_complete (NMDeviceConnectivityHandle *handle, GError *error)
 {
+	const gboolean IS_IPv4 = (handle->addr_family == AF_INET);
+
 	/* The moment we invoke the callback, we unlink it. It signals
 	 * that @handle is handled -- as far as the callee of callback
 	 * is concerned. */
@@ -2810,7 +2908,7 @@ concheck_handle_complete (NMDeviceConnectivityHandle *handle,
 	if (handle->callback) {
 		handle->callback (handle->self,
 		                  handle,
-		                  NM_DEVICE_GET_PRIVATE (handle->self)->connectivity_state,
+		                  NM_DEVICE_GET_PRIVATE (handle->self)->concheck_x[IS_IPv4].state,
 		                  error,
 		                  handle->user_data);
 	}
@@ -2846,7 +2944,8 @@ concheck_cb (NMConnectivity *connectivity,
 		/* 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)",
+		_LOGT (LOGD_CONCHECK, "connectivity: [IPv%c] complete check (seq:%llu, cancelled)",
+		       nm_utils_addr_family_to_char (handle->addr_family),
 		       (long long unsigned) handle->seq);
 		return;
 	}
@@ -2856,7 +2955,8 @@ concheck_cb (NMConnectivity *connectivity,
 
 	self_keep_alive = g_object_ref (self);
 
-	_LOGT (LOGD_CONCHECK, "connectivity: complete check (seq:%llu, state:%s)",
+	_LOGT (LOGD_CONCHECK, "connectivity: [Ipv%c] complete check (seq:%llu, state:%s)",
+	       nm_utils_addr_family_to_char (handle->addr_family),
 	       (long long unsigned) handle->seq,
 	       nm_connectivity_state_to_string (state));
 
@@ -2872,6 +2972,8 @@ concheck_cb (NMConnectivity *connectivity,
 	any_periodic_before = FALSE;
 	any_periodic_after = FALSE;
 	c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) {
+		if (other_handle->addr_family != handle->addr_family)
+			continue;
 		if (other_handle->is_periodic_bump_on_complete) {
 			if (other_handle->seq < seq)
 				any_periodic_before = TRUE;
@@ -2898,7 +3000,7 @@ concheck_cb (NMConnectivity *connectivity,
 	}
 
 	/* first update the new state, and emit signals. */
-	concheck_update_state (self, state, allow_periodic_bump);
+	concheck_update_state (self, handle->addr_family, state, allow_periodic_bump);
 
 	handle_is_alive = FALSE;
 
@@ -2910,6 +3012,8 @@ concheck_cb (NMConnectivity *connectivity,
 	 * @handle, as they are automatically obsoleted. */
 check_handles:
 	c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) {
+		if (other_handle->addr_family != handle->addr_family)
+			continue;
 		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.
@@ -2947,6 +3051,7 @@ check_handles:
 
 static NMDeviceConnectivityHandle *
 concheck_start (NMDevice *self,
+                int addr_family,
                 NMDeviceConnectivityCallback callback,
                 gpointer user_data,
                 gboolean is_periodic)
@@ -2954,6 +3059,7 @@ concheck_start (NMDevice *self,
 	static guint64 seq_counter = 0;
 	NMDevicePrivate *priv;
 	NMDeviceConnectivityHandle *handle;
+	const char *ifname;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
@@ -2967,14 +3073,51 @@ concheck_start (NMDevice *self,
 	handle->is_periodic = is_periodic;
 	handle->is_periodic_bump = is_periodic;
 	handle->is_periodic_bump_on_complete = is_periodic;
+	handle->addr_family = addr_family;
 
 	c_list_link_tail (&priv->concheck_lst_head, &handle->concheck_lst);
 
-	_LOGT (LOGD_CONCHECK, "connectivity: start check (seq:%llu%s)",
+	_LOGT (LOGD_CONCHECK, "connectivity: [IPv%c] start check (seq:%llu%s)",
+	       nm_utils_addr_family_to_char (addr_family),
 	       (long long unsigned) handle->seq,
 	       is_periodic ? ", periodic-check" : "");
 
+	if (   addr_family == AF_INET
+	    && !priv->concheck_rp_filter_checked) {
+
+		if ((ifname = nm_device_get_ip_iface_from_platform (self))) {
+			int val, val_all;
+
+			val = nm_platform_sysctl_ip_conf_get_int_checked (nm_device_get_platform (self),
+			                                                  AF_INET,
+			                                                  ifname,
+			                                                  "rp_filter",
+			                                                  10, 0, 2, 3);
+			if (val < 2) {
+				val_all = nm_platform_sysctl_ip_conf_get_int_checked (nm_device_get_platform (self),
+				                                                      AF_INET,
+				                                                      "all",
+				                                                      "rp_filter",
+				                                                      10, 0, 2, val);
+				if (val_all > val) {
+					val = val_all;
+					ifname = "all";
+				}
+			}
+
+			if (val == 1) {
+				_LOGW (LOGD_CONCHECK, "connectivity: \"/proc/sys/net/ipv4/conf/%s/rp_filter\" is set to \"1\". "
+				       "This might break connectivity checking for IPv4 on this device", ifname);
+			}
+		}
+
+		/* we only check once per device. It's a warning after all.  */
+		priv->concheck_rp_filter_checked = TRUE;
+	}
+
 	handle->c_handle = nm_connectivity_check_start (concheck_get_mgr (self),
+	                                                handle->addr_family,
+	                                                nm_device_get_ip_ifindex (self),
 	                                                nm_device_get_ip_iface (self),
 	                                                concheck_cb,
 	                                                handle);
@@ -2983,6 +3126,7 @@ concheck_start (NMDevice *self,
 
 NMDeviceConnectivityHandle *
 nm_device_check_connectivity (NMDevice *self,
+                              int addr_family,
                               NMDeviceConnectivityCallback callback,
                               gpointer user_data)
 {
@@ -2991,8 +3135,8 @@ nm_device_check_connectivity (NMDevice *self,
 	if (!concheck_is_possible (self))
 		return NULL;
 
-	concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_CHECK_EXTERNAL);
-	handle = concheck_start (self, callback, user_data, FALSE);
+	concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_CHECK_EXTERNAL);
+	handle = concheck_start (self, addr_family, callback, user_data, FALSE);
 	return handle;
 }
 
@@ -3014,11 +3158,26 @@ nm_device_check_connectivity_cancel (NMDeviceConnectivityHandle *handle)
 }
 
 NMConnectivityState
-nm_device_get_connectivity_state (NMDevice *self)
+nm_device_get_connectivity_state (NMDevice *self, int addr_family)
 {
+	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+
 	g_return_val_if_fail (NM_IS_DEVICE (self), NM_CONNECTIVITY_UNKNOWN);
+	nm_assert_addr_family (addr_family);
 
-	return NM_DEVICE_GET_PRIVATE (self)->connectivity_state;
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	switch (addr_family) {
+	case AF_INET:
+	case AF_INET6:
+		return priv->concheck_x[IS_IPv4].state;
+	default:
+		nm_assert (addr_family == AF_UNSPEC);
+		return NM_MAX_WITH_CMP (nm_connectivity_state_cmp,
+		                        priv->concheck_x[0].state,
+		                        priv->concheck_x[1].state);
+	}
 }
 
 /*****************************************************************************/
@@ -3053,6 +3212,7 @@ find_slave_info (NMDevice *self, NMDevice *slave)
 static gboolean
 nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *connection)
 {
+	NMDevicePrivate *priv;
 	SlaveInfo *info;
 	gboolean success = FALSE;
 	gboolean configure;
@@ -3061,6 +3221,7 @@ nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *c
 	g_return_val_if_fail (slave != NULL, FALSE);
 	g_return_val_if_fail (NM_DEVICE_GET_CLASS (self)->enslave_slave != NULL, FALSE);
 
+	priv = NM_DEVICE_GET_PRIVATE (self);
 	info = find_slave_info (self, slave);
 	if (!info)
 		return FALSE;
@@ -3083,15 +3244,20 @@ nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *c
 	 */
 	nm_device_update_hw_address (self);
 
+	/* Send ARP announcements if did not yet and have addresses. */
+	if (   priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
+	    && !priv->acd.announcing)
+		nm_device_arp_announce (self);
+
 	/* Restart IP configuration if we're waiting for slaves.  Do this
 	 * after updating the hardware address as IP config may need the
 	 * new address.
 	 */
 	if (success) {
-		if (NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT)
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_WAIT)
 			nm_device_activate_stage3_ip4_start (self);
 
-		if (NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT)
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_WAIT)
 			nm_device_activate_stage3_ip6_start (self);
 	}
 
@@ -3421,11 +3587,14 @@ nm_device_set_carrier (NMDevice *self, gboolean carrier)
 static void
 nm_device_set_carrier_from_platform (NMDevice *self)
 {
+	int ifindex;
+
 	if (nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) {
-		if (!nm_device_has_capability (self, NM_DEVICE_CAP_NONSTANDARD_CARRIER)) {
+		if (   !nm_device_has_capability (self, NM_DEVICE_CAP_NONSTANDARD_CARRIER)
+		    && (ifindex = nm_device_get_ip_ifindex (self)) > 0) {
 			nm_device_set_carrier (self,
 			                       nm_platform_link_is_connected (nm_device_get_platform (self),
-			                                                      nm_device_get_ip_ifindex (self)));
+			                                                      ifindex));
 		}
 	} else {
 		NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -3599,6 +3768,8 @@ device_link_changed (NMDevice *self)
 	priv->device_link_changed_down = FALSE;
 
 	ifindex = nm_device_get_ifindex (self);
+	if (ifindex <= 0)
+		return G_SOURCE_REMOVE;
 	pllink = nm_platform_link_get (nm_device_get_platform (self), ifindex);
 	if (!pllink)
 		return G_SOURCE_REMOVE;
@@ -3709,11 +3880,13 @@ device_link_changed (NMDevice *self)
 	if (priv->up && (!was_up || seen_down)) {
 		/* the link was down and just came up. That happens for example, while changing MTU.
 		 * We must restore IP configuration. */
-		if (priv->ip4_state == IP_DONE) {
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 			if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 				_LOGW (LOGD_IP4, "failed applying IP4 config after link comes up again");
 		}
-		if (priv->ip6_state == IP_DONE) {
+
+		priv->linklocal6_dad_counter = 0;
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 			if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 				_LOGW (LOGD_IP6, "failed applying IP6 config after link comes up again");
 		}
@@ -3798,126 +3971,6 @@ link_changed_cb (NMPlatform *platform,
 
 /*****************************************************************************/
 
-typedef struct {
-	in_addr_t network;
-	guint8 plen;
-} IP4RPFilterData;
-
-static guint
-_v4_has_shadowed_routes_detect_hash (const IP4RPFilterData *d)
-{
-	NMHashState h;
-
-	nm_hash_init (&h, 1105201169u);
-	nm_hash_update_vals (&h,
-	                     d->network,
-	                     d->plen);
-	return nm_hash_complete (&h);
-}
-
-static gboolean
-_v4_has_shadowed_routes_detect_equal (const IP4RPFilterData *d1, const IP4RPFilterData *d2)
-{
-	return d1->network == d2->network && d1->plen == d2->plen;
-}
-
-static gboolean
-_v4_has_shadowed_routes_detect (NMDevice *self)
-{
-	NMPlatform *platform;
-	int ifindex;
-	NMPLookup lookup;
-	const NMDedupMultiHeadEntry *head_entry;
-	NMDedupMultiIter iter;
-	const NMPObject *o;
-	guint data_len;
-	gs_unref_hashtable GHashTable *data_hash = NULL;
-	gs_free IP4RPFilterData *data_arr = NULL;
-
-	ifindex = nm_device_get_ip_ifindex (self);
-	if (ifindex <= 0)
-		return FALSE;
-
-	platform = nm_device_get_platform (self);
-
-	head_entry = nm_platform_lookup (platform,
-	                                 nmp_lookup_init_object (&lookup,
-	                                                         NMP_OBJECT_TYPE_IP4_ROUTE,
-	                                                         ifindex));
-	if (!head_entry)
-		return FALSE;
-
-	/* first, create a lookup index @data_hash for all network/plen pairs. */
-	data_len = 0;
-	data_arr = g_new (IP4RPFilterData, head_entry->len);
-	data_hash = g_hash_table_new ((GHashFunc) _v4_has_shadowed_routes_detect_hash,
-	                              (GEqualFunc) _v4_has_shadowed_routes_detect_equal);
-
-	nmp_cache_iter_for_each (&iter, head_entry, &o) {
-		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (o);
-		IP4RPFilterData *d;
-
-		nm_assert (r->ifindex == ifindex);
-
-		if (   NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
-		    || r->table_coerced)
-			continue;
-
-		d = &data_arr[data_len++];
-		d->network = nm_utils_ip4_address_clear_host_address (r->network, r->plen);
-		d->plen = r->plen;
-		g_hash_table_add (data_hash, d);
-	}
-
-	/* then, search if there is any route on another interface with the same
-	 * network/plen destination. If yes, we consider this a multihoming
-	 * setup. */
-	head_entry = nm_platform_lookup (platform,
-	                                 nmp_lookup_init_obj_type (&lookup,
-	                                                           NMP_OBJECT_TYPE_IP4_ROUTE));
-	nmp_cache_iter_for_each (&iter, head_entry, &o) {
-		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (o);
-		IP4RPFilterData d;
-
-		if (   r->ifindex == ifindex
-		    || NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
-		    || r->table_coerced)
-			continue;
-
-		d.network = nm_utils_ip4_address_clear_host_address (r->network, r->plen);
-		d.plen = r->plen;
-		if (g_hash_table_contains (data_hash, &d))
-			return TRUE;
-	}
-
-	return FALSE;
-}
-
-static void
-ip4_rp_filter_update (NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *ip4_rp_filter;
-
-	if (   priv->v4_has_shadowed_routes
-	    || nm_device_get_best_default_route (self, AF_INET)) {
-		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;
-		}
-		/* Loose rp_filter */
-		ip4_rp_filter = "2";
-	} else {
-		/* Default rp_filter */
-		ip4_rp_filter = NULL;
-	}
-
-	if (ip4_rp_filter != priv->ip4_rp_filter) {
-		nm_device_ipv4_sysctl_set (self, "rp_filter", ip4_rp_filter);
-		priv->ip4_rp_filter = ip4_rp_filter;
-	}
-}
-
 static void
 link_changed (NMDevice *self, const NMPlatformLink *pllink)
 {
@@ -4331,8 +4384,7 @@ realize_start_setup (NMDevice *self,
 	 * NetworkManager might down the interface or remove the 127.0.0.1 address. */
 	nm_device_set_unmanaged_flags (self,
 	                               NM_UNMANAGED_BY_TYPE,
-	                                  is_loopback (self)
-	                               || NM_IS_DEVICE_WIREGUARD (self));
+	                               is_loopback (self));
 
 	nm_device_set_unmanaged_by_user_udev (self);
 	nm_device_set_unmanaged_by_user_conf (self);
@@ -4804,7 +4856,8 @@ nm_device_master_release_slaves (NMDevice *self)
 	if (priv->state == NM_DEVICE_STATE_FAILED)
 		reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
 
-	if (!nm_platform_link_get (nm_device_get_platform (self), priv->ifindex))
+	if (   priv->ifindex <= 0
+	    || !nm_platform_link_get (nm_device_get_platform (self), priv->ifindex))
 		configure = FALSE;
 
 	c_list_for_each_safe (iter, safe, &priv->slaves) {
@@ -4855,21 +4908,11 @@ static gboolean
 get_ip_config_may_fail (NMDevice *self, int addr_family)
 {
 	NMConnection *connection;
-	NMSettingIPConfig *s_ip = NULL;
+	NMSettingIPConfig *s_ip;
 
 	connection = nm_device_get_applied_connection (self);
 
-	/* Fail the connection if the failed IP method is required to complete */
-	switch (addr_family) {
-	case AF_INET:
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-		break;
-	case AF_INET6:
-		s_ip = nm_connection_get_setting_ip6_config (connection);
-		break;
-	default:
-		nm_assert_not_reached ();
-	}
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 
 	return !s_ip || nm_setting_ip_config_get_may_fail (s_ip);
 }
@@ -4903,32 +4946,32 @@ check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 	    && !priv->is_enslaved)
 		return;
 
-	s_ip4 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP4_CONFIG);
+	s_ip4 = nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP4_CONFIG);
 	if (s_ip4 && nm_streq0 (nm_setting_ip_config_get_method (s_ip4),
 	                        NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
 		ip4_disabled = TRUE;
 
-	s_ip6 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
+	s_ip6 = nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
 	if (s_ip6 && nm_streq0 (nm_setting_ip_config_get_method (s_ip6),
 	                        NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
 		ip6_ignore = TRUE;
 
-	if (   priv->ip4_state == IP_DONE
-	    && priv->ip6_state == IP_DONE) {
+	if (   priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
+	    && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 		/* Both method completed (or disabled), proceed with activation */
 		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
 		return;
 	}
 
-	if (   (priv->ip4_state == IP_FAIL || (ip4_disabled && priv->ip4_state == IP_DONE))
-	    && (priv->ip6_state == IP_FAIL || (ip6_ignore && priv->ip6_state == IP_DONE))) {
+	if (   (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL || (ip4_disabled && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE))
+	    && (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL || (ip6_ignore && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE))) {
 		/* Either both methods failed, or only one failed and the other is
 		 * disabled */
 		if (nm_device_sys_iface_state_is_external_or_assume (self)) {
 			/* We have assumed configuration, but couldn't redo it. No problem,
 			 * move to check state. */
-			_set_ip_state (self, AF_INET, IP_DONE);
-			_set_ip_state (self, AF_INET6, IP_DONE);
+			_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 			state = NM_DEVICE_STATE_IP_CHECK;
 		} else if (   may_fail
 		           && get_ip_config_may_fail (self, AF_INET)
@@ -4951,14 +4994,14 @@ check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 	}
 
 	/* If a method is still pending but required, wait */
-	if (priv->ip4_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET))
+	if (priv->ip_state_4 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail (self, AF_INET))
 		return;
-	if (priv->ip6_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET6))
+	if (priv->ip_state_6 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail (self, AF_INET6))
 		return;
 
 	/* If at least a method has completed, proceed with activation */
-	if (   (priv->ip4_state == IP_DONE && !ip4_disabled)
-	    || (priv->ip6_state == IP_DONE && !ip6_ignore)) {
+	if (   (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !ip4_disabled)
+	    || (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE && !ip6_ignore)) {
 		if (full_state_update)
 			nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
 		return;
@@ -5267,7 +5310,7 @@ nm_device_autoconnect_allowed (NMDevice *self)
 	/* The 'autoconnect-allowed' signal is emitted on a device to allow
 	 * other listeners to block autoconnect on the device if they wish.
 	 * This is mainly used by the OLPC Mesh devices to block autoconnect
-	 * on their companion WiFi device as they share radio resources and
+	 * on their companion Wi-Fi device as they share radio resources and
 	 * cannot be connected at the same time.
 	 */
 
@@ -5486,10 +5529,13 @@ nm_device_generate_connection (NMDevice *self,
 		nm_connection_add_setting (connection, nm_setting_proxy_new ());
 
 		pllink = nm_platform_link_get (nm_device_get_platform (self), priv->ifindex);
-		if (pllink && pllink->inet6_token.id) {
+		if (   pllink
+		    && pllink->inet6_token.id) {
+			char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
 			g_object_set (s_ip6,
 			              NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, NM_IN6_ADDR_GEN_MODE_EUI64,
-			              NM_SETTING_IP6_CONFIG_TOKEN, nm_utils_inet6_interface_identifier_to_token (pllink->inet6_token, NULL),
+			              NM_SETTING_IP6_CONFIG_TOKEN, nm_utils_inet6_interface_identifier_to_token (pllink->inet6_token, sbuf),
 			              NULL);
 		}
 	}
@@ -5507,8 +5553,8 @@ nm_device_generate_connection (NMDevice *self,
 	/* Ignore the connection if it has no IP configuration,
 	 * no slave configuration, and is not a master interface.
 	 */
-	ip4_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	ip6_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	ip4_method = nm_utils_get_ip_config_method (connection, AF_INET);
+	ip6_method = nm_utils_get_ip_config_method (connection, AF_INET6);
 	if (   g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
 	    && g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0
 	    && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))
@@ -5646,7 +5692,6 @@ check_connection_compatible (NMDevice *self, NMConnection *connection, GError **
 	NMDeviceClass *klass;
 	const char *const *patterns;
 	NMSettingMatch *s_match;
-	guint num_patterns;
 
 	klass = NM_DEVICE_GET_CLASS (self);
 	if (klass->connection_type_check_compatible) {
@@ -5687,6 +5732,8 @@ check_connection_compatible (NMDevice *self, NMConnection *connection, GError **
 	s_match = (NMSettingMatch *) nm_connection_get_setting (connection,
 	                                                        NM_TYPE_SETTING_MATCH);
 	if (s_match) {
+		guint num_patterns = 0;
+
 		patterns = nm_setting_match_get_interface_names (s_match, &num_patterns);
 		if (!nm_wildcard_match_check (device_iface, patterns, num_patterns)) {
 			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
@@ -5788,7 +5835,7 @@ unmanaged_on_quit (NMDevice *self)
 	/* the only exception are IPv4 shared connections. We unmanage them on quit. */
 	connection = nm_device_get_applied_connection (self);
 	if (connection) {
-		if (NM_IN_STRSET (nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG),
+		if (NM_IN_STRSET (nm_utils_get_ip_config_method (connection, AF_INET),
 		                  NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
 			/* shared connections are to be unmangaed. */
 			return TRUE;
@@ -6112,7 +6159,7 @@ lldp_rx_enabled (NMDevice *self)
 	lldp = nm_setting_connection_get_lldp (s_con);
 	if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT) {
 		lldp = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-		                                                    "connection.lldp",
+		                                                    NM_CON_DEFAULT ("connection.lldp"),
 		                                                    self,
 		                                                    NM_SETTING_CONNECTION_LLDP_DEFAULT,
 		                                                    NM_SETTING_CONNECTION_LLDP_ENABLE_RX,
@@ -6207,7 +6254,7 @@ act_stage1_prepare (NMDevice *self, NMDeviceStateReason *out_failure_reason)
 
 	if (   priv->ifindex > 0
 	    && nm_device_has_capability (self, NM_DEVICE_CAP_SRIOV)
-	    && (s_sriov = (NMSettingSriov *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))) {
+	    && (s_sriov = nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))) {
 		nm_auto_freev NMPlatformVF **plat_vfs = NULL;
 		gs_free_error GError *error = NULL;
 		NMSriovVF *vf;
@@ -6216,7 +6263,7 @@ act_stage1_prepare (NMDevice *self, NMDeviceStateReason *out_failure_reason)
 		autoprobe = nm_setting_sriov_get_autoprobe_drivers (s_sriov);
 		if (autoprobe == NM_TERNARY_DEFAULT) {
 			autoprobe = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-			                                                         "sriov.autoprobe-drivers",
+			                                                         NM_CON_DEFAULT ("sriov.autoprobe-drivers"),
 			                                                         self,
 			                                                         NM_TERNARY_FALSE,
 			                                                         NM_TERNARY_TRUE,
@@ -6271,8 +6318,8 @@ activate_stage1_device_prepare (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 
-	_set_ip_state (self, AF_INET, IP_NONE);
-	_set_ip_state (self, AF_INET6, IP_NONE);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_NONE);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_NONE);
 
 	/* Notify the new ActiveConnection along with the state change */
 	nm_dbus_track_obj_path_set (&priv->act_request,
@@ -6449,23 +6496,25 @@ static void
 activate_stage2_device_config (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceClass *klass;
 	NMActStageReturn ret;
 	gboolean no_firmware = FALSE;
 	CList *iter;
 
 	nm_device_state_changed (self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
-	/* Assumed connections were already set up outside NetworkManager */
-	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
-		NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-
+	if (!nm_device_sys_iface_state_is_external_or_assume (self))
 		_ethtool_state_set (self);
 
+	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
 		if (!tc_commit (self)) {
 			_LOGW (LOGD_IP6, "failed applying traffic control rules");
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+			return;
 		}
+	}
 
+	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
 		if (!nm_device_bring_up (self, FALSE, &no_firmware)) {
 			if (no_firmware)
 				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_FIRMWARE_MISSING);
@@ -6473,15 +6522,21 @@ activate_stage2_device_config (NMDevice *self)
 				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
 			return;
 		}
+	}
+
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (   klass->act_stage2_config_also_for_external_or_assume
+	    || !nm_device_sys_iface_state_is_external_or_assume (self)) {
+		NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-		ret = NM_DEVICE_GET_CLASS (self)->act_stage2_config (self, &failure_reason);
+		ret = klass->act_stage2_config (self, &failure_reason);
 		if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
 			return;
-		else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+		if (ret != NM_ACT_STAGE_RETURN_SUCCESS) {
+			nm_assert (ret == NM_ACT_STAGE_RETURN_FAILURE);
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
 			return;
 		}
-		g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 	}
 
 	/* If we have slaves that aren't yet enslaved, do that now */
@@ -6498,6 +6553,7 @@ activate_stage2_device_config (NMDevice *self)
 	}
 
 	lldp_init (self, TRUE);
+
 	nm_device_activate_schedule_stage3_ip_config_start (self);
 }
 
@@ -6559,7 +6615,7 @@ nm_device_ip_method_failed (NMDevice *self,
 	g_return_if_fail (NM_IS_DEVICE (self));
 	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	_set_ip_state (self, addr_family, IP_FAIL);
+	_set_ip_state (self, addr_family, NM_DEVICE_IP_STATE_FAIL);
 
 	if (get_ip_config_may_fail (self, addr_family))
 		check_ip_state (self, FALSE, (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG));
@@ -6586,7 +6642,7 @@ get_ipv4_dad_timeout (NMDevice *self)
 		return timeout;
 
 	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                    "ipv4.dad-timeout",
+	                                                    NM_CON_DEFAULT ("ipv4.dad-timeout"),
 	                                                    self,
 	                                                    0,
 	                                                    NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX,
@@ -6594,17 +6650,15 @@ get_ipv4_dad_timeout (NMDevice *self)
 }
 
 static void
-acd_data_destroy (gpointer ptr, GClosure *closure)
+acd_data_destroy (gpointer 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 (AcdData, data);
-	}
+	for (i = 0; data->configs && data->configs[i]; i++)
+		g_object_unref (data->configs[i]);
+	g_free (data->configs);
+	g_slice_free (AcdData, data);
 }
 
 static void
@@ -6612,11 +6666,10 @@ ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean succes
 {
 	NMConnection *connection;
 	const char *method;
-	NMIP4Config *empty;
 
 	connection = nm_device_get_applied_connection (self);
 	nm_assert (connection);
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	method = nm_utils_get_ip_config_method (connection, AF_INET);
 	nm_assert (NM_IN_STRSET (method,
 	                         NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
 	                         NM_SETTING_IP4_CONFIG_METHOD_AUTO));
@@ -6627,19 +6680,18 @@ ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean succes
 		return;
 	}
 
-	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
-		empty = _ip4_config_new (self);
-		nm_device_activate_schedule_ip4_config_result (self, empty);
-		g_object_unref (empty);
-	} else {
-		if (NM_DEVICE_GET_PRIVATE (self)->ip4_state != IP_DONE)
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+		nm_device_activate_schedule_ip_config_result (self, AF_INET, NULL);
+	else {
+		if (NM_DEVICE_GET_PRIVATE (self)->ip_state_4 != NM_DEVICE_IP_STATE_DONE)
 			ip_config_merge_and_apply (self, AF_INET, TRUE);
 	}
 }
 
 static void
-acd_manager_probe_terminated (NMAcdManager *acd_manager, AcdData *data)
+acd_manager_probe_terminated (NMAcdManager *acd_manager, gpointer user_data)
 {
+	AcdData *data = user_data;
 	NMDevice *self;
 	NMDevicePrivate *priv;
 	NMDedupMultiIter ipconf_iter;
@@ -6653,13 +6705,15 @@ acd_manager_probe_terminated (NMAcdManager *acd_manager, AcdData *data)
 
 	for (i = 0; data->configs && data->configs[i]; i++) {
 		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, data->configs[i], &address) {
+			char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
 			result = nm_acd_manager_check_address (acd_manager, address->address);
 			success &= result;
 
 			_NMLOG (result ? LOGL_DEBUG : LOGL_WARN,
 			        LOGD_DEVICE,
 			        "IPv4 DAD result: address %s is %s",
-			        nm_utils_inet4_ntop (address->address, NULL),
+			        nm_utils_inet4_ntop (address->address, sbuf),
 			        result ? "unique" : "duplicate");
 		}
 	}
@@ -6667,7 +6721,7 @@ acd_manager_probe_terminated (NMAcdManager *acd_manager, AcdData *data)
 	data->callback (self, data->configs, success);
 
 	priv->acd.dad_list = g_slist_remove (priv->acd.dad_list, acd_manager);
-	nm_acd_manager_destroy (acd_manager);
+	nm_acd_manager_free (acd_manager);
 }
 
 /**
@@ -6683,6 +6737,10 @@ acd_manager_probe_terminated (NMAcdManager *acd_manager, AcdData *data)
 static void
 ipv4_dad_start (NMDevice *self, NMIP4Config **configs, AcdCallback cb)
 {
+	static const NMAcdCallbacks acd_callbacks = {
+		.probe_terminated_callback = acd_manager_probe_terminated,
+		.user_data_destroy         = acd_data_destroy,
+	};
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMAcdManager *acd_manager;
 	const NMPlatformIP4Address *address;
@@ -6726,26 +6784,23 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, AcdCallback cb)
 		return;
 	}
 
-	/* 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. */
-	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 (AcdData);
 	data->configs = configs;
 	data->callback = cb;
 	data->device = self;
 
+	acd_manager = nm_acd_manager_new (nm_device_get_ip_ifindex (self),
+	                                  hwaddr_arr,
+	                                  length,
+	                                  &acd_callbacks,
+	                                  data);
+	priv->acd.dad_list = g_slist_append (priv->acd.dad_list, acd_manager);
+
 	for (i = 0; configs[i]; i++) {
 		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, configs[i], &address)
 			nm_acd_manager_add_address (acd_manager, address->address);
 	}
 
-	g_signal_connect_data (acd_manager, NM_ACD_MANAGER_PROBE_TERMINATED,
-	                       G_CALLBACK (acd_manager_probe_terminated), data,
-	                       acd_data_destroy, 0);
-
 	ret = nm_acd_manager_start_probe (acd_manager, timeout);
 
 	if (!ret) {
@@ -6755,7 +6810,7 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, AcdCallback cb)
 		cb (self, configs, TRUE);
 
 		priv->acd.dad_list = g_slist_remove (priv->acd.dad_list, acd_manager);
-		nm_acd_manager_destroy (acd_manager);
+		nm_acd_manager_free (acd_manager);
 	}
 }
 
@@ -6783,7 +6838,7 @@ ipv4ll_get_ip4_config (NMDevice *self, guint32 lla)
 	NMPlatformIP4Address address;
 	NMPlatformIP4Route route;
 
-	config = _ip4_config_new (self);
+	config = nm_device_ip4_config_new (self);
 	g_assert (config);
 
 	memset (&address, 0, sizeof (address));
@@ -6811,8 +6866,6 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 {
 	NMDevice *self = data;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection = NULL;
-	const char *method;
 	struct in_addr address;
 	NMIP4Config *config;
 	int r;
@@ -6820,13 +6873,8 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 	if (priv->act_request.obj == NULL)
 		return;
 
-	connection = nm_act_request_get_applied_connection (priv->act_request.obj);
-	g_assert (connection);
-
-	/* Ignore if the connection isn't an AutoIP connection */
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) != 0)
-		return;
+	nm_assert (nm_streq (nm_device_get_effective_ip_config_method (self, AF_INET),
+	                     NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL));
 
 	switch (event) {
 	case SD_IPV4LL_EVENT_BIND:
@@ -6850,11 +6898,11 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 			return;
 		}
 
-		if (priv->ip4_state == IP_CONF) {
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) {
 			nm_clear_g_source (&priv->ipv4ll_timeout);
-			nm_device_activate_schedule_ip4_config_result (self, config);
-		} else if (priv->ip4_state == IP_DONE) {
-			applied_config_init (&priv->dev_ip4_config, config);
+			nm_device_activate_schedule_ip_config_result (self, AF_INET, NM_IP_CONFIG_CAST (config));
+		} else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
+			applied_config_init (&priv->dev_ip_config_4, 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);
@@ -6881,8 +6929,8 @@ ipv4ll_timeout_cb (gpointer user_data)
 		priv->ipv4ll_timeout = 0;
 		ipv4ll_cleanup (self);
 
-		if (priv->ip4_state == IP_CONF)
-			nm_device_activate_schedule_ip4_config_timeout (self);
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET);
 	}
 
 	return FALSE;
@@ -6965,7 +7013,7 @@ ensure_con_ip_config (NMDevice *self, int addr_family)
 	if (!connection)
 		return;
 
-	con_ip_config = _ip_config_new (self, addr_family);
+	con_ip_config = nm_device_ip_config_new (self, addr_family);
 
 	if (IS_IPv4) {
 		nm_ip4_config_merge_setting (NM_IP4_CONFIG (con_ip_config),
@@ -6998,6 +7046,7 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	priv->dhcp4.was_active = FALSE;
 	nm_clear_g_source (&priv->dhcp4.grace_id);
 	g_clear_pointer (&priv->dhcp4.pac_url, g_free);
 	g_clear_pointer (&priv->dhcp4.root_path, g_free);
@@ -7046,10 +7095,9 @@ ip_config_merge_and_apply (NMDevice *self,
 
 	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	if (connection) {
-		NMSettingIPConfig *s_ip =   IS_IPv4
-		                          ? nm_connection_get_setting_ip4_config (connection)
-		                          : nm_connection_get_setting_ip6_config (connection);
+		NMSettingIPConfig *s_ip;
 
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 		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);
@@ -7068,7 +7116,7 @@ ip_config_merge_and_apply (NMDevice *self,
 		}
 	}
 
-	composite = _ip_config_new (self, addr_family);
+	composite = nm_device_ip_config_new (self, addr_family);
 
 	if (!IS_IPv4) {
 		nm_ip6_config_set_privacy (NM_IP6_CONFIG (composite),
@@ -7110,7 +7158,7 @@ ip_config_merge_and_apply (NMDevice *self,
 	if (commit) {
 		gboolean v;
 
-		v = default_route_metric_penalty_detect (self);
+		v = default_route_metric_penalty_detect (self, addr_family);
 		if (IS_IPv4)
 			priv->default_route_metric_penalty_ip4_has = v;
 		else
@@ -7120,7 +7168,7 @@ ip_config_merge_and_apply (NMDevice *self,
 	/* Merge all the IP configs into the composite config */
 
 	if (IS_IPv4) {
-		config = applied_config_get_current (&priv->dev_ip4_config);
+		config = applied_config_get_current (&priv->dev_ip_config_4);
 		if (config) {
 			nm_ip4_config_merge (NM_IP4_CONFIG (composite), NM_IP4_CONFIG (config),
 			                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
@@ -7161,7 +7209,7 @@ ip_config_merge_and_apply (NMDevice *self,
 	/* Merge WWAN config *last* to ensure modem-given settings overwrite
 	 * any external stuff set by pppd or other scripts.
 	 */
-	config = applied_config_get_current (&priv->wwan_ip_config_x[IS_IPv4]);
+	config = applied_config_get_current (&priv->dev2_ip_config_x[IS_IPv4]);
 	if (config) {
 		nm_ip_config_merge (composite, config,
 		                      (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
@@ -7212,15 +7260,15 @@ ip_config_merge_and_apply (NMDevice *self,
 	}
 
 	if (!IS_IPv4) {
-		if (commit) {
-			NMUtilsIPv6IfaceId iid;
+		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);
-			}
+		if (   commit
+		    && priv->ndisc_started
+		    && ip6_addr_gen_token
+		    && nm_utils_ipv6_interface_identifier_get_from_token (&iid, ip6_addr_gen_token)) {
+			nm_platform_link_set_ipv6_token (nm_device_get_platform (self),
+			                                 nm_device_get_ip_ifindex (self),
+			                                 iid);
 		}
 	}
 
@@ -7242,7 +7290,7 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 
 	g_return_val_if_fail (config, FALSE);
 
-	applied_config_init (&priv->dev_ip4_config, config);
+	applied_config_init (&priv->dev_ip_config_4, config);
 
 	if (!ip_config_merge_and_apply (self, AF_INET, TRUE)) {
 		_LOGW (LOGD_DHCP4, "failed to update IPv4 config for DHCP change.");
@@ -7276,28 +7324,32 @@ dhcp4_grace_period_expired (gpointer user_data)
 }
 
 static void
-dhcp4_fail (NMDevice *self)
+dhcp4_fail (NMDevice *self, NMDhcpState dhcp_state)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	_LOGD (LOGD_DHCP4, "DHCPv4 failed (ip_state %s)",
-	       _ip_state_to_string (priv->ip4_state));
+	_LOGD (LOGD_DHCP4, "DHCPv4 failed (ip_state %s, was_active %d)",
+	       _ip_state_to_string (priv->ip_state_4),
+	       priv->dhcp4.was_active);
 
 	/* Keep client running if there are static addresses configured
 	 * on the interface.
 	 */
-	if (   priv->ip4_state == IP_DONE
+	if (   priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
 	    && 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.
+	/* Fail the method when one of the following is true:
+	 * 1) the DHCP client terminated: it does not make sense to start a grace
+	 *    period without a client running;
+	 * 2) we failed to get an initial lease AND the client was
+	 *    not active before.
 	 */
-	if (   !priv->dhcp4.was_active
-	    && priv->ip4_state == IP_CONF) {
+	if (   dhcp_state == NM_DHCP_STATE_TERMINATED
+	    || (!priv->dhcp4.was_active && priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)) {
 		dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-		nm_device_activate_schedule_ip4_config_timeout (self);
+		nm_device_activate_schedule_ip_config_timeout (self, AF_INET);
 		return;
 	}
 
@@ -7329,7 +7381,7 @@ static void
 dhcp4_dad_cb (NMDevice *self, NMIP4Config **configs, gboolean success)
 {
 	if (success)
-		nm_device_activate_schedule_ip4_config_result (self, configs[1]);
+		nm_device_activate_schedule_ip_config_result (self, AF_INET, NM_IP_CONFIG_CAST (configs[1]));
 	else {
 		nm_device_ip_method_failed (self, AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
@@ -7358,7 +7410,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 	case NM_DHCP_STATE_BOUND:
 		if (!ip4_config) {
 			_LOGW (LOGD_DHCP4, "failed to get IPv4 config in response to DHCP event.");
-			dhcp4_fail (self);
+			dhcp4_fail (self, state);
 			break;
 		}
 
@@ -7367,8 +7419,8 @@ dhcp4_state_changed (NMDhcpClient *client,
 		/* After some failures, we have been able to renew the lease:
 		 * update the ip state
 		 */
-		if (priv->ip4_state == IP_FAIL)
-			_set_ip_state (self, AF_INET, IP_CONF);
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL)
+			_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_CONF);
 
 		g_free (priv->dhcp4.pac_url);
 		priv->dhcp4.pac_url = g_strdup (g_hash_table_lookup (options, "wpad"));
@@ -7380,11 +7432,11 @@ dhcp4_state_changed (NMDhcpClient *client,
 		nm_dhcp4_config_set_options (priv->dhcp4.config, options);
 		_notify (self, PROP_DHCP4_CONFIG);
 
-		if (priv->ip4_state == IP_CONF) {
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) {
 			connection = nm_device_get_applied_connection (self);
 			g_assert (connection);
 
-			manual = _ip4_config_new (self);
+			manual = nm_device_ip4_config_new (self);
 			nm_ip4_config_merge_setting (manual,
 			                             nm_connection_get_setting_ip4_config (connection),
 			                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
@@ -7397,24 +7449,25 @@ dhcp4_state_changed (NMDhcpClient *client,
 			configs[1] = g_object_ref (ip4_config);
 
 			ipv4_dad_start (self, configs, dhcp4_dad_cb);
-		} else if (priv->ip4_state == IP_DONE) {
+		} else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 			if (dhcp4_lease_change (self, ip4_config))
 				nm_device_update_metered (self);
 			else
-				dhcp4_fail (self);
+				dhcp4_fail (self, state);
 		}
 		break;
 	case NM_DHCP_STATE_TIMEOUT:
-		dhcp4_fail (self);
+		dhcp4_fail (self, state);
 		break;
 	case NM_DHCP_STATE_EXPIRE:
 		/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
-		if (priv->ip4_state == IP_CONF)
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)
 			break;
 		/* fall through */
 	case NM_DHCP_STATE_DONE:
 	case NM_DHCP_STATE_FAIL:
-		dhcp4_fail (self);
+	case NM_DHCP_STATE_TERMINATED:
+		dhcp4_fail (self, state);
 		break;
 	default:
 		break;
@@ -7434,10 +7487,7 @@ get_dhcp_timeout (NMDevice *self, int addr_family)
 
 	connection = nm_device_get_applied_connection (self);
 
-	if (addr_family == AF_INET)
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-	else
-		s_ip = nm_connection_get_setting_ip6_config (connection);
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 
 	timeout = nm_setting_ip_config_get_dhcp_timeout (s_ip);
 	if (timeout)
@@ -7445,8 +7495,8 @@ get_dhcp_timeout (NMDevice *self, int addr_family)
 
 	timeout = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
 	                                                       addr_family == AF_INET
-	                                                         ? "ipv4.dhcp-timeout"
-	                                                         : "ipv6.dhcp-timeout",
+	                                                         ? NM_CON_DEFAULT ("ipv4.dhcp-timeout")
+	                                                         : NM_CON_DEFAULT ("ipv6.dhcp-timeout"),
 	                                                       self,
 	                                                       0, G_MAXINT32, 0);
 	if (timeout)
@@ -7481,7 +7531,8 @@ dhcp4_get_client_id (NMDevice *self,
 
 	if (!client_id) {
 		client_id_default = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-		                                                           "ipv4.dhcp-client-id", self);
+		                                                           NM_CON_DEFAULT ("ipv4.dhcp-client-id"),
+		                                                           self);
 		if (client_id_default && client_id_default[0]) {
 			/* a non-empty client-id is always valid, see nm_dhcp_utils_client_id_string_to_bytes().  */
 			client_id = client_id_default;
@@ -7490,7 +7541,7 @@ dhcp4_get_client_id (NMDevice *self,
 
 	if (!client_id) {
 		_LOGD (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4,
-		       "ipv4.dhcp-client-id: no explicity client-id configured");
+		       "ipv4.dhcp-client-id: no explicit client-id configured");
 		return NULL;
 	}
 
@@ -7723,7 +7774,7 @@ shared4_new_config (NMDevice *self, NMConnection *connection)
 		is_generated = TRUE;
 	}
 
-	config = _ip4_config_new (self);
+	config = nm_device_ip4_config_new (self);
 	nm_ip4_config_add_address (config, &address);
 	if (is_generated) {
 		/* Remove the address lock when the object gets disposed */
@@ -7737,37 +7788,25 @@ shared4_new_config (NMDevice *self, NMConnection *connection)
 /*****************************************************************************/
 
 static gboolean
-connection_ip4_method_requires_carrier (NMConnection *connection,
-                                        gboolean *out_ip4_enabled)
-{
-	const char *method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	static const char *ip4_carrier_methods[] = {
-		NM_SETTING_IP4_CONFIG_METHOD_AUTO,
-		NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL,
-		NULL
-	};
+connection_ip_method_requires_carrier (NMConnection *connection,
+                                       int addr_family,
+                                       gboolean *out_ip_enabled)
+{
+	const char *method;
 
-	if (out_ip4_enabled)
-		*out_ip4_enabled = !!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
-	return g_strv_contains (ip4_carrier_methods, method);
-}
+	method = nm_utils_get_ip_config_method (connection, addr_family);
 
-static gboolean
-connection_ip6_method_requires_carrier (NMConnection *connection,
-                                        gboolean *out_ip6_enabled)
-{
-	const char *method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	static const char *ip6_carrier_methods[] = {
-		NM_SETTING_IP6_CONFIG_METHOD_AUTO,
-		NM_SETTING_IP6_CONFIG_METHOD_DHCP,
-		NM_SETTING_IP6_CONFIG_METHOD_SHARED,
-		NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
-		NULL
-	};
+	if (addr_family == AF_INET) {
+		NM_SET_OUT (out_ip_enabled, !nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED));
+		return NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+		                             NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL);
+	}
 
-	if (out_ip6_enabled)
-		*out_ip6_enabled = !!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
-	return g_strv_contains (ip6_carrier_methods, method);
+	NM_SET_OUT (out_ip_enabled, !nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE));
+	return NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+	                             NM_SETTING_IP6_CONFIG_METHOD_DHCP,
+	                             NM_SETTING_IP6_CONFIG_METHOD_SHARED,
+	                             NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL);
 }
 
 static gboolean
@@ -7784,7 +7823,7 @@ connection_requires_carrier (NMConnection *connection)
 	if (nm_setting_connection_get_master (s_con))
 		return FALSE;
 
-	ip4_carrier_wanted = connection_ip4_method_requires_carrier (connection, &ip4_used);
+	ip4_carrier_wanted = connection_ip_method_requires_carrier (connection, AF_INET, &ip4_used);
 	if (ip4_carrier_wanted) {
 		/* If IPv4 wants a carrier and cannot fail, the whole connection
 		 * requires a carrier regardless of the IPv6 method.
@@ -7794,7 +7833,7 @@ connection_requires_carrier (NMConnection *connection)
 			return TRUE;
 	}
 
-	ip6_carrier_wanted = connection_ip6_method_requires_carrier (connection, &ip6_used);
+	ip6_carrier_wanted = connection_ip_method_requires_carrier (connection, AF_INET6, &ip6_used);
 	if (ip6_carrier_wanted) {
 		/* If IPv6 wants a carrier and cannot fail, the whole connection
 		 * requires a carrier regardless of the IPv4 method.
@@ -7835,107 +7874,6 @@ have_any_ready_slaves (NMDevice *self)
 	return FALSE;
 }
 
-static gboolean
-ip4_requires_slaves (NMConnection *connection)
-{
-	const char *method;
-
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	return strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0;
-}
-
-static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *self,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
-	const char *method;
-
-	connection = nm_device_get_applied_connection (self);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	if (   connection_ip4_method_requires_carrier (connection, NULL)
-	    && nm_device_is_master (self)
-	    && !priv->carrier) {
-		_LOGI (LOGD_IP4 | LOGD_DEVICE,
-		       "IPv4 config waiting until carrier is on");
-		return NM_ACT_STAGE_RETURN_IP_WAIT;
-	}
-
-	if (nm_device_is_master (self) && ip4_requires_slaves (connection)) {
-		/* If the master has no ready slaves, and depends on slaves for
-		 * a successful IPv4 attempt, then postpone IPv4 addressing.
-		 */
-		if (!have_any_ready_slaves (self)) {
-			_LOGI (LOGD_DEVICE | LOGD_IP4,
-			       "IPv4 config waiting until slaves are ready");
-			return NM_ACT_STAGE_RETURN_IP_WAIT;
-		}
-	}
-
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-
-	if (NM_IN_STRSET (method,
-	                  NM_SETTING_IP4_CONFIG_METHOD_AUTO,
-	                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
-		NMSettingIPConfig *s_ip4;
-		NMIP4Config **configs, *config;
-		guint num_addresses;
-
-		s_ip4 = nm_connection_get_setting_ip4_config (connection);
-		g_return_val_if_fail (s_ip4, NM_ACT_STAGE_RETURN_FAILURE);
-		num_addresses = nm_setting_ip_config_get_num_addresses (s_ip4);
-
-		if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
-			ret = dhcp4_start (self);
-			if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
-				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
-				return ret;
-			}
-		} else {
-			g_return_val_if_fail (num_addresses != 0, NM_ACT_STAGE_RETURN_FAILURE);
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-		}
-
-		if (num_addresses) {
-			config = _ip4_config_new (self);
-			nm_ip4_config_merge_setting (config,
-			                             nm_connection_get_setting_ip4_config (connection),
-			                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
-			                             NM_SETTING_CONNECTION_LLMNR_DEFAULT,
-			                             nm_device_get_route_table (self, AF_INET, TRUE),
-			                             nm_device_get_route_metric (self, AF_INET));
-			configs = g_new0 (NMIP4Config *, 2);
-			configs[0] = config;
-			ipv4_dad_start (self, configs, ipv4_manual_method_apply);
-		}
-	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
-		ret = ipv4ll_start (self);
-		if (ret == NM_ACT_STAGE_RETURN_FAILURE)
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
-	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
-		if (out_config) {
-			*out_config = shared4_new_config (self, connection);
-			if (*out_config) {
-				priv->dnsmasq_manager = nm_dnsmasq_manager_new (nm_device_get_ip_iface (self));
-				ret = NM_ACT_STAGE_RETURN_SUCCESS;
-			} else {
-				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-				ret = NM_ACT_STAGE_RETURN_FAILURE;
-			}
-		} else
-			g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
-	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
-		ret = NM_ACT_STAGE_RETURN_SUCCESS;
-	else
-		_LOGW (LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", method);
-
-	return ret;
-}
-
 /*****************************************************************************/
 /* DHCPv6 stuff */
 
@@ -7944,6 +7882,7 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	priv->dhcp6.was_active = FALSE;
 	priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
 	applied_config_clear (&priv->dhcp6.ip6_config);
 	g_clear_pointer (&priv->dhcp6.event_id, g_free);
@@ -8017,12 +7956,14 @@ dhcp6_grace_period_expired (gpointer user_data)
 }
 
 static void
-dhcp6_fail (NMDevice *self, gboolean timeout)
+dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean is_dhcp_managed;
 
-	_LOGD (LOGD_DHCP6, "DHCPv6 failed%s", timeout ? " (timeout)" : "");
+	_LOGD (LOGD_DHCP6, "DHCPv6 failed (ip_state %s, was_active %d)",
+	       _ip_state_to_string (priv->ip_state_6),
+	       priv->dhcp6.was_active);
 
 	is_dhcp_managed = (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED);
 
@@ -8030,18 +7971,21 @@ dhcp6_fail (NMDevice *self, gboolean timeout)
 		/* Keep client running if there are static addresses configured
 		 * on the interface.
 		 */
-		if (   priv->ip6_state == IP_DONE
+		if (   priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE
 		    && 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.
+		/* Fail the method when one of the following is true:
+		 * 1) the DHCP client terminated: it does not make sense to start a grace
+		 *    period without a client running;
+		 * 2) we failed to get an initial lease AND the client was
+		 *    not active before.
 		 */
-		if (   !priv->dhcp6.was_active
-		    && (timeout || priv->ip6_state == IP_CONF)) {
+		if (   dhcp_state == NM_DHCP_STATE_TERMINATED
+		    || (!priv->dhcp6.was_active && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)) {
 			dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-			nm_device_activate_schedule_ip6_config_timeout (self);
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 			return;
 		}
 
@@ -8061,8 +8005,8 @@ dhcp6_fail (NMDevice *self, gboolean timeout)
 	} 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);
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)
+			nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 	}
 	return;
 
@@ -8076,21 +8020,6 @@ clear_config:
 }
 
 static void
-dhcp6_timeout (NMDevice *self, NMDhcpClient *client)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED)
-		dhcp6_fail (self, TRUE);
-	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);
-	}
-}
-
-static void
 dhcp6_state_changed (NMDhcpClient *client,
                      NMDhcpState state,
                      NMIP6Config *ip6_config,
@@ -8136,28 +8065,35 @@ dhcp6_state_changed (NMDhcpClient *client,
 		/* After long time we have been able to renew the lease:
 		 * update the ip state
 		 */
-		if (priv->ip6_state == IP_FAIL)
-			_set_ip_state (self, AF_INET6, IP_CONF);
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
 
-		if (priv->ip6_state == IP_CONF) {
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
 			if (!applied_config_get_current (&priv->dhcp6.ip6_config)) {
 				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_DHCP_FAILED);
 				break;
 			}
-			nm_device_activate_schedule_ip6_config_result (self);
-		} else if (priv->ip6_state == IP_DONE)
+			nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
+		} else if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE)
 			if (!dhcp6_lease_change (self))
-				dhcp6_fail (self, FALSE);
+				dhcp6_fail (self, state);
 		break;
 	case NM_DHCP_STATE_TIMEOUT:
-		dhcp6_timeout (self, client);
+		if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED)
+			dhcp6_fail (self, state);
+		else {
+			/* not a hard failure; just live with the RA info */
+			dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
+			if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)
+				nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
+		}
 		break;
 	case NM_DHCP_STATE_EXPIRE:
 		/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
-		if (priv->ip6_state != IP_CONF)
-			dhcp6_fail (self, FALSE);
+		if (priv->ip_state_6 != NM_DEVICE_IP_STATE_CONF)
+			dhcp6_fail (self, state);
 		break;
-	case NM_DHCP_STATE_DONE:
+	case NM_DHCP_STATE_TERMINATED:
 		/* In IPv6 info-only mode, the client doesn't handle leases so it
 		 * may exit right after getting a response from the server.  That's
 		 * normal.  In that case we just ignore the exit.
@@ -8165,8 +8101,9 @@ dhcp6_state_changed (NMDhcpClient *client,
 		if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF)
 			break;
 		/* fall through */
+	case NM_DHCP_STATE_DONE:
 	case NM_DHCP_STATE_FAIL:
-		dhcp6_fail (self, FALSE);
+		dhcp6_fail (self, state);
 		break;
 	default:
 		break;
@@ -8312,7 +8249,8 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 
 	if (!duid) {
 		duid_default = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-		                                                      "ipv6.dhcp-duid", self);
+		                                                      NM_CON_DEFAULT ("ipv6.dhcp-duid"),
+		                                                      self);
 		duid = duid_default;
 		if (!duid)
 			duid = "lease";
@@ -8359,7 +8297,7 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 		/* preferably, we would salt the checksum differently for each @duid type. We missed
 		 * to do that initially, so most types use the DEFAULT_SALT.
 		 *
-		 * Implemenations that are added later, should use a distinct salt instead,
+		 * Implementations that are added later, should use a distinct salt instead,
 		 * like "stable-ll"/"stable-llt" with ARPHRD_INFINIBAND below. */
 		const guint32 DEFAULT_SALT = 670531087u;
 		nm_auto_free_checksum GChecksum *sum = NULL;
@@ -8664,6 +8602,7 @@ nm_device_use_ip6_subnet (NMDevice *self, const NMPlatformIP6Address *subnet)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMPlatformIP6Address address = *subnet;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	if (!applied_config_get_current (&priv->ac_ip6_config))
 		applied_config_init_new (&priv->ac_ip6_config, self, AF_INET6);
@@ -8673,7 +8612,7 @@ nm_device_use_ip6_subnet (NMDevice *self, const NMPlatformIP6Address *subnet)
 	applied_config_add_address (&priv->ac_ip6_config, NM_PLATFORM_IP_ADDRESS_CAST (&address));
 
 	_LOGD (LOGD_IP6, "ipv6-pd: using %s address (preferred for %u seconds)",
-	       nm_utils_inet6_ntop (&address.address, NULL),
+	       nm_utils_inet6_ntop (&address.address, sbuf),
 	       subnet->preferred);
 
 	/* This also updates the ndisc if there are actual changes. */
@@ -8727,7 +8666,7 @@ linklocal6_failed (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	nm_clear_g_source (&priv->linklocal6_timeout_id);
-	nm_device_activate_schedule_ip6_config_timeout (self);
+	nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 }
 
 static gboolean
@@ -8765,20 +8704,20 @@ linklocal6_check_complete (NMDevice *self)
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	method = nm_device_get_effective_ip_config_method (self, AF_INET6);
 
 	_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 (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+	                          NM_SETTING_IP6_CONFIG_METHOD_SHARED))
 		addrconf6_start_with_link_ready (self);
-	else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
+	else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
 		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);
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 		}
-	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0)
-		nm_device_activate_schedule_ip6_config_result (self);
+	} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
+		nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 	else
 		g_return_if_fail (FALSE);
 }
@@ -8792,6 +8731,7 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 	NMSettingIP6Config *s_ip6 = NULL;
 	GError *error = NULL;
 	const char *addr_type;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	if (!priv->ipv6ll_handle)
 		return;
@@ -8852,7 +8792,8 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 		addr_type = "EUI-64";
 	}
 
-	_LOGD (LOGD_IP6, "linklocal6: generated %s IPv6LL address %s", addr_type, nm_utils_inet6_ntop (&lladdr, NULL));
+	_LOGD (LOGD_IP6, "linklocal6: generated %s IPv6LL address %s",
+	       addr_type, nm_utils_inet6_ntop (&lladdr, sbuf));
 	priv->ipv6ll_has = TRUE;
 	priv->ipv6ll_addr = lladdr;
 	ip_config_merge_and_apply (self, AF_INET6, TRUE);
@@ -8862,8 +8803,6 @@ static gboolean
 linklocal6_start (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
-	const char *method;
 
 	nm_clear_g_source (&priv->linklocal6_timeout_id);
 
@@ -8873,11 +8812,8 @@ linklocal6_start (NMDevice *self)
 	                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL))
 		return TRUE;
 
-	connection = nm_device_get_applied_connection (self);
-	g_assert (connection);
-
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	_LOGD (LOGD_DEVICE, "linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses configured. Wait.", method);
+	_LOGD (LOGD_DEVICE, "linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses configured. Wait.",
+	       nm_device_get_effective_ip_config_method (self, AF_INET6));
 
 	check_and_add_ipv6ll_addr (self);
 
@@ -8925,19 +8861,23 @@ nm_device_get_configured_mtu_from_connection (NMDevice *self,
 	if (setting_type == NM_TYPE_SETTING_WIRED) {
 		if (setting)
 			mtu = nm_setting_wired_get_mtu (NM_SETTING_WIRED (setting));
-		global_property_name = "ethernet.mtu";
+		global_property_name = NM_CON_DEFAULT ("ethernet.mtu");
 	} else if (setting_type == NM_TYPE_SETTING_WIRELESS) {
 		if (setting)
 			mtu = nm_setting_wireless_get_mtu (NM_SETTING_WIRELESS (setting));
-		global_property_name = "wifi.mtu";
+		global_property_name = NM_CON_DEFAULT ("wifi.mtu");
 	} else if (setting_type == NM_TYPE_SETTING_INFINIBAND) {
 		if (setting)
 			mtu = nm_setting_infiniband_get_mtu (NM_SETTING_INFINIBAND (setting));
-		global_property_name = "infiniband.mtu";
+		global_property_name = NM_CON_DEFAULT ("infiniband.mtu");
 	} else if (setting_type == NM_TYPE_SETTING_IP_TUNNEL) {
 		if (setting)
 			mtu = nm_setting_ip_tunnel_get_mtu (NM_SETTING_IP_TUNNEL (setting));
-		global_property_name = "ip-tunnel.mtu";
+		global_property_name = NM_CON_DEFAULT ("ip-tunnel.mtu");
+	} else if (setting_type == NM_TYPE_SETTING_WIREGUARD) {
+		if (setting)
+			mtu = nm_setting_wireguard_get_mtu (NM_SETTING_WIREGUARD (setting));
+		global_property_name = NM_CON_DEFAULT ("wireguard.mtu");
 	} else
 		g_return_val_if_reached (0);
 
@@ -9017,7 +8957,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 	{
 		guint32 mtu = 0;
 
-		/* preferably, get the MTU from explict user-configuration.
+		/* preferably, get the MTU from explicit user-configuration.
 		 * Only if that fails, look at the current @config (which contains
 		 * MTUs from DHCP/PPP) or maybe fallback to a device-specific MTU. */
 
@@ -9052,7 +8992,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 	if (mtu_desired && mtu_desired < 1280) {
 		NMSettingIPConfig *s_ip6;
 
-		s_ip6 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
+		s_ip6 = nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
 		if (   s_ip6
 		    && !NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip6),
 		                      NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
@@ -9107,7 +9047,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 #define _IP6_MTU_SYS() \
 	({ \
 		if (!ip6_mtu_sysctl.initialized) { \
-			ip6_mtu_sysctl.value = nm_device_ipv6_sysctl_get_uint32 (self, "mtu", 0); \
+			ip6_mtu_sysctl.value = nm_device_sysctl_ip_conf_get_int_checked (self, AF_INET6, "mtu", 10, 0, G_MAXUINT32, 0); \
 			ip6_mtu_sysctl.initialized = TRUE; \
 		} \
 		ip6_mtu_sysctl.value; \
@@ -9124,7 +9064,10 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 		}
 
 		if (mtu_desired && mtu_desired != mtu_plat) {
-			if (nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, mtu_desired) == NM_PLATFORM_ERROR_CANT_SET_MTU) {
+			int r;
+
+			r = nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, mtu_desired);
+			if (r == -NME_PL_CANT_SET_MTU) {
 				anticipated_failure = TRUE;
 				success = FALSE;
 				_LOGW (LOGD_DEVICE, "mtu: failure to set MTU. %s",
@@ -9138,8 +9081,8 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 		}
 
 		if (ip6_mtu && ip6_mtu != _IP6_MTU_SYS ()) {
-			if (!nm_device_ipv6_sysctl_set (self, "mtu",
-			                                nm_sprintf_buf (sbuf, "%u", (unsigned) ip6_mtu))) {
+			if (!nm_device_sysctl_ip_conf_set (self, AF_INET6, "mtu",
+			                                   nm_sprintf_buf (sbuf, "%u", (unsigned) ip6_mtu))) {
 				int errsv = errno;
 
 				_NMLOG (anticipated_failure && errsv == EINVAL ? LOGL_DEBUG : LOGL_WARN,
@@ -9281,7 +9224,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 	}
 
 	if (changed & NM_NDISC_CONFIG_HOP_LIMIT)
-		nm_platform_sysctl_set_ip6_hop_limit_safe (nm_device_get_platform (self), nm_device_get_ip_iface (self), rdata->hop_limit);
+		nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (nm_device_get_platform (self), nm_device_get_ip_iface (self), rdata->hop_limit);
 
 	if (changed & NM_NDISC_CONFIG_MTU) {
 		if (priv->ip6_mtu != rdata->mtu) {
@@ -9290,7 +9233,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		}
 	}
 
-	nm_device_activate_schedule_ip6_config_result (self);
+	nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 }
 
 static void
@@ -9304,7 +9247,7 @@ ndisc_ra_timeout (NMNDisc *ndisc, NMDevice *self)
 	 */
 
 	_LOGD (LOGD_IP6, "timed out waiting for IPv6 router advertisement");
-	if (priv->ip6_state == IP_CONF) {
+	if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
 		/* If RA is our only source of addressing information and we don't
 		 * ever receive one, then time out IPv6.  But if there is other
 		 * IPv6 configuration, like manual IPv6 addresses or external IPv6
@@ -9317,9 +9260,9 @@ ndisc_ra_timeout (NMNDisc *ndisc, NMDevice *self)
 		    && 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);
+			nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 		else
-			nm_device_activate_schedule_ip6_config_timeout (self);
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 	}
 }
 
@@ -9337,7 +9280,7 @@ addrconf6_start_with_link_ready (NMDevice *self)
 	} else {
 		/* Don't abort the addrconf at this point -- if ndisc needs the iid
 		 * it will notice this itself. */
-		_LOGI (LOGD_IP6, "addrconf6: no interface identifier; IPv6 adddress creation may fail");
+		_LOGI (LOGD_IP6, "addrconf6: no interface identifier; IPv6 address creation may fail");
 	}
 
 	/* Apply any manual configuration before starting RA */
@@ -9348,15 +9291,15 @@ addrconf6_start_with_link_ready (NMDevice *self)
 	switch (nm_ndisc_get_node_type (priv->ndisc)) {
 	case NM_NDISC_NODE_TYPE_HOST:
 		/* Accepting prefixes from discovered routers. */
-		nm_device_ipv6_sysctl_set (self, "accept_ra", "1");
-		nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0");
-		nm_device_ipv6_sysctl_set (self, "accept_ra_pinfo", "0");
-		nm_device_ipv6_sysctl_set (self, "accept_ra_rtr_pref", "0");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra", "1");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_defrtr", "0");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_pinfo", "0");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_rtr_pref", "0");
 		break;
 	case NM_NDISC_NODE_TYPE_ROUTER:
 		/* We're the router. */
-		nm_device_ipv6_sysctl_set (self, "forwarding", "1");
-		nm_device_activate_schedule_ip6_config_result (self);
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "forwarding", "1");
+		nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 		priv->needs_ip6_subnet = TRUE;
 		g_signal_emit (self, signals[IP6_SUBNET_NEEDED], 0);
 		break;
@@ -9375,22 +9318,17 @@ addrconf6_start_with_link_ready (NMDevice *self)
 
 	ndisc_set_router_config (priv->ndisc, self);
 	nm_ndisc_start (priv->ndisc);
+	priv->ndisc_started = TRUE;
 	return;
 }
 
 static NMNDiscNodeType
 ndisc_node_type (NMDevice *self)
 {
-	NMConnection *connection;
-
-	connection = nm_device_get_applied_connection (self);
-	g_assert (connection);
-
-	if (strcmp (nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG),
-	            NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0)
+	if (nm_streq (nm_device_get_effective_ip_config_method (self, AF_INET6),
+	              NM_SETTING_IP4_CONFIG_METHOD_SHARED))
 		return NM_NDISC_NODE_TYPE_ROUTER;
-	else
-		return NM_NDISC_NODE_TYPE_HOST;
+	return NM_NDISC_NODE_TYPE_HOST;
 }
 
 static gboolean
@@ -9459,6 +9397,7 @@ addrconf6_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	priv->ndisc_started = FALSE;
 	nm_clear_g_signal_handler (priv->ndisc, &priv->ndisc_changed_id);
 	nm_clear_g_signal_handler (priv->ndisc, &priv->ndisc_timeout_id);
 
@@ -9472,34 +9411,36 @@ addrconf6_cleanup (NMDevice *self)
 
 /*****************************************************************************/
 
-static const char *ip6_properties_to_save[] = {
-	"accept_ra",
-	"accept_ra_defrtr",
-	"accept_ra_pinfo",
-	"accept_ra_rtr_pref",
-	"forwarding",
-	"disable_ipv6",
-	"hop_limit",
-	"use_tempaddr",
-};
-
 static void
 save_ip6_properties (NMDevice *self)
 {
+	static const char *const ip6_properties_to_save[] = {
+		"accept_ra",
+		"accept_ra_defrtr",
+		"accept_ra_pinfo",
+		"accept_ra_rtr_pref",
+		"forwarding",
+		"disable_ipv6",
+		"hop_limit",
+		"use_tempaddr",
+	};
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *ifname = nm_device_get_ip_iface (self);
+	NMPlatform *platform = nm_device_get_platform (self);
+	const char *ifname;
 	char *value;
 	int i;
 
 	g_hash_table_remove_all (priv->ip6_saved_properties);
 
-	if (!nm_device_get_ip_ifindex (self))
+	ifname = nm_device_get_ip_iface_from_platform (self);
+	if (!ifname)
 		return;
 
 	for (i = 0; i < G_N_ELEMENTS (ip6_properties_to_save); i++) {
-		char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-		value = nm_platform_sysctl_get (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, ifname, ip6_properties_to_save[i])));
+		value = nm_platform_sysctl_ip_conf_get (platform,
+		                                        AF_INET6,
+		                                        ifname,
+		                                        ip6_properties_to_save[i]);
 		if (value) {
 			g_hash_table_insert (priv->ip6_saved_properties,
 			                     (char *) ip6_properties_to_save[i],
@@ -9521,24 +9462,23 @@ restore_ip6_properties (NMDevice *self)
 		if (   priv->ipv6ll_handle
 		    && nm_streq (key, "disable_ipv6"))
 			continue;
-		nm_device_ipv6_sysctl_set (self, key, value);
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, key, value);
 	}
 }
 
-static inline void
+static 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)->ipv6ll_handle)
-		nm_device_ipv6_sysctl_set (self, "disable_ipv6", value);
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", value);
 }
 
-static inline void
+static void
 set_nm_ipv6ll (NMDevice *self, gboolean enable)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int ifindex = nm_device_get_ip_ifindex (self);
-	char *value;
 
 	if (!nm_platform_check_kernel_support (nm_device_get_platform (self),
 	                                       NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
@@ -9546,32 +9486,34 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 
 	priv->ipv6ll_handle = enable;
 	if (ifindex > 0) {
-		NMPlatformError plerr;
 		const char *detail = enable ? "enable" : "disable";
+		int r;
 
 		_LOGD (LOGD_IP6, "will %s userland IPv6LL", detail);
-		plerr = nm_platform_link_set_user_ipv6ll_enabled (nm_device_get_platform (self), ifindex, enable);
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
-			_NMLOG ((   plerr == NM_PLATFORM_ERROR_NOT_FOUND
-			         || plerr == NM_PLATFORM_ERROR_OPNOTSUPP) ? LOGL_DEBUG : LOGL_WARN,
+		r = nm_platform_link_set_user_ipv6ll_enabled (nm_device_get_platform (self), ifindex, enable);
+		if (r < 0) {
+			_NMLOG (  NM_IN_SET (r, -NME_PL_NOT_FOUND,
+			                        -NME_PL_OPNOTSUPP)
+			        ? LOGL_DEBUG
+			        : LOGL_WARN,
 			        LOGD_IP6,
 			        "failed to %s userspace IPv6LL address handling (%s)",
 			        detail,
-			        nm_platform_error_to_string_a (plerr));
+			        nm_strerror (r));
 		}
 
 		if (enable) {
-			char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+			gs_free char *value = NULL;
 
 			/* Bounce IPv6 to ensure the kernel stops IPv6LL address generation */
-			value = nm_platform_sysctl_get (nm_device_get_platform (self),
-			                                NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), "disable_ipv6")));
-			if (g_strcmp0 (value, "0") == 0)
-				nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
-			g_free (value);
+			value = nm_device_sysctl_ip_conf_get (self,
+			                                      AF_INET6,
+			                                      "disable_ipv6");
+			if (nm_streq0 (value, "0"))
+				nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1");
 
 			/* Ensure IPv6 is enabled */
-			nm_device_ipv6_sysctl_set (self, "disable_ipv6", "0");
+			nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "0");
 		}
 
 	}
@@ -9616,7 +9558,7 @@ _ip6_privacy_get (NMDevice *self)
 
 	/* 2.) use the default value from the configuration. */
 	ip6_privacy = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                           "ipv6.ip6-privacy",
+	                                                           NM_CON_DEFAULT ("ipv6.ip6-privacy"),
 	                                                           self,
 	                                                           NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN,
 	                                                           NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
@@ -9632,150 +9574,228 @@ _ip6_privacy_get (NMDevice *self)
 	 * Instead of reading static config files in /etc, just read the current sysctl value.
 	 * This works as NM only writes to "/proc/sys/net/ipv6/conf/IFNAME/use_tempaddr", but leaves
 	 * the "default" entry untouched. */
-	ip6_privacy = nm_platform_sysctl_get_int32 (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv6/conf/default/use_tempaddr"), NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+	ip6_privacy = nm_platform_sysctl_get_int32 (nm_device_get_platform (self),
+	                                            NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv6/conf/default/use_tempaddr"),
+	                                            NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 	return _ip6_privacy_clamp (ip6_privacy);
 }
 
 /*****************************************************************************/
 
 static gboolean
-ip6_requires_slaves (NMConnection *connection)
+ip_requires_slaves (NMDevice *self, int addr_family)
 {
 	const char *method;
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	method = nm_device_get_effective_ip_config_method (self, addr_family);
+
+	if (addr_family == AF_INET)
+		return nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
 	/* SLAAC, DHCP, and Link-Local depend on connectivity (and thus slaves)
 	 * to complete addressing.  SLAAC and DHCP need a peer to provide a prefix.
 	 */
-	return    strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
-	       || strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0;
+	return NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+	                             NM_SETTING_IP6_CONFIG_METHOD_DHCP);
 }
 
 static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *self,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *self,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMConnection *connection;
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	const char *method;
-	NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
-	const char *ip6_privacy_str = "0";
+
+	nm_assert_addr_family (addr_family);
 
 	connection = nm_device_get_applied_connection (self);
+
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
-	if (   connection_ip6_method_requires_carrier (connection, NULL)
+	if (   connection_ip_method_requires_carrier (connection, addr_family, NULL)
 	    && nm_device_is_master (self)
 	    && !priv->carrier) {
-		_LOGI (LOGD_IP6 | LOGD_DEVICE,
-		       "IPv6 config waiting until carrier is on");
+		_LOGI (LOGD_IP | LOGD_DEVICE,
+		       "IPv%c config waiting until carrier is on",
+		       nm_utils_addr_family_to_char (addr_family));
 		return NM_ACT_STAGE_RETURN_IP_WAIT;
 	}
 
-	if (nm_device_is_master (self) && ip6_requires_slaves (connection)) {
+	if (   nm_device_is_master (self)
+	    && ip_requires_slaves (self, addr_family)) {
 		/* If the master has no ready slaves, and depends on slaves for
-		 * a successful IPv6 attempt, then postpone IPv6 addressing.
+		 * a successful IP configuration attempt, then postpone IP addressing.
 		 */
 		if (!have_any_ready_slaves (self)) {
-			_LOGI (LOGD_DEVICE | LOGD_IP6,
-			       "IPv6 config waiting until slaves are ready");
+			_LOGI (LOGD_DEVICE | LOGD_IP,
+			       "IPv%c config waiting until slaves are ready",
+			       nm_utils_addr_family_to_char (addr_family));
 			return NM_ACT_STAGE_RETURN_IP_WAIT;
 		}
 	}
 
-	priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	if (!IS_IPv4)
+		priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
 
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) {
-		if (   !priv->master
-		    && !nm_device_sys_iface_state_is_external (self)) {
-			gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
+	method = nm_device_get_effective_ip_config_method (self, addr_family);
 
-			/* 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 (ipv6ll_handle_old)
-				nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
-			restore_ip6_properties (self);
+	_LOGD (LOGD_IP | LOGD_DEVICE, "IPv%c config method is %s",
+	       nm_utils_addr_family_to_char (addr_family), method);
+
+	if (IS_IPv4) {
+		if (NM_IN_STRSET (method,
+		                  NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+		                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
+			NMSettingIPConfig *s_ip4;
+			NMIP4Config **configs, *config;
+			guint num_addresses;
+
+			s_ip4 = nm_connection_get_setting_ip4_config (connection);
+			g_return_val_if_fail (s_ip4, NM_ACT_STAGE_RETURN_FAILURE);
+			num_addresses = nm_setting_ip_config_get_num_addresses (s_ip4);
+
+			if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+				ret = dhcp4_start (self);
+				if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+					NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
+					return ret;
+				}
+			} else {
+				g_return_val_if_fail (num_addresses != 0, NM_ACT_STAGE_RETURN_FAILURE);
+				ret = NM_ACT_STAGE_RETURN_POSTPONE;
+			}
+
+			if (num_addresses) {
+				config = nm_device_ip4_config_new (self);
+				nm_ip4_config_merge_setting (config,
+				                             nm_connection_get_setting_ip4_config (connection),
+				                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
+				                             NM_SETTING_CONNECTION_LLMNR_DEFAULT,
+				                             nm_device_get_route_table (self, AF_INET, TRUE),
+				                             nm_device_get_route_metric (self, AF_INET));
+				configs = g_new0 (NMIP4Config *, 2);
+				configs[0] = config;
+				ipv4_dad_start (self, configs, ipv4_manual_method_apply);
+			}
+		} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
+			ret = ipv4ll_start (self);
+			if (ret == NM_ACT_STAGE_RETURN_FAILURE)
+				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
+		} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
+			if (out_config) {
+				*out_config = shared4_new_config (self, connection);
+				if (*out_config) {
+					priv->dnsmasq_manager = nm_dnsmasq_manager_new (nm_device_get_ip_iface (self));
+					ret = NM_ACT_STAGE_RETURN_SUCCESS;
+				} else {
+					NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+					ret = NM_ACT_STAGE_RETURN_FAILURE;
+				}
+			} else
+				g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
+		} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+			ret = NM_ACT_STAGE_RETURN_SUCCESS;
+		else
+			_LOGW (LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", method);
+
+		return ret;
+	} else {
+		NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
+		const char *ip6_privacy_str = "0";
+
+		if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+			if (   !priv->master
+			    && !nm_device_sys_iface_state_is_external (self)) {
+				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 (ipv6ll_handle_old)
+					nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1");
+				restore_ip6_properties (self);
+			}
+			return NM_ACT_STAGE_RETURN_IP_DONE;
 		}
-		return NM_ACT_STAGE_RETURN_IP_DONE;
-	}
 
-	/* Ensure the MTU makes sense. If it was below 1280 the kernel would not
-	 * 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->ip_config_4);
+		/* Ensure the MTU makes sense. If it was below 1280 the kernel would not
+		 * 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->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
-	 * will already have IPv6 set up.
-	 */
-	if (!nm_device_sys_iface_state_is_external_or_assume (self))
-		set_nm_ipv6ll (self, TRUE);
+		/* Any method past this point requires an IPv6LL address. Use NM-controlled
+		 * IPv6LL if this is not an assumed connection, since assumed connections
+		 * will already have IPv6 set up.
+		 */
+		if (!nm_device_sys_iface_state_is_external_or_assume (self))
+			set_nm_ipv6ll (self, TRUE);
 
-	/* Re-enable IPv6 on the interface */
-	set_disable_ipv6 (self, "0");
+		/* Re-enable IPv6 on the interface */
+		set_disable_ipv6 (self, "0");
 
-	/* Synchronize external IPv6 configuration with kernel, since
-	 * linklocal6_start() uses the information there to determine if we can
-	 * proceed with the selected method (SLAAC, DHCP, link-local).
-	 */
-	nm_platform_process_events (nm_device_get_platform (self));
-	g_clear_object (&priv->ext_ip6_config_captured);
-	priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
-	                                                       nm_device_get_platform (self),
-	                                                       nm_device_get_ip_ifindex (self),
-	                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+		/* Synchronize external IPv6 configuration with kernel, since
+		 * linklocal6_start() uses the information there to determine if we can
+		 * proceed with the selected method (SLAAC, DHCP, link-local).
+		 */
+		nm_platform_process_events (nm_device_get_platform (self));
+		g_clear_object (&priv->ext_ip6_config_captured);
+		priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
+		                                                       nm_device_get_platform (self),
+		                                                       nm_device_get_ip_ifindex (self),
+		                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 
-	ip6_privacy = _ip6_privacy_get (self);
+		ip6_privacy = _ip6_privacy_get (self);
 
-	if (   strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
-	    || strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0) {
-		if (!addrconf6_start (self, ip6_privacy)) {
-			/* IPv6 might be disabled; allow IPv4 to proceed */
-			ret = NM_ACT_STAGE_RETURN_IP_FAIL;
-		} else
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) {
-		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)) {
-			/* IPv6 might be disabled; allow IPv4 to proceed */
-			ret = NM_ACT_STAGE_RETURN_IP_FAIL;
-		} else
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) == 0) {
-		ret = NM_ACT_STAGE_RETURN_SUCCESS;
-	} else
-		_LOGW (LOGD_IP6, "unhandled IPv6 config method '%s'; will fail", method);
-
-	if (   ret != NM_ACT_STAGE_RETURN_FAILURE
-	    && !nm_device_sys_iface_state_is_external_or_assume (self)) {
-		switch (ip6_privacy) {
-		case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN:
-		case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
-			ip6_privacy_str = "0";
-			break;
-		case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
-			ip6_privacy_str = "1";
-			break;
-		case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
-			ip6_privacy_str = "2";
-			break;
+		if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		                          NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
+			if (!addrconf6_start (self, ip6_privacy)) {
+				/* IPv6 might be disabled; allow IPv4 to proceed */
+				ret = NM_ACT_STAGE_RETURN_IP_FAIL;
+			} else
+				ret = NM_ACT_STAGE_RETURN_POSTPONE;
+		} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
+			ret = linklocal6_start (self)
+			      ? NM_ACT_STAGE_RETURN_SUCCESS
+			      : NM_ACT_STAGE_RETURN_POSTPONE;
+		} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
+			priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
+			if (!dhcp6_start (self, TRUE)) {
+				/* IPv6 might be disabled; allow IPv4 to proceed */
+				ret = NM_ACT_STAGE_RETURN_IP_FAIL;
+			} else
+				ret = NM_ACT_STAGE_RETURN_POSTPONE;
+		} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
+			ret = NM_ACT_STAGE_RETURN_SUCCESS;
+		else
+			_LOGW (LOGD_IP6, "unhandled IPv6 config method '%s'; will fail", method);
+
+		if (   ret != NM_ACT_STAGE_RETURN_FAILURE
+		    && !nm_device_sys_iface_state_is_external_or_assume (self)) {
+			switch (ip6_privacy) {
+			case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN:
+			case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
+				ip6_privacy_str = "0";
+				break;
+			case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
+				ip6_privacy_str = "1";
+				break;
+			case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
+				ip6_privacy_str = "2";
+				break;
+			}
+			nm_device_sysctl_ip_conf_set (self, AF_INET6, "use_tempaddr", ip6_privacy_str);
 		}
-		nm_device_ipv6_sysctl_set (self, "use_tempaddr", ip6_privacy_str);
-	}
 
-	return ret;
+		return ret;
+	}
 }
 
 /**
@@ -9790,35 +9810,32 @@ nm_device_activate_stage3_ip4_start (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-	NMIP4Config *ip4_config = NULL;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
 
-	g_assert (priv->ip4_state == IP_WAIT);
+	g_assert (priv->ip_state_4 == NM_DEVICE_IP_STATE_WAIT);
 
 	if (nm_device_sys_iface_state_is_external (self)) {
-		_set_ip_state (self, AF_INET, IP_DONE);
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 		return TRUE;
 	}
 
-	_set_ip_state (self, AF_INET, IP_CONF);
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip4_config_start (self, &ip4_config, &failure_reason);
-	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
-		if (!ip4_config)
-			ip4_config = _ip4_config_new (self);
-		nm_device_activate_schedule_ip4_config_result (self, ip4_config);
-		g_object_unref (ip4_config);
-	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
-		_set_ip_state (self, AF_INET, IP_DONE);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_CONF);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip_config_start (self, AF_INET, (gpointer *) &ip4_config, &failure_reason);
+	if (ret == NM_ACT_STAGE_RETURN_SUCCESS)
+		nm_device_activate_schedule_ip_config_result (self, AF_INET, NM_IP_CONFIG_CAST (ip4_config));
+	else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 	} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
 		return FALSE;
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_FAIL) {
 		/* Activation not wanted */
-		_set_ip_state (self, AF_INET, IP_FAIL);
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_FAIL);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_WAIT) {
 		/* Wait for something to try IP config again */
-		_set_ip_state (self, AF_INET, IP_WAIT);
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
 	} else
 		g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
 
@@ -9839,37 +9856,37 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 	NMIP6Config *ip6_config = NULL;
 
-	g_assert (priv->ip6_state == IP_WAIT);
+	g_assert (priv->ip_state_6 == NM_DEVICE_IP_STATE_WAIT);
 
 	if (nm_device_sys_iface_state_is_external (self)) {
-		_set_ip_state (self, AF_INET6, IP_DONE);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 		return TRUE;
 	}
 
-	_set_ip_state (self, AF_INET6, IP_CONF);
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip6_config_start (self, &ip6_config, &failure_reason);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip_config_start (self, AF_INET6, (gpointer *) &ip6_config, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
 		if (!ip6_config)
-			ip6_config = _ip6_config_new (self);
+			ip6_config = nm_device_ip6_config_new (self);
 		/* Here we get a static IPv6 config, like for Shared where it's
 		 * autogenerated or from modems where it comes from ModemManager.
 		 */
 		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);
+		nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
-		_set_ip_state (self, AF_INET6, IP_DONE);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 	} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
 		return FALSE;
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_FAIL) {
 		/* Activation not wanted */
-		_set_ip_state (self, AF_INET6, IP_FAIL);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_FAIL);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_WAIT) {
 		/* Wait for something to try IP config again */
-		_set_ip_state (self, AF_INET6, IP_WAIT);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
 	} else
 		g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
 
@@ -9885,8 +9902,10 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 static void
 activate_stage3_ip_config_start (NMDevice *self)
 {
-	_set_ip_state (self, AF_INET, IP_WAIT);
-	_set_ip_state (self, AF_INET6, IP_WAIT);
+	int ifindex;
+
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
 
 	_active_connection_set_state_flags (self,
 	                                    NM_ACTIVATION_STATE_FLAG_LAYER2_READY);
@@ -9894,7 +9913,8 @@ activate_stage3_ip_config_start (NMDevice *self)
 	nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
 	/* Device should be up before we can do anything with it */
-	if (!nm_platform_link_is_up (nm_device_get_platform (self), nm_device_get_ip_ifindex (self)))
+	if (   (ifindex = nm_device_get_ip_ifindex (self)) > 0
+	    && !nm_platform_link_is_up (nm_device_get_platform (self), ifindex))
 		_LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (self));
 
 	/* IPv4 */
@@ -9940,7 +9960,7 @@ fw_change_zone_cb (NMFirewallManager *firewall_manager,
 		break;
 	case FIREWALL_STATE_WAIT_IP_CONFIG:
 		priv->fw_state = FIREWALL_STATE_INITIALIZED;
-		if (priv->ip4_state == IP_DONE || priv->ip6_state == IP_DONE)
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE || priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE)
 			nm_device_start_ip_check (self);
 		break;
 	case FIREWALL_STATE_INITIALIZED:
@@ -10017,15 +10037,21 @@ nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self)
 }
 
 static NMActStageReturn
-act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *out_failure_reason)
+act_stage4_ip_config_timeout (NMDevice *self,
+                              int addr_family,
+                              NMDeviceStateReason *out_failure_reason)
 {
-	if (!get_ip_config_may_fail (self, AF_INET)) {
+	nm_assert_addr_family (addr_family);
+
+	if (!get_ip_config_may_fail (self, addr_family)) {
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
+
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
+
 /*
  * nm_device_activate_stage4_ip4_config_timeout
  *
@@ -10033,12 +10059,12 @@ act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *out_failure_
  *
  */
 static void
-activate_stage4_ip4_config_timeout (NMDevice *self)
+activate_stage4_ip_config_timeout_4 (NMDevice *self)
 {
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip4_config_timeout (self, &failure_reason);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip_config_timeout (self, AF_INET6, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
 		return;
 	else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
@@ -10047,54 +10073,35 @@ activate_stage4_ip4_config_timeout (NMDevice *self)
 	}
 	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 
-	_set_ip_state (self, AF_INET, IP_FAIL);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_FAIL);
 
 	check_ip_state (self, FALSE, TRUE);
 }
 
-/*
- * nm_device_activate_schedule_ip4_config_timeout
- *
- * Deal with a timeout of the IPv4 configuration
- *
- */
 void
-nm_device_activate_schedule_ip4_config_timeout (NMDevice *self)
+nm_device_activate_schedule_ip_config_timeout (NMDevice *self,
+                                               int addr_family)
 {
 	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	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);
-	g_return_if_fail (priv->act_request.obj);
 
-	activation_source_schedule (self, activate_stage4_ip4_config_timeout, AF_INET);
-}
-
-static NMActStageReturn
-act_stage4_ip6_config_timeout (NMDevice *self, NMDeviceStateReason *out_failure_reason)
-{
-	if (!get_ip_config_may_fail (self, AF_INET6)) {
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-		return NM_ACT_STAGE_RETURN_FAILURE;
-	}
+	g_return_if_fail (priv->act_request.obj);
 
-	return NM_ACT_STAGE_RETURN_SUCCESS;
+	activation_source_schedule (self, activate_stage4_ip_config_timeout_x[IS_IPv4], addr_family);
 }
 
-/*
- * activate_stage4_ip6_config_timeout
- *
- * Time out on retrieving the IPv6 config.
- *
- */
 static void
-activate_stage4_ip6_config_timeout (NMDevice *self)
+activate_stage4_ip_config_timeout_6 (NMDevice *self)
 {
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip6_config_timeout (self, &failure_reason);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip_config_timeout (self, AF_INET6, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
 		return;
 	if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
@@ -10103,30 +10110,11 @@ activate_stage4_ip6_config_timeout (NMDevice *self)
 	}
 	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 
-	_set_ip_state (self, AF_INET6, IP_FAIL);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_FAIL);
 
 	check_ip_state (self, FALSE, TRUE);
 }
 
-/*
- * nm_device_activate_schedule_ip6_config_timeout
- *
- * Deal with a timeout of the IPv6 configuration
- *
- */
-void
-nm_device_activate_schedule_ip6_config_timeout (NMDevice *self)
-{
-	NMDevicePrivate *priv;
-
-	g_return_if_fail (NM_IS_DEVICE (self));
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request.obj);
-
-	activation_source_schedule (self, activate_stage4_ip6_config_timeout, AF_INET6);
-}
-
 static gboolean
 share_init (NMDevice *self, GError **error)
 {
@@ -10141,9 +10129,9 @@ share_init (NMDevice *self, GError **error)
 	} else if (!nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv4/ip_forward"), "1")) {
 		errsv = errno;
 		_LOGD (LOGD_SHARING, "share: error enabling IPv4 forwarding: (%d) %s",
-		       errsv, g_strerror (errsv));
+		       errsv, nm_strerror_native (errsv));
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "cannot set ipv4/ip_forward: %s", g_strerror (errsv));
+		             "cannot set ipv4/ip_forward: %s", nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -10152,7 +10140,7 @@ share_init (NMDevice *self, GError **error)
 	} else if (!nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv4/ip_dynaddr"), "1")) {
 		errsv = errno;
 		_LOGD (LOGD_SHARING, "share: error enabling dynamic addresses: (%d) %s",
-		       errsv, strerror (errsv));
+		       errsv, nm_strerror_native (errsv));
 	}
 
 	for (iter = modules; *iter; iter++)
@@ -10179,6 +10167,9 @@ start_sharing (NMDevice *self, NMIP4Config *config, GError **error)
 	const NMPlatformIP4Address *ip4_addr = NULL;
 	const char *ip_iface;
 	GError *local = NULL;
+	NMConnection *conn;
+	NMSettingConnection *s_con;
+	gboolean announce_android_metered;
 
 	g_return_val_if_fail (config, FALSE);
 
@@ -10200,7 +10191,7 @@ start_sharing (NMDevice *self, NMIP4Config *config, GError **error)
 		return FALSE;
 
 	req = nm_device_get_act_request (self);
-	g_assert (req);
+	g_return_val_if_fail (req, FALSE);
 
 	netmask = _nm_utils_ip4_prefix_to_netmask (ip4_addr->plen);
 	nm_utils_inet4_ntop (netmask, str_mask);
@@ -10221,7 +10212,35 @@ start_sharing (NMDevice *self, NMIP4Config *config, GError **error)
 
 	nm_act_request_set_shared (req, TRUE);
 
-	if (!nm_dnsmasq_manager_start (priv->dnsmasq_manager, config, &local)) {
+	conn = nm_act_request_get_applied_connection (req);
+	s_con = nm_connection_get_setting_connection (conn);
+
+	switch (nm_setting_connection_get_metered (s_con)) {
+	case NM_METERED_YES:
+		/* honor the metered flag. Note that reapply on the device does not affect
+		 * the metered setting. This is different from other profiles, where the
+		 * metered flag of an activated profile can be changed (reapplied). */
+		announce_android_metered = TRUE;
+		break;
+	case NM_METERED_UNKNOWN:
+		/* we pick up the current value and announce it. But again, we cannot update
+		 * the announced setting without restarting dnsmasq. That means, if the default
+		 * route changes w.r.t. being metered, then the shared connection does not get
+		 * updated before reactivating. */
+		announce_android_metered = NM_IN_SET (nm_manager_get_metered (nm_manager_get ()),
+		                                      NM_METERED_YES,
+		                                      NM_METERED_GUESS_YES);
+		break;
+	default:
+		announce_android_metered = FALSE;
+		break;
+	}
+
+
+	if (!nm_dnsmasq_manager_start (priv->dnsmasq_manager,
+	                               config,
+	                               announce_android_metered,
+	                               &local)) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "could not start dnsmasq due to %s", local->message);
 		g_error_free (local);
@@ -10240,10 +10259,7 @@ arp_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->acd.announcing) {
-		nm_acd_manager_destroy (priv->acd.announcing);
-		priv->acd.announcing = NULL;
-	}
+	nm_clear_pointer (&priv->acd.announcing, nm_acd_manager_free);
 }
 
 void
@@ -10278,7 +10294,11 @@ nm_device_arp_announce (NMDevice *self)
 	if (num == 0)
 		return;
 
-	priv->acd.announcing = nm_acd_manager_new (nm_device_get_ip_ifindex (self), hw_addr, hw_addr_len);
+	priv->acd.announcing = nm_acd_manager_new (nm_device_get_ip_ifindex (self),
+	                                           hw_addr,
+	                                           hw_addr_len,
+	                                           NULL,
+	                                           NULL);
 
 	for (i = 0; i < num; i++) {
 		NMIPAddress *ip = nm_setting_ip_config_get_address (s_ip4, i);
@@ -10294,18 +10314,16 @@ nm_device_arp_announce (NMDevice *self)
 }
 
 static void
-activate_stage5_ip4_config_result (NMDevice *self)
+activate_stage5_ip_config_result_4 (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActRequest *req;
 	const char *method;
-	NMConnection *connection;
 	int ip_ifindex;
+	gboolean do_announce = FALSE;
 
 	req = nm_device_get_act_request (self);
 	g_assert (req);
-	connection = nm_act_request_get_applied_connection (req);
-	g_assert (connection);
 
 	/* Interface must be IFF_UP before IP config can be applied */
 	ip_ifindex = nm_device_get_ip_ifindex (self);
@@ -10322,9 +10340,8 @@ activate_stage5_ip4_config_result (NMDevice *self)
 	}
 
 	/* Start IPv4 sharing if we need it */
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) {
+	method = nm_device_get_effective_ip_config_method (self, AF_INET);
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
 		gs_free_error GError *error = NULL;
 
 		if (!start_sharing (self, priv->ip_config_4, &error)) {
@@ -10346,45 +10363,77 @@ activate_stage5_ip4_config_result (NMDevice *self)
 		                           NULL, NULL, NULL);
 	}
 
-	nm_device_arp_announce (self);
+	/* Send ARP announcements */
+
+	if (nm_device_is_master (self)) {
+		CList *iter;
+		SlaveInfo *info;
+
+		/* Skip announcement if there are no device enslaved, for two reasons:
+		 * 1) the master has a temporary MAC address until the first slave comes
+		 * 2) announcements are going to be dropped anyway without slaves
+		 */
+		do_announce = FALSE;
+
+		c_list_for_each (iter, &priv->slaves) {
+			info = c_list_entry (iter, SlaveInfo, lst_slave);
+			if (info->slave_is_enslaved) {
+				do_announce = TRUE;
+				break;
+			}
+		}
+	} else
+		do_announce = TRUE;
+
+	if (do_announce)
+		nm_device_arp_announce (self);
+
 	nm_device_remove_pending_action (self, NM_PENDING_ACTION_DHCP4, FALSE);
 
 	/* Enter the IP_CHECK state if this is the first method to complete */
-	_set_ip_state (self, AF_INET, IP_DONE);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
 	check_ip_state (self, FALSE, TRUE);
 }
 
 void
-nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *config)
+nm_device_activate_schedule_ip_config_result (NMDevice *self,
+                                              int addr_family,
+                                              NMIPConfig *config)
 {
 	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	g_return_if_fail (   !config
+	                  || (   addr_family == AF_INET
+	                      && nm_ip_config_get_addr_family (config) == AF_INET));
+
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	applied_config_init (&priv->dev_ip4_config, config);
-	activation_source_schedule (self, activate_stage5_ip4_config_result, AF_INET);
-}
+	if (IS_IPv4) {
+		applied_config_init (&priv->dev_ip_config_4, config);
+	} else {
+		/* If IP had previously failed, move it back to NM_DEVICE_IP_STATE_CONF since we
+		 * clearly now have configuration.
+		 */
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
+	}
 
-gboolean
-nm_device_activate_ip4_state_in_conf (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_CONF;
+	activation_source_schedule (self, activate_stage5_ip_config_result_x[IS_IPv4], addr_family);
 }
 
-gboolean
-nm_device_activate_ip4_state_in_wait (NMDevice *self)
+NMDeviceIPState
+nm_device_activate_get_ip_state (NMDevice *self,
+                                 int addr_family)
 {
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT;
-}
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-gboolean
-nm_device_activate_ip4_state_done (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_DONE;
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_IP_STATE_NONE);
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NM_DEVICE_IP_STATE_NONE);
+
+	return NM_DEVICE_GET_PRIVATE (self)->ip_state_x[IS_IPv4];
 }
 
 static void
@@ -10407,7 +10456,7 @@ dad6_add_pending_address (NMDevice *self,
 		       nm_platform_ip6_address_to_string (pl_addr, NULL, 0));
 
 		if (!*dad6_config)
-			*dad6_config = _ip6_config_new (self);
+			*dad6_config = nm_device_ip6_config_new (self);
 
 		nm_ip6_config_add_address (*dad6_config, pl_addr);
 	}
@@ -10424,7 +10473,7 @@ dad6_get_pending_addresses (NMDevice *self)
 	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) };
+	                         (NMIP6Config *) applied_config_get_current (&priv->dev2_ip_config_6) };
 	const NMPlatformIP6Address *addr;
 	NMIP6Config *dad6_config = NULL;
 	NMDedupMultiIter ipconf_iter;
@@ -10465,19 +10514,16 @@ dad6_get_pending_addresses (NMDevice *self)
 }
 
 static void
-activate_stage5_ip6_config_commit (NMDevice *self)
+activate_stage5_ip_config_result_6 (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActRequest *req;
 	const char *method;
-	NMConnection *connection;
 	int ip_ifindex;
 	int errsv;
 
 	req = nm_device_get_act_request (self);
 	g_assert (req);
-	connection = nm_act_request_get_applied_connection (req);
-	g_assert (connection);
 
 	/* Interface must be IFF_UP before IP config can be applied */
 	ip_ifindex = nm_device_get_ip_ifindex (self);
@@ -10491,7 +10537,7 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 
 	if (ip_config_merge_and_apply (self, AF_INET6, TRUE)) {
 		if (   priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE
-		    && priv->ip6_state == IP_CONF) {
+		    && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
 			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.
@@ -10509,18 +10555,17 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 		nm_device_remove_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, FALSE);
 
 		/* Start IPv6 forwarding if we need it */
-		method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-
-		if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0) {
+		method = nm_device_get_effective_ip_config_method (self, AF_INET6);
+		if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
 			if (!nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv6/conf/all/forwarding"), "1")) {
 				errsv = errno;
-				_LOGE (LOGD_SHARING, "share: error enabling IPv6 forwarding: (%d) %s", errsv, strerror (errsv));
+				_LOGE (LOGD_SHARING, "share: error enabling IPv6 forwarding: (%d) %s", errsv, nm_strerror_native (errsv));
 				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
 			}
 		}
 
 		/* Check if we have to wait for DAD */
-		if (priv->ip6_state == IP_CONF && !priv->dad6_ip6_config) {
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF && !priv->dad6_ip6_config) {
 			if (!priv->carrier && priv->ignore_carrier && get_ip_config_may_fail (self, AF_INET6))
 				_LOGI (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: carrier missing and ignored, not delaying activation");
 			else
@@ -10529,7 +10574,7 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 			if (priv->dad6_ip6_config) {
 				_LOGD (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: awaiting termination");
 			} else {
-				_set_ip_state (self, AF_INET6, IP_DONE);
+				_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 				check_ip_state (self, FALSE, TRUE);
 			}
 		}
@@ -10539,43 +10584,6 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 	}
 }
 
-void
-nm_device_activate_schedule_ip6_config_result (NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	g_return_if_fail (NM_IS_DEVICE (self));
-
-	/* If IP had previously failed, move it back to IP_CONF since we
-	 * clearly now have configuration.
-	 */
-	if (priv->ip6_state == IP_FAIL)
-		_set_ip_state (self, AF_INET6, IP_CONF);
-
-	activation_source_schedule (self, activate_stage5_ip6_config_commit, AF_INET6);
-}
-
-gboolean
-nm_device_activate_ip6_state_in_conf (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_CONF;
-}
-
-gboolean
-nm_device_activate_ip6_state_in_wait (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT;
-}
-
-gboolean
-nm_device_activate_ip6_state_done (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_DONE;
-}
-
 /*****************************************************************************/
 
 static void
@@ -10741,7 +10749,7 @@ _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, addr_family, IP_NONE);
+	_set_ip_state (self, addr_family, NM_DEVICE_IP_STATE_NONE);
 
 	if (nm_clear_g_source (&priv->queued_ip_config_id_x[IS_IPv4])) {
 		_LOGD (LOGD_DEVICE, "clearing queued IP%c config change",
@@ -10753,7 +10761,7 @@ _cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type)
 		arp_cleanup (self);
 		dnsmasq_cleanup (self);
 		ipv4ll_cleanup (self);
-		g_slist_free_full (priv->acd.dad_list, (GDestroyNotify) nm_acd_manager_destroy);
+		g_slist_free_full (priv->acd.dad_list, (GDestroyNotify) nm_acd_manager_free);
 		priv->acd.dad_list = NULL;
 	} else {
 		g_slist_free_full (priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
@@ -10766,8 +10774,10 @@ _cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type)
 }
 
 gboolean
-_nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
-                                    GError **error, const char **whitelist)
+_nm_device_hash_check_invalid_keys (GHashTable *hash,
+                                    const char *setting_name,
+                                    GError **error,
+                                    const char *const*whitelist)
 {
 	guint found_whitelisted_keys = 0;
 	guint i;
@@ -10841,12 +10851,12 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 	g_return_if_fail (NM_IS_DEVICE (self));
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->ip4_state != IP_NONE) {
+	if (priv->ip_state_4 != NM_DEVICE_IP_STATE_NONE) {
 		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);
+		g_clear_object (&priv->dev_ip_config_4.current);
+		g_clear_object (&priv->dev2_ip_config_4.current);
+		priv->con_ip_config_4 = nm_device_ip4_config_new (self);
 		nm_ip4_config_merge_setting (priv->con_ip_config_4,
 		                             s_ip4_new,
 		                             _get_mdns (self),
@@ -10863,7 +10873,7 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 
 		if (!nm_streq0 (method_old, method_new)) {
 			_cleanup_ip_pre (self, AF_INET, CLEANUP_TYPE_DECONFIGURE);
-			_set_ip_state (self, AF_INET, IP_WAIT);
+			_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
 			if (!nm_device_activate_stage3_ip4_start (self))
 				_LOGW (LOGD_IP4, "Failed to apply IPv4 configuration");
 			return;
@@ -10882,12 +10892,12 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 			metric_new = nm_setting_ip_config_get_route_metric (s_ip4_new);
 
 			if (metric_old != metric_new) {
-				if (priv->dev_ip4_config.orig) {
-					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->dev_ip4_config.orig,
+				if (priv->dev_ip_config_4.orig) {
+					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->dev_ip_config_4.orig,
 					                                    nm_device_get_route_metric (self, AF_INET));
 				}
-				if (priv->wwan_ip_config_4.orig) {
-					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->wwan_ip_config_4.orig,
+				if (priv->dev2_ip_config_4.orig) {
+					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->dev2_ip_config_4.orig,
 					                                    nm_device_get_route_metric (self, AF_INET));
 				}
 				if (priv->dhcp4.client) {
@@ -10913,16 +10923,16 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 	g_return_if_fail (NM_IS_DEVICE (self));
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->ip6_state != IP_NONE) {
+	if (priv->ip_state_6 != NM_DEVICE_IP_STATE_NONE) {
 		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);
+		g_clear_object (&priv->dev2_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);
+		priv->con_ip_config_6 = nm_device_ip6_config_new (self);
 		nm_ip6_config_merge_setting (priv->con_ip_config_6,
 		                             s_ip6_new,
 		                             nm_device_get_route_table (self, AF_INET6, TRUE),
@@ -10937,7 +10947,7 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 
 		if (!nm_streq0 (method_old, method_new)) {
 			_cleanup_ip_pre (self, AF_INET6, CLEANUP_TYPE_DECONFIGURE);
-			_set_ip_state (self, AF_INET6, IP_WAIT);
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
 			if (!nm_device_activate_stage3_ip6_start (self))
 				_LOGW (LOGD_IP6, "Failed to apply IPv6 configuration");
 			return;
@@ -10946,7 +10956,7 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 		if (s_ip6_old && s_ip6_new) {
 			gint64 metric_old, metric_new;
 
-			/* See comment in nm_device_reactivate_ip6_config() */
+			/* See comment in nm_device_reactivate_ip4_config() */
 			metric_old = nm_setting_ip_config_get_route_metric (s_ip6_old);
 			metric_new = nm_setting_ip_config_get_route_metric (s_ip6_new);
 
@@ -10959,8 +10969,8 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->dhcp6.ip6_config.orig,
 					                                    nm_device_get_route_metric (self, AF_INET6));
 				}
-				if (priv->wwan_ip_config_6.orig) {
-					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->wwan_ip_config_6.orig,
+				if (priv->dev2_ip_config_6.orig) {
+					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->dev2_ip_config_6.orig,
 					                                    nm_device_get_route_metric (self, AF_INET6));
 				}
 				if (priv->dhcp6.client) {
@@ -11070,7 +11080,6 @@ can_reapply_change (NMDevice *self, const char *setting_name,
 static void
 reapply_connection (NMDevice *self, NMConnection *con_old, NMConnection *con_new)
 {
-
 }
 
 /* check_and_reapply_connection:
@@ -11469,7 +11478,7 @@ _rt6_temporary_not_available_timeout (gpointer user_data)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	priv->rt6_temporary_not_available_id = 0;
-	nm_device_activate_schedule_ip6_config_result (self);
+	nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 
 	return G_SOURCE_REMOVE;
 }
@@ -11583,14 +11592,15 @@ disconnect_cb (NMDevice *self,
 }
 
 static void
-_clear_queued_act_request (NMDevicePrivate *priv)
+_clear_queued_act_request (NMDevicePrivate *priv,
+                           NMActiveConnectionStateReason active_reason)
 {
 	if (priv->queued_act_request) {
 		gs_unref_object NMActRequest *ac = NULL;
 
 		ac = g_steal_pointer (&priv->queued_act_request);
 		nm_active_connection_set_state_fail ((NMActiveConnection *) ac,
-		                                     NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED,
+		                                     active_reason,
 		                                     NULL);
 	}
 }
@@ -11732,7 +11742,8 @@ _carrier_wait_check_queued_act_request (NMDevice *self)
 	priv->queued_act_request_is_waiting_for_carrier = FALSE;
 	if (!priv->carrier) {
 		_LOGD (LOGD_DEVICE, "Cancel queued activation request as we have no carrier after timeout");
-		_clear_queued_act_request (priv);
+		_clear_queued_act_request (priv,
+		                           NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
 	} else {
 		gs_unref_object NMActRequest *queued_req = NULL;
 
@@ -11787,7 +11798,9 @@ _carrier_wait_check_act_request_must_queue (NMDevice *self, NMActRequest *req)
 }
 
 void
-nm_device_disconnect_active_connection (NMActiveConnection *active)
+nm_device_disconnect_active_connection (NMActiveConnection *active,
+                                        NMDeviceStateReason device_reason,
+                                        NMActiveConnectionStateReason active_reason)
 {
 	NMDevice *self;
 	NMDevicePrivate *priv;
@@ -11795,38 +11808,59 @@ nm_device_disconnect_active_connection (NMActiveConnection *active)
 	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (active));
 
 	self = nm_active_connection_get_device (active);
-
 	if (!self) {
 		/* hm, no device? Just fail the active connection. */
-		nm_active_connection_set_state_fail (active,
-		                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
-		                                     NULL);
-		return;
+		goto do_fail;
 	}
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (NM_ACTIVE_CONNECTION (priv->queued_act_request) == active) {
-		_clear_queued_act_request (priv);
+		_clear_queued_act_request (priv, active_reason);
 		return;
 	}
+
 	if (NM_ACTIVE_CONNECTION (priv->act_request.obj) == active) {
 		if (priv->state < NM_DEVICE_STATE_DEACTIVATING) {
 			nm_device_state_changed (self,
 			                         NM_DEVICE_STATE_DEACTIVATING,
-			                         NM_DEVICE_STATE_REASON_NEW_ACTIVATION);
+			                         device_reason);
 		} else {
-			/* it's going down already... */
+			/* @active is the current ac of @self, but it's going down already.
+			 * Nothing to do. */
 		}
+		return;
 	}
+
+	/* the active connection references this device, but it's neither the
+	 * queued_act_request nor the current act_request. Just set it to fail... */
+do_fail:
+	nm_active_connection_set_state_fail (active,
+	                                     active_reason,
+	                                     NULL);
 }
 
 void
 nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
 	gboolean must_queue;
 
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	nm_keep_alive_arm (nm_active_connection_get_keep_alive (NM_ACTIVE_CONNECTION (req)));
+
+	if (nm_active_connection_get_state (NM_ACTIVE_CONNECTION (req)) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
+		/* it's already deactivating. Nothing to do. */
+		nm_assert (NM_IN_SET (nm_active_connection_get_device (NM_ACTIVE_CONNECTION (req)), NULL, self));
+		return;
+	}
+
+	nm_assert (self == nm_active_connection_get_device (NM_ACTIVE_CONNECTION (req)));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
 	must_queue = _carrier_wait_check_act_request_must_queue (self, req);
 
 	if (   !priv->act_request.obj
@@ -11836,8 +11870,9 @@ nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 		return;
 	}
 
-	/* supercede any already-queued request */
-	_clear_queued_act_request (priv);
+	/* supersede any already-queued request */
+	_clear_queued_act_request (priv,
+	                           NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
 	priv->queued_act_request = g_object_ref (req);
 	priv->queued_act_request_is_waiting_for_carrier = must_queue;
 
@@ -12027,16 +12062,11 @@ nm_device_set_ip_config (NMDevice *self,
 		       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);
+			applied_config_clear (&priv->dev_ip_config_4);
 		} else
 			priv->needs_ip6_subnet = FALSE;
 	}
 
-	if (IS_IPv4 && FALSE /* rp_filter handling is disabled */) {
-		if (!nm_device_sys_iface_state_is_external_or_assume (self))
-			ip4_rp_filter_update (self);
-	}
-
 	if (has_changes) {
 
 		if (IS_IPv4)
@@ -12133,13 +12163,25 @@ nm_device_replace_vpn4_config (NMDevice *self, NMIP4Config *old, NMIP4Config *co
 }
 
 void
-nm_device_set_wwan_ip4_config (NMDevice *self, NMIP4Config *config)
+nm_device_set_dev2_ip_config (NMDevice *self,
+                              int addr_family,
+                              NMIPConfig *config)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-	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");
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	g_return_if_fail (   !config
+	                  || nm_ip_config_get_addr_family (config) == addr_family);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	applied_config_init (&priv->dev2_ip_config_x[IS_IPv4], config);
+	if (!ip_config_merge_and_apply (self, addr_family, TRUE)) {
+		_LOGW (LOGD_IP, "failed to set extra device IPv%c configuration",
+		       nm_utils_addr_family_to_char (addr_family));
+	}
 }
 
 void
@@ -12160,16 +12202,6 @@ nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *co
 		_LOGW (LOGD_IP6, "failed to set VPN routes for device");
 }
 
-void
-nm_device_set_wwan_ip6_config (NMDevice *self, NMIP6Config *config)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	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");
-}
-
 NMDhcp6Config *
 nm_device_get_dhcp6_config (NMDevice *self)
 {
@@ -12404,7 +12436,7 @@ nm_device_start_ip_check (NMDevice *self)
 	g_return_if_fail (!priv->gw_ping.watch);
 	g_return_if_fail (!priv->gw_ping.timeout);
 	g_return_if_fail (!priv->gw_ping.pid);
-	g_return_if_fail (priv->ip4_state == IP_DONE || priv->ip6_state == IP_DONE);
+	g_return_if_fail (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE || priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE);
 
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
@@ -12417,14 +12449,14 @@ nm_device_start_ip_check (NMDevice *self)
 	if (timeout) {
 		const NMPObject *gw;
 
-		if (priv->ip_config_4 && priv->ip4_state == IP_DONE) {
+		if (priv->ip_config_4 && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 			gw = nm_ip4_config_best_default_route_get (priv->ip_config_4);
 			if (gw) {
 				nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (gw)->gateway, buf);
 				ping_binary = nm_utils_find_helper ("ping", "/usr/bin/ping", NULL);
 				log_domain = LOGD_IP4;
 			}
-		} else if (priv->ip_config_6 && priv->ip6_state == IP_DONE) {
+		} else if (priv->ip_config_6 && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 			gw = nm_ip6_config_best_default_route_get (priv->ip_config_6);
 			if (gw) {
 				nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (gw)->gateway, buf);
@@ -12564,11 +12596,11 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 	_update_ip4_address (self);
 
 	/* when the link comes up, we must restore IP configuration if necessary. */
-	if (priv->ip4_state == IP_DONE) {
+	if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 		if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 			_LOGW (LOGD_IP4, "failed applying IP4 config after bringing link up");
 	}
-	if (priv->ip6_state == IP_DONE) {
+	if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 		if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 			_LOGW (LOGD_IP6, "failed applying IP6 config after bringing link up");
 	}
@@ -12637,6 +12669,7 @@ nm_device_get_firmware_missing (NMDevice *self)
 static void
 intersect_ext_config (NMDevice *self,
                       AppliedConfig *config,
+                      gboolean intersect_addresses,
                       gboolean intersect_routes)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -12653,11 +12686,16 @@ intersect_ext_config (NMDevice *self,
 	      ? (NMIPConfig *) priv->ext_ip_config_4
 	      : (NMIPConfig *) priv->ext_ip_config_6;
 
-	if (config->current)
-		nm_ip_config_intersect (config->current, ext, intersect_routes, penalty);
-	else {
+	if (config->current) {
+		nm_ip_config_intersect (config->current,
+		                        ext,
+		                        intersect_addresses,
+		                        intersect_routes,
+		                        penalty);
+	} else {
 		config->current = nm_ip_config_intersect_alloc (config->orig,
 		                                                ext,
+		                                                intersect_addresses,
 		                                                intersect_routes,
 		                                                penalty);
 	}
@@ -12693,15 +12731,16 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				 * by the user. */
 				if (priv->con_ip_config_4) {
 					nm_ip4_config_intersect (priv->con_ip_config_4, priv->ext_ip_config_4,
+					                         TRUE,
 					                         is_up,
 					                         default_route_metric_penalty_get (self, AF_INET));
 				}
 
-				intersect_ext_config (self, &priv->dev_ip4_config, is_up);
-				intersect_ext_config (self, &priv->wwan_ip_config_4, is_up);
+				intersect_ext_config (self, &priv->dev_ip_config_4, TRUE, is_up);
+				intersect_ext_config (self, &priv->dev2_ip_config_4, TRUE, is_up);
 
 				for (iter = priv->vpn_configs_4; iter; iter = iter->next)
-					nm_ip4_config_intersect (iter->data, priv->ext_ip_config_4, is_up, 0);
+					nm_ip4_config_intersect (iter->data, priv->ext_ip_config_4, TRUE, is_up, 0);
 			}
 
 			/* Remove parts from ext_ip_config_4 to only contain the information that
@@ -12711,14 +12750,14 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				nm_ip4_config_subtract (priv->ext_ip_config_4, priv->con_ip_config_4,
 				                        default_route_metric_penalty_get (self, AF_INET));
 			}
-			if (applied_config_get_current (&priv->dev_ip4_config)) {
+			if (applied_config_get_current (&priv->dev_ip_config_4)) {
 				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_4,
-				                       applied_config_get_current (&priv->dev_ip4_config),
+				                       applied_config_get_current (&priv->dev_ip_config_4),
 				                       default_route_metric_penalty_get (self, AF_INET));
 			}
-			if (applied_config_get_current (&priv->wwan_ip_config_4)) {
+			if (applied_config_get_current (&priv->dev2_ip_config_4)) {
 				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_4,
-				                       applied_config_get_current (&priv->wwan_ip_config_4),
+				                       applied_config_get_current (&priv->dev2_ip_config_4),
 				                       default_route_metric_penalty_get (self, AF_INET));
 			}
 			for (iter = priv->vpn_configs_4; iter; iter = iter->next)
@@ -12746,15 +12785,16 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				if (priv->con_ip_config_6) {
 					nm_ip6_config_intersect (priv->con_ip_config_6, priv->ext_ip_config_6,
 					                         is_up,
+					                         is_up,
 					                         default_route_metric_penalty_get (self, AF_INET6));
 				}
 
-				intersect_ext_config (self, &priv->ac_ip6_config, is_up);
-				intersect_ext_config (self, &priv->dhcp6.ip6_config, is_up);
-				intersect_ext_config (self, &priv->wwan_ip_config_6, is_up);
+				intersect_ext_config (self, &priv->ac_ip6_config, is_up, is_up);
+				intersect_ext_config (self, &priv->dhcp6.ip6_config, is_up, is_up);
+				intersect_ext_config (self, &priv->dev2_ip_config_6, is_up, is_up);
 
 				for (iter = priv->vpn_configs_6; iter; iter = iter->next)
-					nm_ip6_config_intersect (iter->data, priv->ext_ip_config_6, is_up, 0);
+					nm_ip6_config_intersect (iter->data, priv->ext_ip_config_6, is_up, is_up, 0);
 
 				if (   priv->ipv6ll_has
 				    && !nm_ip6_config_lookup_address (priv->ext_ip_config_6, &priv->ipv6ll_addr))
@@ -12778,9 +12818,9 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				                       applied_config_get_current (&priv->dhcp6.ip6_config),
 				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
-			if (applied_config_get_current (&priv->wwan_ip_config_6)) {
+			if (applied_config_get_current (&priv->dev2_ip_config_6)) {
 				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_6,
-				                       applied_config_get_current (&priv->wwan_ip_config_6),
+				                       applied_config_get_current (&priv->dev2_ip_config_6),
 				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
 			for (iter = priv->vpn_configs_6; iter; iter = iter->next)
@@ -12844,9 +12884,7 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 	 * update in such case.
 	 */
 	if (activation_source_is_scheduled (self,
-	                                    IS_IPv4
-	                                      ? activate_stage5_ip4_config_result
-	                                      : activate_stage5_ip6_config_commit,
+	                                    activate_stage5_ip_config_result_x[IS_IPv4],
 	                                    addr_family))
 		return G_SOURCE_CONTINUE;
 
@@ -12864,14 +12902,17 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 	if (!IS_IPv4) {
 		NMPlatform *platform;
 		GSList *dad6_failed_addrs, *iter;
+		const NMPlatformLink *pllink;
 
 		dad6_failed_addrs = g_steal_pointer (&priv->dad6_failed_addrs);
 
 		if (   priv->state > NM_DEVICE_STATE_DISCONNECTED
 		    && priv->state < NM_DEVICE_STATE_DEACTIVATING
+		    && priv->ifindex > 0
 		    && !nm_device_sys_iface_state_is_external (self)
 		    && (platform = nm_device_get_platform (self))
-		    && nm_platform_link_get (platform, priv->ifindex)) {
+		    && (pllink = nm_platform_link_get (platform, priv->ifindex))
+		    && (pllink->n_ifi_flags & IFF_UP)) {
 			gboolean need_ipv6ll = FALSE;
 			NMNDiscConfigMap ndisc_config_changed = NM_NDISC_CONFIG_NONE;
 
@@ -12914,29 +12955,22 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 
 	if (!IS_IPv4) {
 		/* Check if DAD is still pending */
-		if (   priv->ip6_state == IP_CONF
+		if (   priv->ip_state_6 == NM_DEVICE_IP_STATE_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, addr_family, IP_DONE);
+			_set_ip_state (self, addr_family, NM_DEVICE_IP_STATE_DONE);
 			check_ip_state (self, FALSE, TRUE);
 			if (priv->rt6_temporary_not_available)
-				nm_device_activate_schedule_ip6_config_result (self);
+				nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 		}
 	}
 
 	set_unmanaged_external_down (self, TRUE);
 
-	if (IS_IPv4 && FALSE /* rp_filter handling is disabled */) {
-		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;
 }
 
@@ -13089,10 +13123,10 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
 
 		/* @for_user_request can make the result only ~more~ managed.
 		 * If the flags already indicate a managed state for a non-user-request,
-		 * then it is also managed for an explict user-request.
+		 * then it is also managed for an explicit user-request.
 		 *
 		 * Effectively, this check is redundant, as the code below already
-		 * already ensures that. Still, express this invariant explictly here. */
+		 * already ensures that. Still, express this invariant explicitly here. */
 		if (_get_managed_by_flags (flags, mask, FALSE))
 			return TRUE;
 
@@ -13148,7 +13182,7 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
 /**
  * nm_device_get_managed:
  * @self: the #NMDevice
- * @for_user_request: whether to check the flags for an explict user-request
+ * @for_user_request: whether to check the flags for an explicit user-request
  *
  * Whether the device is unmanaged according to the unmanaged flags.
  *
@@ -13326,7 +13360,7 @@ _set_unmanaged_flags (NMDevice *self,
 	       flags,
 	       NM_PRINT_FMT_QUOTED (allow_state_transition,
 	                            ", reason ",
-	                            reason_to_string (reason),
+	                            reason_to_string_a (reason),
 	                            transition_state ? ", transition-state" : "",
 	                            ""));
 
@@ -13603,7 +13637,7 @@ 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->ip_config_4
-		    && priv->ip4_state == IP_DONE
+		    && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
 		    && nm_ip4_config_get_metered (priv->ip_config_4))
 			value = NM_METERED_GUESS_YES;
 	}
@@ -14101,22 +14135,32 @@ nm_device_remove_pending_action (NMDevice *self, const char *action, gboolean as
 	return FALSE;
 }
 
-gboolean
-nm_device_has_pending_action (NMDevice *self)
+const char *
+nm_device_has_pending_action_reason (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->pending_actions)
-		return TRUE;
+	if (priv->pending_actions) {
+
+		if (   !priv->pending_actions->next
+		    && nm_device_get_state (self) == NM_DEVICE_STATE_ACTIVATED
+		    && nm_streq (priv->pending_actions->data, NM_PENDING_ACTION_CARRIER_WAIT)) {
+			/* if the device is already in activated state, and the only reason
+			 * why it appears still busy is "carrier-wait", then we are already complete. */
+			return NULL;
+		}
+
+		return priv->pending_actions->data;
+	}
 
 	if (   nm_device_is_real (self)
 	    && nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
 		/* as long as the platform link is not yet initialized, we have a pending
 		 * action. */
-		return TRUE;
+		return NM_PENDING_ACTION_LINK_INIT;
 	}
 
-	return FALSE;
+	return NULL;
 }
 
 /*****************************************************************************/
@@ -14194,15 +14238,15 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	nm_device_set_ip_config (self, AF_INET6, NULL, TRUE, NULL);
 	g_clear_object (&priv->proxy_config);
 	g_clear_object (&priv->con_ip_config_4);
-	applied_config_clear (&priv->dev_ip4_config);
-	applied_config_clear (&priv->wwan_ip_config_4);
+	applied_config_clear (&priv->dev_ip_config_4);
+	applied_config_clear (&priv->dev2_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);
-	applied_config_clear (&priv->wwan_ip_config_6);
+	applied_config_clear (&priv->dev2_ip_config_6);
 	g_clear_object (&priv->ip_config_6);
 	g_clear_object (&priv->dad6_ip6_config);
 	priv->ipv6ll_has = FALSE;
@@ -14242,7 +14286,7 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 		delete_on_deactivate_check_and_schedule (self, nm_device_get_ip_ifindex (self));
 	}
 
-	/* ip_iface should be cleared after flushing all routes and addreses, since
+	/* ip_iface should be cleared after flushing all routes and addresses, since
 	 * those are identified by ip_iface, not by iface (which might be a tty
 	 * or ATM device).
 	 */
@@ -14266,7 +14310,7 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 	if (reason == NM_DEVICE_STATE_REASON_NOW_MANAGED)
 		_LOGD (LOGD_DEVICE, "preparing device");
 	else
-		_LOGD (LOGD_DEVICE, "deactivating device (reason '%s') [%d]", reason_to_string (reason), reason);
+		_LOGD (LOGD_DEVICE, "deactivating device (reason '%s') [%d]", reason_to_string_a (reason), reason);
 
 	/* Save whether or not we tried IPv6 for later */
 	priv = NM_DEVICE_GET_PRIVATE (self);
@@ -14276,8 +14320,8 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 	/* Turn off kernel IPv6 */
 	if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
 		set_disable_ipv6 (self, "1");
-		nm_device_ipv6_sysctl_set (self, "accept_ra", "0");
-		nm_device_ipv6_sysctl_set (self, "use_tempaddr", "0");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra", "0");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "use_tempaddr", "0");
 	}
 
 	/* Call device type-specific deactivation */
@@ -14306,6 +14350,7 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 
 	/* slave: mark no longer enslaved */
 	if (   priv->master
+	    && priv->ifindex > 0
 	    && nm_platform_link_get_master (nm_device_get_platform (self), priv->ifindex) <= 0)
 		nm_device_master_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
 
@@ -14342,8 +14387,8 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 			if (priv->ip6_mtu_initial) {
 				char sbuf[64];
 
-				nm_device_ipv6_sysctl_set (self, "mtu",
-				                           nm_sprintf_buf (sbuf, "%u", (unsigned) priv->ip6_mtu_initial));
+				nm_device_sysctl_ip_conf_set (self, AF_INET6, "mtu",
+				                              nm_sprintf_buf (sbuf, "%u", (unsigned) priv->ip6_mtu_initial));
 			}
 		}
 		priv->mtu_initial = 0;
@@ -14373,7 +14418,7 @@ find_dhcp4_address (NMDevice *self)
 
 	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));
+			return nm_utils_inet4_ntop_dup (a->address);
 	}
 	return NULL;
 }
@@ -14398,7 +14443,8 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		return;
 
 	connection = nm_device_get_applied_connection (self);
-	g_assert (connection);
+
+	g_return_if_fail (connection);
 
 	argv = g_ptr_array_sized_new (10);
 	g_ptr_array_set_free_func (argv, g_free);
@@ -14432,12 +14478,12 @@ nm_device_spawn_iface_helper (NMDevice *self)
 
 	dhcp4_address = find_dhcp4_address (self);
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
+	method = nm_device_get_effective_ip_config_method (self, AF_INET);
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
 		NMSettingIPConfig *s_ip4;
 
 		s_ip4 = nm_connection_get_setting_ip4_config (connection);
-		g_assert (s_ip4);
+		nm_assert (s_ip4);
 
 		g_ptr_array_add (argv, g_strdup ("--priority4"));
 		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_route_metric (self, AF_INET)));
@@ -14455,11 +14501,11 @@ nm_device_spawn_iface_helper (NMDevice *self)
 			if (client_id) {
 				g_ptr_array_add (argv, g_strdup ("--dhcp4-clientid"));
 				g_ptr_array_add (argv,
-				                 _nm_utils_bin2hexstr_full (g_bytes_get_data (client_id, NULL),
-				                                            g_bytes_get_size (client_id),
-				                                            ':',
-				                                            FALSE,
-				                                            NULL));
+				                 nm_utils_bin2hexstr_full (g_bytes_get_data (client_id, NULL),
+				                                           g_bytes_get_size (client_id),
+				                                           ':',
+				                                           FALSE,
+				                                           NULL));
 			}
 
 			hostname = nm_dhcp_client_get_hostname (priv->dhcp4.client);
@@ -14475,8 +14521,8 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		configured = TRUE;
 	}
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	if (g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) {
+	method = nm_utils_get_ip_config_method (connection, AF_INET6);
+	if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
 		NMSettingIPConfig *s_ip6;
 		NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT;
 
@@ -14497,11 +14543,11 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		if (nm_device_get_ip_iface_identifier (self, &iid, FALSE)) {
 			g_ptr_array_add (argv, g_strdup ("--iid"));
 			g_ptr_array_add (argv,
-			                 _nm_utils_bin2hexstr_full (iid.id_u8,
-			                                            sizeof (NMUtilsIPv6IfaceId),
-			                                            ':',
-			                                            FALSE,
-			                                            NULL));
+			                 nm_utils_bin2hexstr_full (iid.id_u8,
+			                                           sizeof (NMUtilsIPv6IfaceId),
+			                                           ':',
+			                                           FALSE,
+			                                           NULL));
 		}
 
 		g_ptr_array_add (argv, g_strdup ("--addr-gen-mode"));
@@ -14560,39 +14606,34 @@ ip6_managed_setup (NMDevice *self)
 {
 	set_nm_ipv6ll (self, TRUE);
 	set_disable_ipv6 (self, "1");
-	nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0");
-	nm_device_ipv6_sysctl_set (self, "accept_ra_pinfo", "0");
-	nm_device_ipv6_sysctl_set (self, "accept_ra_rtr_pref", "0");
-	nm_device_ipv6_sysctl_set (self, "use_tempaddr", "0");
-	nm_device_ipv6_sysctl_set (self, "forwarding", "0");
+	nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_defrtr", "0");
+	nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_pinfo", "0");
+	nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_rtr_pref", "0");
+	nm_device_sysctl_ip_conf_set (self, AF_INET6, "use_tempaddr", "0");
+	nm_device_sysctl_ip_conf_set (self, AF_INET6, "forwarding", "0");
 }
 
 static void
 deactivate_async_ready (NMDevice *self,
-                        GAsyncResult *res,
+                        GError *error,
                         gpointer user_data)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceStateReason reason = GPOINTER_TO_UINT (user_data);
-	GError *error = NULL;
 
-	NM_DEVICE_GET_CLASS (self)->deactivate_async_finish (self, res, &error);
-
-	/* If operation cancelled, just return */
-	if (   g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)
-	    || (priv->deactivating_cancellable && g_cancellable_is_cancelled (priv->deactivating_cancellable))) {
-		_LOGW (LOGD_DEVICE, "Deactivation cancelled");
-	} else {
-		/* In every other case, transition to the DISCONNECTED state */
-		if (error) {
-			_LOGW (LOGD_DEVICE, "Deactivation failed: %s",
-			       error->message);
-		}
-		nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		_LOGD (LOGD_DEVICE, "Deactivation cancelled");
+		return;
 	}
 
 	g_clear_object (&priv->deactivating_cancellable);
-	g_clear_error (&error);
+
+	/* In every other case, transition to the DISCONNECTED state */
+	if (error) {
+		_LOGW (LOGD_DEVICE, "Deactivation failed: %s",
+		       error->message);
+	}
+	nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
 }
 
 static void
@@ -14612,14 +14653,13 @@ deactivate_dispatcher_complete (guint call_id, gpointer user_data)
 	priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
 
 	if (nm_clear_g_cancellable (&priv->deactivating_cancellable))
-		g_warn_if_reached ();
+		nm_assert_not_reached ();
 
-	if (   NM_DEVICE_GET_CLASS (self)->deactivate_async
-	    && NM_DEVICE_GET_CLASS (self)->deactivate_async_finish) {
+	if (NM_DEVICE_GET_CLASS (self)->deactivate_async) {
 		priv->deactivating_cancellable = g_cancellable_new ();
 		NM_DEVICE_GET_CLASS (self)->deactivate_async (self,
 		                                              priv->deactivating_cancellable,
-		                                              (GAsyncReadyCallback) deactivate_async_ready,
+		                                              deactivate_async_ready,
 		                                              GUINT_TO_POINTER (reason));
 	} else
 		nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
@@ -14657,7 +14697,7 @@ _set_state_full (NMDevice *self,
 		_LOGD (LOGD_DEVICE, "state change: %s -> %s (reason '%s', sys-iface-state: '%s'%s)",
 		       nm_device_state_to_str (old_state),
 		       nm_device_state_to_str (state),
-		       reason_to_string (reason),
+		       reason_to_string_a (reason),
 		       _sys_iface_state_to_str (priv->sys_iface_state),
 		       priv->firmware_missing ? ", missing firmware" : "");
 		return;
@@ -14666,7 +14706,7 @@ _set_state_full (NMDevice *self,
 	_LOGI (LOGD_DEVICE, "state change: %s -> %s (reason '%s', sys-iface-state: '%s')",
 	       nm_device_state_to_str (old_state),
 	       nm_device_state_to_str (state),
-	       reason_to_string (reason),
+	       reason_to_string_a (reason),
 	       _sys_iface_state_to_str (priv->sys_iface_state));
 
 	priv->in_state_changed = TRUE;
@@ -14677,8 +14717,8 @@ _set_state_full (NMDevice *self,
 	queued_state_clear (self);
 
 	dispatcher_cleanup (self);
-	if (priv->deactivating_cancellable)
-		g_cancellable_cancel (priv->deactivating_cancellable);
+
+	nm_clear_g_cancellable (&priv->deactivating_cancellable);
 
 	/* Cache the activation request for the dispatcher */
 	req = nm_g_object_ref (priv->act_request.obj);
@@ -14702,8 +14742,10 @@ _set_state_full (NMDevice *self,
 	if (state <= NM_DEVICE_STATE_UNAVAILABLE) {
 		if (available_connections_del_all (self))
 			_notify (self, PROP_AVAILABLE_CONNECTIONS);
-		if (old_state > NM_DEVICE_STATE_UNAVAILABLE)
-			_clear_queued_act_request (priv);
+		if (old_state > NM_DEVICE_STATE_UNAVAILABLE) {
+			_clear_queued_act_request (priv,
+			                           NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
+		}
 	}
 
 	/* Update the available connections list when a device first becomes available */
@@ -14833,7 +14875,7 @@ _set_state_full (NMDevice *self,
 		}
 		break;
 	case NM_DEVICE_STATE_DEACTIVATING:
-		if (   (s_sriov = (NMSettingSriov *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))
+		if (   (s_sriov = nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))
 		    && priv->ifindex > 0) {
 			nm_platform_link_set_sriov_params (nm_device_get_platform (self),
 			                                   priv->ifindex, 0, NM_TERNARY_TRUE);
@@ -14968,8 +15010,8 @@ _set_state_full (NMDevice *self,
 	if (ip_config_valid (old_state) && !ip_config_valid (state))
 	    notify_ip_properties (self);
 
-	concheck_update_interval (self,
-	                          state == NM_DEVICE_STATE_ACTIVATED);
+	concheck_update_interval (self, AF_INET, state == NM_DEVICE_STATE_ACTIVATED);
+	concheck_update_interval (self, AF_INET6, state == NM_DEVICE_STATE_ACTIVATED);
 
 	/* Dispose of the cached activation request */
 	if (req)
@@ -15001,7 +15043,7 @@ queued_state_set (gpointer user_data)
 
 	_LOGD (LOGD_DEVICE, "queue-state[%s, reason:%s, id:%u]: %s",
 	       nm_device_state_to_str (priv->queued_state.state),
-	       reason_to_string (priv->queued_state.reason),
+	       reason_to_string_a (priv->queued_state.reason),
 	       priv->queued_state.id,
 	       "change state");
 
@@ -15032,23 +15074,23 @@ nm_device_queue_state (NMDevice *self,
 	if (priv->queued_state.id && priv->queued_state.state == state) {
 		_LOGD (LOGD_DEVICE, "queue-state[%s, reason:%s, id:%u]: %s%s%s%s",
 		       nm_device_state_to_str (priv->queued_state.state),
-		       reason_to_string (priv->queued_state.reason),
+		       reason_to_string_a (priv->queued_state.reason),
 		       priv->queued_state.id,
 		       "ignore queuing same state change",
 		       NM_PRINT_FMT_QUOTED (priv->queued_state.reason != reason,
-		                            " (reason differs: ", reason_to_string (reason), ")", ""));
+		                            " (reason differs: ", reason_to_string_a (reason), ")", ""));
 		return;
 	}
 
 	/* Add pending action for the new state before clearing the queued states, so
-	 * that we don't accidently pop all pending states and reach 'startup complete'  */
+	 * that we don't accidentally pop all pending states and reach 'startup complete'  */
 	nm_device_add_pending_action (self, queued_state_to_string (state), TRUE);
 
 	/* We should only ever have one delayed state transition at a time */
 	if (priv->queued_state.id) {
 		_LOGW (LOGD_DEVICE, "queue-state[%s, reason:%s, id:%u]: %s",
 		       nm_device_state_to_str (priv->queued_state.state),
-		       reason_to_string (priv->queued_state.reason),
+		       reason_to_string_a (priv->queued_state.reason),
 		       priv->queued_state.id,
 		       "replace previously queued state change");
 		nm_clear_g_source (&priv->queued_state.id);
@@ -15061,7 +15103,7 @@ nm_device_queue_state (NMDevice *self,
 
 	_LOGD (LOGD_DEVICE, "queue-state[%s, reason:%s, id:%u]: %s",
 	       nm_device_state_to_str (state),
-	       reason_to_string (reason),
+	       reason_to_string_a (reason),
 	       priv->queued_state.id,
 	       "queue state change");
 }
@@ -15076,7 +15118,7 @@ queued_state_clear (NMDevice *self)
 
 	_LOGD (LOGD_DEVICE, "queue-state[%s, reason:%s, id:%u]: %s",
 	       nm_device_state_to_str (priv->queued_state.state),
-	       reason_to_string (priv->queued_state.reason),
+	       reason_to_string_a (priv->queued_state.reason),
 	       priv->queued_state.id,
 	       "clear queued state change");
 	nm_clear_g_source (&priv->queued_state.id);
@@ -15166,7 +15208,7 @@ nm_device_update_hw_address (NMDevice *self)
 	        && priv->state < NM_DEVICE_STATE_PREPARE
 	        && !nm_device_is_activating (self))) {
 		/* when we get a hw_addr the first time or while the device
-		 * is not activated (with no explict hw address set), always
+		 * is not activated (with no explicit hw address set), always
 		 * update our initial hw-address as well. */
 		nm_device_update_initial_hw_address (self);
 	}
@@ -15183,7 +15225,7 @@ nm_device_update_initial_hw_address (NMDevice *self)
 		if (   priv->hw_addr_initial
 		    && priv->hw_addr_type != HW_ADDR_TYPE_UNSET) {
 			/* once we have the initial hw address set, we only allow
-			 * update if the currenty type is "unset". */
+			 * update if the currently type is "unset". */
 			return;
 		}
 		g_free (priv->hw_addr_initial);
@@ -15308,7 +15350,9 @@ _get_cloned_mac_address_setting (NMDevice *self, NMConnection *connection, gbool
 		gs_free char *a = NULL;
 
 		a = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-		                                           is_wifi ? "wifi.cloned-mac-address" : "ethernet.cloned-mac-address",
+		                                           is_wifi
+		                                               ? NM_CON_DEFAULT ("wifi.cloned-mac-address")
+		                                               : NM_CON_DEFAULT ("ethernet.cloned-mac-address"),
 		                                           self);
 
 		addr = NM_CLONED_MAC_PRESERVE;
@@ -15319,7 +15363,7 @@ _get_cloned_mac_address_setting (NMDevice *self, NMConnection *connection, gbool
 
 				/* for backward compatibility, read the deprecated wifi.mac-address-randomization setting. */
 				a = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-				                                           "wifi." NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION,
+				                                           NM_CON_DEFAULT ("wifi.mac-address-randomization"),
 				                                           self);
 				v = _nm_utils_ascii_str_to_int64 (a, 10,
 				                                  NM_SETTING_MAC_RANDOMIZATION_DEFAULT,
@@ -15356,7 +15400,9 @@ _get_generate_mac_address_mask_setting (NMDevice *self, NMConnection *connection
 	}
 
 	a = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                           is_wifi ? "wifi.generate-mac-address-mask" : "ethernet.generate-mac-address-mask",
+	                                           is_wifi
+	                                               ? NM_CON_DEFAULT ("wifi.generate-mac-address-mask")
+	                                               : NM_CON_DEFAULT ("ethernet.generate-mac-address-mask"),
 	                                           self);
 	if (!a)
 		return NULL;
@@ -15394,7 +15440,7 @@ _hw_addr_set (NMDevice *self,
 {
 	NMDevicePrivate *priv;
 	gboolean success = FALSE;
-	NMPlatformError plerr;
+	int r;
 	guint8 addr_bytes[NM_UTILS_HWADDR_LEN_MAX];
 	gsize addr_len;
 	gboolean was_taken_down = FALSE;
@@ -15431,21 +15477,21 @@ _hw_addr_set (NMDevice *self,
 	}
 
 again:
-	plerr = nm_platform_link_set_address (nm_device_get_platform (self), nm_device_get_ip_ifindex (self), addr_bytes, addr_len);
-	success = (plerr == NM_PLATFORM_ERROR_SUCCESS);
+	r = nm_platform_link_set_address (nm_device_get_platform (self), nm_device_get_ip_ifindex (self), addr_bytes, addr_len);
+	success = (r >= 0);
 	if (!success) {
 		retry_down =    !was_taken_down
-		             && plerr != NM_PLATFORM_ERROR_NOT_FOUND
+		             && r != -NME_PL_NOT_FOUND
 		             && nm_platform_link_is_up (nm_device_get_platform (self),
 		                                        nm_device_get_ip_ifindex (self));
-		_NMLOG (     retry_down
-		          || plerr == NM_PLATFORM_ERROR_NOT_FOUND
+		_NMLOG (  (   retry_down
+		           || r == -NME_PL_NOT_FOUND)
 		        ? LOGL_DEBUG
 		        : LOGL_WARN,
 		        LOGD_DEVICE,
 		        "set-hw-addr: failed to %s MAC address to %s (%s) (%s)%s",
 		        operation, addr, detail,
-		        nm_platform_error_to_string_a (plerr),
+		        nm_strerror (r),
 		        retry_down ? " (retry with taking down)" : "");
 	} else {
 		/* MAC address successfully changed; update the current MAC to match */
@@ -15468,7 +15514,7 @@ again:
 			 * that is rather complicated and it is not expected that this case
 			 * happens for regular drivers.
 			 * Note that brcmfmac can block NetworkManager for 500 msec while
-			 * taking down the device. Let's add annother 100 msec to that.
+			 * taking down the device. Let's add another 100 msec to that.
 			 *
 			 * wait/poll up to 100 msec until it changes. */
 
@@ -15870,7 +15916,9 @@ nm_device_get_supplicant_timeout (NMDevice *self)
 	g_return_val_if_fail (NM_IS_DEVICE (self), SUPPLICANT_DEFAULT_TIMEOUT);
 
 	connection = nm_device_get_applied_connection (self);
+
 	g_return_val_if_fail (connection, SUPPLICANT_DEFAULT_TIMEOUT);
+
 	s_8021x = nm_connection_get_setting_802_1x (connection);
 	if (s_8021x) {
 		timeout = nm_setting_802_1x_get_auth_timeout (s_8021x);
@@ -15879,7 +15927,7 @@ nm_device_get_supplicant_timeout (NMDevice *self)
 	}
 
 	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                    "802-1x.auth-timeout",
+	                                                    NM_CON_DEFAULT ("802-1x.auth-timeout"),
 	                                                    self,
 	                                                    1,
 	                                                    G_MAXINT32,
@@ -15901,13 +15949,13 @@ nm_device_auth_retries_try_next (NMDevice *self)
 	if (G_UNLIKELY (auth_retries == NM_DEVICE_AUTH_RETRIES_UNSET)) {
 		auth_retries = -1;
 
-		s_con = NM_SETTING_CONNECTION (nm_device_get_applied_setting (self, NM_TYPE_SETTING_CONNECTION));
+		s_con = nm_device_get_applied_setting (self, NM_TYPE_SETTING_CONNECTION);
 		if (s_con)
 			auth_retries = nm_setting_connection_get_auth_retries (s_con);
 
 		if (auth_retries == -1) {
 			auth_retries = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-			                                                            "connection.auth-retries",
+			                                                            NM_CON_DEFAULT ("connection.auth-retries"),
 			                                                            self,
 			                                                            -1, G_MAXINT32, -1);
 		}
@@ -15945,10 +15993,10 @@ _activation_func_to_string (ActivationHandleFunc func)
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage1_device_prepare);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage2_device_config);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage3_ip_config_start);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip4_config_timeout);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip6_config_timeout);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip4_config_result);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip6_config_commit);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip_config_timeout_4);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip_config_timeout_6);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip_config_result_4);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip_config_result_6);
 	g_return_val_if_reached ("unknown");
 }
 
@@ -15967,7 +16015,8 @@ nm_device_init (NMDevice *self)
 	c_list_init (&self->devices_lst);
 	c_list_init (&priv->slaves);
 
-	priv->connectivity_state = NM_CONNECTIVITY_UNKNOWN;
+	priv->concheck_x[0].state = NM_CONNECTIVITY_UNKNOWN;
+	priv->concheck_x[1].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]);
@@ -16155,7 +16204,8 @@ dispose (GObject *object)
 	if (nm_clear_g_source (&priv->carrier_wait_id))
 		nm_device_remove_pending_action (self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
 
-	_clear_queued_act_request (priv);
+	_clear_queued_act_request (priv,
+	                           NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
 
 	nm_clear_g_source (&priv->device_link_changed_id);
 	nm_clear_g_source (&priv->device_ip_link_changed_id);
@@ -16168,12 +16218,13 @@ dispose (GObject *object)
 		g_clear_object (&priv->lldp_listener);
 	}
 
-	nm_clear_g_source (&priv->concheck_p_cur_id);
+	nm_clear_g_source (&priv->concheck_x[0].p_cur_id);
+	nm_clear_g_source (&priv->concheck_x[1].p_cur_id);
 
 	G_OBJECT_CLASS (nm_device_parent_class)->dispose (object);
 
 	if (nm_clear_g_source (&priv->queued_state.id)) {
-		/* FIXME: we'd expect the queud_state to be alredy cleared and this statement
+		/* FIXME: we'd expect the queud_state to be already cleared and this statement
 		 * not being necessary. Add this check here to hopefully investigate crash
 		 * rh#1270247. */
 		g_return_if_reached ();
@@ -16293,6 +16344,8 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		nm_assert (priv->type == NM_DEVICE_TYPE_UNKNOWN);
 		priv->type = g_value_get_uint (value);
+		nm_assert (priv->type > NM_DEVICE_TYPE_UNKNOWN);
+		nm_assert (priv->type <= NM_DEVICE_TYPE_WIFI_P2P);
 		break;
 	case PROP_LINK_TYPE:
 		/* construct-only */
@@ -16505,8 +16558,11 @@ get_property (GObject *object, guint prop_id,
 	case PROP_RX_BYTES:
 		g_value_set_uint64 (value, priv->stats.rx_bytes);
 		break;
-	case PROP_CONNECTIVITY:
-		g_value_set_uint (value, priv->connectivity_state);
+	case PROP_IP4_CONNECTIVITY:
+		g_value_set_uint (value, priv->concheck_x[1].state);
+		break;
+	case PROP_IP6_CONNECTIVITY:
+		g_value_set_uint (value, priv->concheck_x[0].state);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -16594,6 +16650,8 @@ static const NMDBusInterfaceInfoExtended interface_info_device = {
 			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),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE       ("Ip4Connectivity",      "u",      NM_DEVICE_IP4_CONNECTIVITY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE       ("Ip6Connectivity",      "u",      NM_DEVICE_IP6_CONNECTIVITY),
 		),
 	),
 };
@@ -16636,10 +16694,8 @@ nm_device_class_init (NMDeviceClass *klass)
 	klass->is_available = is_available;
 	klass->act_stage1_prepare = act_stage1_prepare;
 	klass->act_stage2_config = act_stage2_config;
-	klass->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	klass->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
-	klass->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
-	klass->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
+	klass->act_stage3_ip_config_start = act_stage3_ip_config_start;
+	klass->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
 
 	klass->get_type_description = get_type_description;
 	klass->can_auto_connect = can_auto_connect;
@@ -16870,8 +16926,13 @@ nm_device_class_init (NMDeviceClass *klass)
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_CONNECTIVITY] =
-	     g_param_spec_uint (NM_DEVICE_CONNECTIVITY, "", "",
+	obj_properties[PROP_IP4_CONNECTIVITY] =
+	     g_param_spec_uint (NM_DEVICE_IP4_CONNECTIVITY, "", "",
+	                        NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_IP6_CONNECTIVITY] =
+	     g_param_spec_uint (NM_DEVICE_IP6_CONNECTIVITY, "", "",
 	                        NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
@@ -16951,12 +17012,12 @@ nm_device_class_init (NMDeviceClass *klass)
 	                  G_SIGNAL_RUN_FIRST,
 	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 0);
-
-	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);
 }
+
+/* Connection defaults from plugins */
+NM_CON_DEFAULT_NOP ("cdma.mtu");
+NM_CON_DEFAULT_NOP ("gsm.mtu");
+NM_CON_DEFAULT_NOP ("wifi.powersave");
+NM_CON_DEFAULT_NOP ("wifi.wake-on-wlan");
+NM_CON_DEFAULT_NOP ("wifi-sec.pmf");
+NM_CON_DEFAULT_NOP ("wifi-sec.fils");
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 3c703564..45c9dda0 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -54,7 +54,7 @@ typedef enum {
 static inline NMDeviceStateReason
 nm_device_state_reason_check (NMDeviceStateReason reason)
 {
-	/* the device-state-reason serves mostly informational purpse during a state
+	/* the device-state-reason serves mostly informational purpose during a state
 	 * change. In some cases however, decisions are made based on the reason.
 	 * I tend to think that interpreting the state reason to derive some behaviors
 	 * is confusing, because the cause and effect are so far apart.
@@ -76,6 +76,7 @@ nm_device_state_reason_check (NMDeviceStateReason reason)
 #define NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT    "waiting-for-supplicant"
 #define NM_PENDING_ACTION_WIFI_SCAN                 "wifi-scan"
 #define NM_PENDING_ACTION_WAITING_FOR_COMPANION     "waiting-for-companion"
+#define NM_PENDING_ACTION_LINK_INIT                 "link-init"
 
 #define NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "queued-state-change-"
 #define NM_PENDING_ACTIONPREFIX_ACTIVATION          "activation-"
@@ -142,13 +143,13 @@ 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"
 #define NM_DEVICE_STATISTICS_RX_BYTES        "rx-bytes"
 
-#define NM_DEVICE_CONNECTIVITY               "connectivity"
+#define NM_DEVICE_IP4_CONNECTIVITY           "ip4-connectivity"
+#define NM_DEVICE_IP6_CONNECTIVITY           "ip6-connectivity"
 
 #define NM_TYPE_DEVICE            (nm_device_get_type ())
 #define NM_DEVICE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE, NMDevice))
@@ -182,13 +183,13 @@ typedef enum { /*< skip >*/
 	_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP           = (1L << 2),
 
 	/* a device can be marked as unmanaged for various reasons. Some of these reasons
-	 * are authorative, others not. Non-authoritative reasons can be overruled by
+	 * are authoritative, others not. Non-authoritative reasons can be overruled by
 	 * `nmcli device set $DEVICE managed yes`. Also, for an explicit user activation
 	 * request we may want to consider the device as managed. This flag makes devices
 	 * that are unmanaged appear available. */
 	_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED  = (1L << 3),
 
-	/* a collection of flags, that are commonly set for an explict user-request. */
+	/* a collection of flags, that are commonly set for an explicit user-request. */
 	NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST                      = _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST
 	                                                                    | _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER
 	                                                                    | _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP
@@ -221,6 +222,10 @@ typedef enum { /*< skip >*/
 	NM_DEVICE_CHECK_DEV_AVAILABLE_ALL                                   = (1L << 1) - 1,
 } NMDeviceCheckDevAvailableFlags;
 
+typedef void (*NMDeviceDeactivateCallback) (NMDevice *self,
+                                            GError *error,
+                                            gpointer user_data);
+
 typedef struct _NMDeviceClass {
 	NMDBusObjectClass parent;
 
@@ -278,7 +283,7 @@ typedef struct _NMDeviceClass {
 	 * Hook for derived classes to be notfied during realize_start_setup()
 	 * and perform additional setup.
 	 *
-	 * The default implemention of NMDevice calls link_changed().
+	 * The default implementation of NMDevice calls link_changed().
 	 */
 	void        (*realize_start_notify) (NMDevice *self,
 	                                     const NMPlatformLink *pllink);
@@ -327,9 +332,11 @@ typedef struct _NMDeviceClass {
 
 	guint32     (*get_configured_mtu) (NMDevice *self, NMDeviceMtuSource *out_source);
 
+	const char *(*get_auto_ip_config_method) (NMDevice *self, int addr_family);
+
 	/* Checks whether the connection is compatible with the device using
 	 * only the devices type and characteristics.  Does not use any live
-	 * network information like WiFi scan lists etc.
+	 * network information like Wi-Fi scan lists etc.
 	 */
 	gboolean    (* check_connection_compatible) (NMDevice *self,
 	                                             NMConnection *connection,
@@ -362,27 +369,21 @@ typedef struct _NMDeviceClass {
 	                                             NMDeviceStateReason *out_failure_reason);
 	NMActStageReturn    (* act_stage2_config)   (NMDevice *self,
 	                                             NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage3_ip4_config_start) (NMDevice *self,
-	                                                     NMIP4Config **out_config,
-	                                                     NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage3_ip6_config_start) (NMDevice *self,
-	                                                     NMIP6Config **out_config,
-	                                                     NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage4_ip4_config_timeout)   (NMDevice *self,
-	                                                         NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage4_ip6_config_timeout)   (NMDevice *self,
-	                                                         NMDeviceStateReason *out_failure_reason);
+	NMActStageReturn    (* act_stage3_ip_config_start) (NMDevice *self,
+	                                                    int addr_family,
+	                                                    gpointer *out_config,
+	                                                    NMDeviceStateReason *out_failure_reason);
+	NMActStageReturn    (* act_stage4_ip_config_timeout)   (NMDevice *self,
+	                                                        int addr_family,
+	                                                        NMDeviceStateReason *out_failure_reason);
 
 	void                (* ip4_config_pre_commit) (NMDevice *self, NMIP4Config *config);
 
 	/* Async deactivating (in the DEACTIVATING phase) */
 	void            (* deactivate_async)        (NMDevice *self,
 	                                             GCancellable *cancellable,
-	                                             GAsyncReadyCallback callback,
+	                                             NMDeviceDeactivateCallback callback,
 	                                             gpointer user_data);
-	gboolean        (* deactivate_async_finish) (NMDevice *self,
-	                                             GAsyncResult *res,
-	                                             GError **error);
 
 	void            (* deactivate_reset_hw_addr) (NMDevice *self);
 
@@ -452,6 +453,11 @@ typedef struct _NMDeviceClass {
 
 	guint32         (* get_dhcp_timeout) (NMDevice *self,
 	                                      int addr_family);
+
+	/* Controls, whether to call act_stage2_config() callback also for assuming
+	 * a device or for external activations. In this case, act_stage2_config() must
+	 * take care not to touch the device's configuration. */
+	bool act_stage2_config_also_for_external_or_assume:1;
 } NMDeviceClass;
 
 typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device,
@@ -481,6 +487,7 @@ int             nm_device_get_ifindex           (NMDevice *dev);
 gboolean        nm_device_is_software           (NMDevice *dev);
 gboolean        nm_device_is_real               (NMDevice *dev);
 const char *    nm_device_get_ip_iface          (NMDevice *dev);
+const char *    nm_device_get_ip_iface_from_platform (NMDevice *dev);
 int             nm_device_get_ip_ifindex        (const NMDevice *dev);
 const char *    nm_device_get_driver            (NMDevice *dev);
 const char *    nm_device_get_driver_version    (NMDevice *dev);
@@ -541,7 +548,10 @@ NMConnection *  nm_device_get_settings_connection_get_connection (NMDevice *self
 NMConnection *  nm_device_get_applied_connection (NMDevice *dev);
 gboolean        nm_device_has_unmodified_applied_connection (NMDevice *self,
                                                              NMSettingCompareFlags compare_flags);
-NMSetting *     nm_device_get_applied_setting   (NMDevice *dev, GType setting_type);
+NMActivationStateFlags nm_device_get_activation_state_flags (NMDevice *self);
+
+gpointer /* (NMSetting *) */ nm_device_get_applied_setting   (NMDevice *dev,
+                                                              GType setting_type);
 
 void            nm_device_removed               (NMDevice *self, gboolean unconfigure_ip_config);
 
@@ -617,7 +627,7 @@ void nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device);
  *   the settings plugin (for example keyfile.unmanaged-devices or ifcfg-rh's
  *   NM_CONTROLLED=no). Although this is user-configuration (provided from
  *   the settings plugins, such as NM_CONTROLLED=no in ifcfg-rh), it cannot
- *   be overruled and is authorative. That is because users may depend on
+ *   be overruled and is authoritative. That is because users may depend on
  *   dropping a ifcfg-rh file to ensure the device is unmanaged.
  * @NM_UNMANAGED_USER_CONF: %TRUE when unmanaged by user decision via
  *   the NetworkManager.conf ("unmanaged" in the [device] section).
@@ -634,7 +644,7 @@ void nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device);
 typedef enum { /*< skip >*/
 	NM_UNMANAGED_NONE          = 0,
 
-	/* these flags are authorative. If one of them is set,
+	/* these flags are authoritative. If one of them is set,
 	 * the device cannot be managed. */
 	NM_UNMANAGED_SLEEPING      = (1LL <<  0),
 	NM_UNMANAGED_QUITTING      = (1LL <<  1),
@@ -767,7 +777,9 @@ void nm_device_queue_state   (NMDevice *self,
 
 gboolean nm_device_get_firmware_missing (NMDevice *self);
 
-void nm_device_disconnect_active_connection (NMActiveConnection *active);
+void nm_device_disconnect_active_connection (NMActiveConnection *active,
+                                             NMDeviceStateReason device_reason,
+                                             NMActiveConnectionStateReason active_reason);
 
 void nm_device_queue_activation (NMDevice *device, NMActRequest *req);
 
@@ -775,7 +787,13 @@ gboolean nm_device_supports_vlans (NMDevice *device);
 
 gboolean nm_device_add_pending_action    (NMDevice *device, const char *action, gboolean assert_not_yet_pending);
 gboolean nm_device_remove_pending_action (NMDevice *device, const char *action, gboolean assert_is_pending);
-gboolean nm_device_has_pending_action    (NMDevice *device);
+const char *nm_device_has_pending_action_reason (NMDevice *device);
+
+static inline gboolean
+nm_device_has_pending_action (NMDevice *device)
+{
+	return !!nm_device_has_pending_action_reason (device);
+}
 
 NMSettingsConnection *nm_device_get_best_connection (NMDevice *device,
                                                      const char *specific_object,
@@ -838,12 +856,13 @@ typedef void (*NMDeviceConnectivityCallback) (NMDevice *self,
 void nm_device_check_connectivity_update_interval (NMDevice *self);
 
 NMDeviceConnectivityHandle *nm_device_check_connectivity (NMDevice *self,
+                                                          int addr_family,
                                                           NMDeviceConnectivityCallback callback,
                                                           gpointer user_data);
 
 void nm_device_check_connectivity_cancel (NMDeviceConnectivityHandle *handle);
 
-NMConnectivityState nm_device_get_connectivity_state (NMDevice *self);
+NMConnectivityState nm_device_get_connectivity_state (NMDevice *self, int addr_family);
 
 typedef struct _NMBtVTableNetworkServer NMBtVTableNetworkServer;
 struct _NMBtVTableNetworkServer {
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index c0484ed3..cb3b5907 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -23,7 +23,6 @@
 #include "nm-lldp-listener.h"
 
 #include <net/ethernet.h>
-#include <errno.h>
 
 #include "platform/nm-platform.h"
 #include "nm-utils.h"
@@ -128,7 +127,7 @@ typedef struct {
             int _ifindex = (self) ? NM_LLDP_LISTENER_GET_PRIVATE (self)->ifindex : 0; \
             \
             _nm_log (_level, _NMLOG_DOMAIN, 0, \
-                     nm_platform_link_get_name (NM_PLATFORM_GET, _ifindex), \
+                     _ifindex > 0 ? nm_platform_link_get_name (NM_PLATFORM_GET, _ifindex) : NULL, \
                      NULL, \
                      "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                      _NMLOG_PREFIX_NAME, \
@@ -384,7 +383,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	                                     &chassis_id, &chassis_id_len);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading chassis-id: %s", g_strerror (-r));
+		             "failed reading chassis-id: %s", nm_strerror_native (-r));
 		return NULL;
 	}
 	if (chassis_id_len < 1) {
@@ -397,7 +396,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	                                  &port_id, &port_id_len);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading port-id: %s", g_strerror (-r));
+		             "failed reading port-id: %s", nm_strerror_native (-r));
 		return NULL;
 	}
 	if (port_id_len < 1) {
@@ -413,7 +412,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	r = sd_lldp_neighbor_get_destination_address (neighbor_sd, &neigh->destination_address);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed getting destination address: %s", g_strerror (-r));
+		             "failed getting destination address: %s", nm_strerror_native (-r));
 		goto out;
 	}
 
@@ -464,7 +463,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	r = sd_lldp_neighbor_tlv_rewind (neighbor_sd);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading tlv (rewind): %s", g_strerror (-r));
+		             "failed reading tlv (rewind): %s", nm_strerror_native (-r));
 		goto out;
 	}
 	do {
@@ -476,7 +475,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 			if (r == -ENXIO)
 				continue;
 			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-			             "failed reading tlv: %s", g_strerror (-r));
+			             "failed reading tlv: %s", nm_strerror_native (-r));
 			goto out;
 		}
 
diff --git a/src/devices/ovs/meson.build b/src/devices/ovs/meson.build
index 9d637fe9..834b27b0 100644
--- a/src/devices/ovs/meson.build
+++ b/src/devices/ovs/meson.build
@@ -3,12 +3,12 @@ sources = files(
   'nm-device-ovs-interface.c',
   'nm-device-ovs-port.c',
   'nm-ovsdb.c',
-  'nm-ovs-factory.c'
+  'nm-ovs-factory.c',
 )
 
 deps = [
   jansson_dep,
-  nm_dep
+  nm_dep,
 ]
 
 libnm_device_plugin_ovs = shared_module(
@@ -18,7 +18,7 @@ libnm_device_plugin_ovs = shared_module(
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_ovs
diff --git a/src/devices/ovs/nm-device-ovs-bridge.c b/src/devices/ovs/nm-device-ovs-bridge.c
index eff355a3..be707e7a 100644
--- a/src/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/devices/ovs/nm-device-ovs-bridge.c
@@ -77,17 +77,10 @@ get_generic_capabilities (NMDevice *device)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	return NM_ACT_STAGE_RETURN_IP_FAIL;
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	return NM_ACT_STAGE_RETURN_IP_FAIL;
 }
@@ -146,8 +139,7 @@ nm_device_ovs_bridge_class_init (NMDeviceOvsBridgeClass *klass)
 	device_class->create_and_realize = create_and_realize;
 	device_class->unrealize = unrealize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->enslave_slave = enslave_slave;
 	device_class->release_slave = release_slave;
 }
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index 2b48fae6..e3d3f9ee 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -121,48 +121,32 @@ link_changed (NMDevice *device,
 static gboolean
 _is_internal_interface (NMDevice *device)
 {
-	NMConnection *connection = nm_device_get_applied_connection (device);
-	NMSettingOvsInterface *s_ovs_iface = nm_connection_get_setting_ovs_interface (connection);
-
-	g_return_val_if_fail (s_ovs_iface, FALSE);
-
-	return strcmp (nm_setting_ovs_interface_get_interface_type (s_ovs_iface), "internal") == 0;
-}
-
-static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (device);
+	NMSettingOvsInterface *s_ovs_iface;
 
-	if (!_is_internal_interface (device))
-		return NM_ACT_STAGE_RETURN_IP_FAIL;
+	s_ovs_iface = nm_device_get_applied_setting (device, NM_TYPE_SETTING_OVS_INTERFACE);
 
-	if (!nm_device_get_ip_ifindex (device)) {
-		priv->waiting_for_interface = TRUE;
-		return NM_ACT_STAGE_RETURN_POSTPONE;
-	}
+	g_return_val_if_fail (s_ovs_iface, FALSE);
 
-	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
+	return nm_streq (nm_setting_ovs_interface_get_interface_type (s_ovs_iface), "internal");
 }
 
 static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (device);
 
 	if (!_is_internal_interface (device))
 		return NM_ACT_STAGE_RETURN_IP_FAIL;
 
-	if (!nm_device_get_ip_ifindex (device)) {
+	if (nm_device_get_ip_ifindex (device) <= 0) {
 		priv->waiting_for_interface = TRUE;
 		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
-	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static gboolean
@@ -206,7 +190,6 @@ nm_device_ovs_interface_class_init (NMDeviceOvsInterfaceClass *klass)
 	device_class->is_available = is_available;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->link_changed = link_changed;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->can_unmanaged_external_down = can_unmanaged_external_down;
 }
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/devices/ovs/nm-device-ovs-port.c
index 1f9afbab..b96eba68 100644
--- a/src/devices/ovs/nm-device-ovs-port.c
+++ b/src/devices/ovs/nm-device-ovs-port.c
@@ -71,17 +71,10 @@ get_generic_capabilities (NMDevice *device)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	return NM_ACT_STAGE_RETURN_IP_FAIL;
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	return NM_ACT_STAGE_RETURN_IP_FAIL;
 }
@@ -186,8 +179,7 @@ nm_device_ovs_port_class_init (NMDeviceOvsPortClass *klass)
 	device_class->get_type_description = get_type_description;
 	device_class->create_and_realize = create_and_realize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->enslave_slave = enslave_slave;
 	device_class->release_slave = release_slave;
 }
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index 494e2ea0..9d73c3ac 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -21,7 +21,6 @@
 
 #include "nm-ovsdb.h"
 
-#include <string.h>
 #include <gmodule.h>
 #include <gio/gunixsocketaddress.h>
 
@@ -1118,7 +1117,7 @@ ovsdb_got_msg (NMOvsdb *self, json_t *msg)
 			ovsdb_disconnect (self, FALSE);
 			return;
 		}
-		/* Cool, we found a corresponsing call. Finish it. */
+		/* Cool, we found a corresponding call. Finish it. */
 
 		_call_trace ("response", call, msg);
 
diff --git a/src/devices/team/meson.build b/src/devices/team/meson.build
index 0f0763bd..3f755012 100644
--- a/src/devices/team/meson.build
+++ b/src/devices/team/meson.build
@@ -1,12 +1,12 @@
 sources = files(
   'nm-device-team.c',
-  'nm-team-factory.c'
+  'nm-team-factory.c',
 )
 
 deps = [
   jansson_dep,
   libteamdctl_dep,
-  nm_dep
+  nm_dep,
 ]
 
 libnm_device_plugin_team = shared_module(
@@ -16,7 +16,7 @@ libnm_device_plugin_team = shared_module(
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_team
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index 899932fd..38a6dd8c 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -34,6 +34,7 @@
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
 #include "platform/nm-platform.h"
+#include "nm-config.h"
 #include "nm-core-internal.h"
 #include "nm-ip4-config.h"
 #include "nm-dbus-compat.h"
@@ -479,11 +480,25 @@ teamd_child_setup (gpointer user_data)
 	signal (SIGPIPE, SIG_IGN);
 }
 
+static const char **
+teamd_env (void)
+{
+	const char **env = g_new0 (const char *, 2);
+
+	if (nm_config_get_is_debug (nm_config_get ()))
+		env[0] = "TEAM_LOG_OUTPUT=stderr";
+	else
+		env[0] = "TEAM_LOG_OUTPUT=syslog";
+
+	return env;
+}
+
 static gboolean
 teamd_kill (NMDeviceTeam *self, const char *teamd_binary, GError **error)
 {
 	gs_unref_ptrarray GPtrArray *argv = NULL;
 	gs_free char *tmp_str = NULL;
+	gs_free const char **envp = NULL;
 
 	if (!teamd_binary) {
 		teamd_binary = nm_utils_find_helper ("teamd", NULL, error);
@@ -500,8 +515,11 @@ teamd_kill (NMDeviceTeam *self, const char *teamd_binary, GError **error)
 	g_ptr_array_add (argv, (gpointer) nm_device_get_iface (NM_DEVICE (self)));
 	g_ptr_array_add (argv, NULL);
 
+	envp = teamd_env ();
+
 	_LOGD (LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv (" ", (char **) argv->pdata)));
-	return g_spawn_sync ("/", (char **) argv->pdata, NULL, 0, teamd_child_setup, NULL, NULL, NULL, NULL, error);
+	return g_spawn_sync ("/", (char **) argv->pdata, (char **) envp, 0,
+	                     teamd_child_setup, NULL, NULL, NULL, NULL, error);
 }
 
 static gboolean
@@ -518,6 +536,7 @@ teamd_start (NMDevice *device, NMConnection *connection)
 	nm_auto_free const char *config_free = NULL;
 	NMSettingTeam *s_team;
 	gs_free char *cloned_mac = NULL;
+	gs_free const char **envp = NULL;
 
 	s_team = nm_connection_get_setting_team (connection);
 	g_return_val_if_fail (s_team, FALSE);
@@ -588,8 +607,10 @@ teamd_start (NMDevice *device, NMConnection *connection)
 		g_ptr_array_add (argv, (gpointer) "-gg");
 	g_ptr_array_add (argv, NULL);
 
+	envp = teamd_env ();
+
 	_LOGD (LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv (" ", (char **) argv->pdata)));
-	if (!g_spawn_async ("/", (char **) argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
+	if (!g_spawn_async ("/", (char **) argv->pdata, (char **) envp, G_SPAWN_DO_NOT_REAP_CHILD,
 	                    teamd_child_setup, NULL, &priv->teamd_pid, &error)) {
 		_LOGW (LOGD_TEAM, "Activation: (team) failed to start teamd: %s", error->message);
 		teamd_cleanup (device, TRUE);
@@ -626,6 +647,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	connection = nm_device_get_applied_connection (device);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
 	s_team = nm_connection_get_setting_team (connection);
 	g_return_val_if_fail (s_team, NM_ACT_STAGE_RETURN_FAILURE);
 
@@ -782,15 +804,15 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
+	int r;
 
-	plerr = nm_platform_link_team_add (nm_device_get_platform (device), iface, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_team_add (nm_device_get_platform (device), iface, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create team master interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/team/nm-team-factory.c b/src/devices/team/nm-team-factory.c
index d062429f..f18b943d 100644
--- a/src/devices/team/nm-team-factory.c
+++ b/src/devices/team/nm-team-factory.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <gmodule.h>
 
 #include "nm-manager.h"
diff --git a/src/devices/tests/meson.build b/src/devices/tests/meson.build
index 02c61ced..4702c656 100644
--- a/src/devices/tests/meson.build
+++ b/src/devices/tests/meson.build
@@ -1,18 +1,19 @@
 test_units = [
   'test-acd',
-  'test-lldp'
+  'test-lldp',
 ]
 
 foreach test_unit: test_units
   exe = executable(
     test_unit,
     test_unit + '.c',
-    dependencies: test_nm_dep
+    dependencies: test_nm_dep,
   )
 
   test(
     'devices/' + test_unit,
     test_script,
-    args: test_args + [exe.full_path()]
+    args: test_args + [exe.full_path()],
+    timeout: default_test_timeout,
   )
 endforeach
diff --git a/src/devices/tests/test-acd.c b/src/devices/tests/test-acd.c
index 8a2852a2..aff71825 100644
--- a/src/devices/tests/test-acd.c
+++ b/src/devices/tests/test-acd.c
@@ -20,6 +20,8 @@
 
 #include "nm-default.h"
 
+#include "n-acd/src/n-acd.h"
+
 #include "devices/nm-acd-manager.h"
 #include "platform/tests/test-common.h"
 
@@ -31,6 +33,46 @@
 #define ADDR3 0x03030303
 #define ADDR4 0x04040404
 
+/*****************************************************************************/
+
+static gboolean
+_skip_acd_test_check (void)
+{
+	NAcd *acd;
+	NAcdConfig *config;
+	const guint8 hwaddr[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+	int r;
+	static int skip = -1;
+
+	if (skip == -1) {
+		r = n_acd_config_new (&config);
+		g_assert (r == 0);
+
+		n_acd_config_set_ifindex (config, 1);
+		n_acd_config_set_transport (config, N_ACD_TRANSPORT_ETHERNET);
+		n_acd_config_set_mac (config, hwaddr, sizeof (hwaddr));
+
+		r = n_acd_new (&acd, config);
+		n_acd_config_free (config);
+		if (r == 0)
+			n_acd_unref (acd);
+
+		skip = (r != 0);
+	}
+	return skip;
+}
+
+#define _skip_acd_test() \
+	({ \
+		gboolean _skip = _skip_acd_test_check (); \
+		\
+		if (_skip) \
+			g_test_skip ("Cannot create NAcd. Running under valgind?"); \
+		_skip; \
+	})
+
+/*****************************************************************************/
+
 typedef struct {
 	int ifindex0;
 	int ifindex1;
@@ -61,20 +103,26 @@ typedef struct {
 } TestInfo;
 
 static void
-acd_manager_probe_terminated (NMAcdManager *acd_manager, GMainLoop *loop)
+acd_manager_probe_terminated (NMAcdManager *acd_manager, gpointer user_data)
 {
-	g_main_loop_quit (loop);
+	g_main_loop_quit (user_data);
 }
 
 static void
 test_acd_common (test_fixture *fixture, TestInfo *info)
 {
-	gs_unref_object NMAcdManager *manager = NULL;
+	NMAcdManager *manager;
 	GMainLoop *loop;
 	int i;
 	const guint WAIT_TIME_OPTIMISTIC = 50;
 	guint wait_time;
-	gulong signal_id;
+	static const NMAcdCallbacks callbacks = {
+		.probe_terminated_callback = acd_manager_probe_terminated,
+		.user_data_destroy         = (GDestroyNotify) g_main_loop_unref,
+	};
+
+	if (_skip_acd_test ())
+		return;
 
 	/* 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
@@ -83,7 +131,13 @@ test_acd_common (test_fixture *fixture, TestInfo *info)
 	wait_time = WAIT_TIME_OPTIMISTIC;
 again:
 
-	manager = nm_acd_manager_new (fixture->ifindex0, fixture->hwaddr0, fixture->hwaddr0_len);
+	loop = g_main_loop_new (NULL, FALSE);
+
+	manager = nm_acd_manager_new (fixture->ifindex0,
+	                              fixture->hwaddr0,
+	                              fixture->hwaddr0_len,
+	                              &callbacks,
+	                              g_main_loop_ref (loop));
 	g_assert (manager != NULL);
 
 	for (i = 0; info->addresses[i]; i++)
@@ -94,16 +148,13 @@ again:
 		                        24, 0, 3600, 1800, 0, NULL);
 	}
 
-	loop = g_main_loop_new (NULL, FALSE);
-	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++) {
 		gboolean val;
+		char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 		val = nm_acd_manager_check_address (manager, info->addresses[i]);
 		if (val == info->expected_result[i])
@@ -113,14 +164,16 @@ again:
 			/* 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);
+			nm_clear_pointer (&manager, nm_acd_manager_free);
 			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),
+		         i, nm_utils_inet4_ntop (info->addresses[i], sbuf),
 		         info->expected_result[i] ? "detect no duplicated" : "detect a duplicate");
 	}
+
+	nm_acd_manager_free (manager);
 }
 
 static void
@@ -146,10 +199,17 @@ test_acd_probe_2 (test_fixture *fixture, gconstpointer user_data)
 static void
 test_acd_announce (test_fixture *fixture, gconstpointer user_data)
 {
-	gs_unref_object NMAcdManager *manager = NULL;
+	NMAcdManager *manager;
 	GMainLoop *loop;
 
-	manager = nm_acd_manager_new (fixture->ifindex0, fixture->hwaddr0, fixture->hwaddr0_len);
+	if (_skip_acd_test ())
+		return;
+
+	manager = nm_acd_manager_new (fixture->ifindex0,
+	                              fixture->hwaddr0,
+	                              fixture->hwaddr0_len,
+	                              NULL,
+	                              NULL);
 	g_assert (manager != NULL);
 
 	g_assert (nm_acd_manager_add_address (manager, ADDR1));
@@ -159,6 +219,8 @@ test_acd_announce (test_fixture *fixture, gconstpointer user_data)
 	nm_acd_manager_announce_addresses (manager);
 	g_assert (!nmtst_main_loop_run (loop, 200));
 	g_main_loop_unref (loop);
+
+	nm_acd_manager_free (manager);
 }
 
 static void
diff --git a/src/devices/wifi/meson.build b/src/devices/wifi/meson.build
index 2745040b..4dfbe4c8 100644
--- a/src/devices/wifi/meson.build
+++ b/src/devices/wifi/meson.build
@@ -1,13 +1,15 @@
 common_sources = files(
   'nm-wifi-ap.c',
-  'nm-wifi-utils.c'
+  'nm-wifi-p2p-peer.c',
+  'nm-wifi-utils.c',
 )
 
 sources = common_sources + files(
-  'nm-wifi-factory.c',
-  'nm-wifi-common.c',
+  'nm-device-olpc-mesh.c',
+  'nm-device-wifi-p2p.c',
   'nm-device-wifi.c',
-  'nm-device-olpc-mesh.c'
+  'nm-wifi-common.c',
+  'nm-wifi-factory.c',
 )
 
 if enable_iwd
@@ -18,7 +20,7 @@ if enable_iwd
 endif
 
 deps = [
-  nm_dep
+  nm_dep,
 ]
 
 libnm_device_plugin_wifi = shared_module(
@@ -28,7 +30,7 @@ libnm_device_plugin_wifi = shared_module(
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_wifi
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index 1d1be742..dcc161d2 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -22,8 +22,6 @@
 
 #include "nm-device-iwd.h"
 
-#include <string.h>
-
 #include "nm-common-macros.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-private.h"
@@ -41,6 +39,7 @@
 #include "nm-config.h"
 #include "nm-iwd-manager.h"
 #include "nm-dbus-manager.h"
+#include "nm-dbus-compat.h"
 
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceIwd);
@@ -69,6 +68,8 @@ typedef struct {
 	GDBusObject *   dbus_obj;
 	GDBusProxy *    dbus_device_proxy;
 	GDBusProxy *    dbus_station_proxy;
+	GDBusProxy *    dbus_ap_proxy;
+	GDBusProxy *    dbus_adhoc_proxy;
 	CList           aps_lst_head;
 	NMWifiAP *      current_ap;
 	GCancellable *  cancellable;
@@ -80,6 +81,7 @@ typedef struct {
 	bool            can_connect:1;
 	bool            scanning:1;
 	bool            scan_requested:1;
+	bool            act_mode_switch:1;
 	gint64          last_scan;
 } NMDeviceIwdPrivate;
 
@@ -104,7 +106,7 @@ G_DEFINE_TYPE (NMDeviceIwd, nm_device_iwd, NM_TYPE_DEVICE)
 /*****************************************************************************/
 
 static void schedule_periodic_scan (NMDeviceIwd *self,
-                                    NMDeviceState current_state);
+                                    gboolean initial_scan);
 
 /*****************************************************************************/
 
@@ -229,7 +231,11 @@ vardict_from_network_type (const char *type)
 }
 
 static void
-insert_ap_from_network (GHashTable *aps, const char *path, int16_t signal, uint32_t ap_id)
+insert_ap_from_network (NMDeviceIwd *self,
+                        GHashTable *aps,
+                        const char *path,
+                        int16_t signal,
+                        uint32_t ap_id)
 {
 	gs_unref_object GDBusProxy *network_proxy = NULL;
 	gs_unref_variant GVariant *name_value = NULL, *type_value = NULL;
@@ -240,6 +246,11 @@ insert_ap_from_network (GHashTable *aps, const char *path, int16_t signal, uint3
 	uint8_t bssid[6];
 	NMWifiAP *ap;
 
+	if (g_hash_table_lookup (aps, path)) {
+		_LOGD (LOGD_WIFI, "Duplicate network at %s", path);
+		return;
+	}
+
 	network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
 	                                                   path,
 	                                                   NM_IWD_NETWORK_INTERFACE);
@@ -304,7 +315,7 @@ 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);
+	NMDeviceIwdPrivate *priv;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *variant = NULL;
 	GVariantIter *networks;
@@ -314,33 +325,42 @@ get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	gboolean changed = FALSE;
 	GHashTableIter ap_iter;
 	gs_unref_hashtable GHashTable *new_aps = NULL;
-	/* Depending on whether we're using the Station interface or the Device
-	 * interface for compatibility with IWD <= 0.7, the return signature of
-	 * GetOrderedNetworks will be different.
-	 */
-	gboolean compat = priv->dbus_station_proxy == priv->dbus_device_proxy;
-	const char *return_sig = compat ? "(a(osns))" : "(a(on))";
+	gboolean compat;
+	const char *return_sig;
 	static uint32_t ap_id = 0;
 
-	variant = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
-	                                      G_VARIANT_TYPE (return_sig),
-	                                      &error);
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
 	if (!variant) {
 		_LOGE (LOGD_WIFI, "Station.GetOrderedNetworks failed: %s",
 		       error->message);
 		return;
 	}
 
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	/* Depending on whether we're using the Station interface or the Device
+	 * interface for compatibility with IWD <= 0.7, the return signature of
+	 * GetOrderedNetworks will be different.
+	 */
+	compat = priv->dbus_station_proxy == priv->dbus_device_proxy;
+	return_sig = compat ? "(a(osns))" : "(a(on))";
+
+	if (!g_variant_is_of_type (variant, G_VARIANT_TYPE (return_sig))) {
+		_LOGE (LOGD_WIFI, "Station.GetOrderedNetworks returned type %s instead of %s",
+		       g_variant_get_type_string (variant), return_sig);
+		return;
+	}
+
 	new_aps = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_object_unref);
 
 	g_variant_get (variant, return_sig, &networks);
 
 	if (compat) {
 		while (g_variant_iter_next (networks, "(&o&sn&s)", &path, &name, &signal, &type))
-			insert_ap_from_network (new_aps, path, signal, ap_id++);
+			insert_ap_from_network (self, new_aps, path, signal, ap_id++);
 	} else {
 		while (g_variant_iter_next (networks, "(&on)", &path, &signal))
-			insert_ap_from_network (new_aps, path, signal, ap_id++);
+			insert_ap_from_network (self, new_aps, path, signal, ap_id++);
 	}
 
 	g_variant_iter_free (networks);
@@ -393,7 +413,7 @@ update_aps (NMDeviceIwd *self)
 		priv->cancellable = g_cancellable_new ();
 
 	g_dbus_proxy_call (priv->dbus_station_proxy, "GetOrderedNetworks",
-	                   g_variant_new ("()"), G_DBUS_CALL_FLAGS_NONE,
+	                   NULL, G_DBUS_CALL_FLAGS_NONE,
 	                   2000, priv->cancellable,
 	                   get_ordered_networks_cb, self);
 }
@@ -403,8 +423,8 @@ send_disconnect (NMDeviceIwd *self)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
-	g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect", g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+	g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect",
+	                   NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 }
 
 static void
@@ -431,55 +451,104 @@ cleanup_association_attempt (NMDeviceIwd *self, gboolean disconnect)
 }
 
 static void
+reset_mode (NMDeviceIwd *self,
+            GCancellable *cancellable,
+            GAsyncReadyCallback callback,
+            gpointer user_data)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	g_dbus_proxy_call (priv->dbus_device_proxy,
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
+	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
+	                                  "Mode",
+	                                  g_variant_new_string ("station")),
+	                   G_DBUS_CALL_FLAGS_NONE, 2000,
+	                   cancellable,
+	                   callback,
+	                   user_data);
+}
+
+static void
 deactivate (NMDevice *device)
 {
-	cleanup_association_attempt (NM_DEVICE_IWD (device), TRUE);
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (!priv->dbus_obj)
+		return;
+
+	cleanup_association_attempt (self, TRUE);
+	priv->act_mode_switch = FALSE;
+
+	if (!priv->dbus_station_proxy)
+		reset_mode (self, NULL, NULL, NULL);
 }
 
-static gboolean
-deactivate_async_finish (NMDevice *device, GAsyncResult *res, GError **error)
+static void
+disconnect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 {
-	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (NM_DEVICE_IWD (device));
+	gs_unref_object NMDeviceIwd *self = NULL;
+	NMDeviceDeactivateCallback callback;
+	gpointer callback_user_data;
 	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
 
-	variant = g_dbus_proxy_call_finish (priv->dbus_station_proxy, res, error);
-	return variant != NULL;
-}
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
 
-typedef struct {
-	NMDeviceIwd *self;
-	GAsyncReadyCallback callback;
-	gpointer user_data;
-} DeactivateContext;
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	callback (NM_DEVICE (self), error, callback_user_data);
+}
 
 static void
-disconnect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+disconnect_cb_on_idle (gpointer user_data,
+                       GCancellable *cancellable)
 {
-	DeactivateContext *ctx = user_data;
+	gs_unref_object NMDeviceIwd *self = NULL;
+	NMDeviceDeactivateCallback callback;
+	gpointer callback_user_data;
+	gs_free_error GError *cancelled_error = NULL;
 
-	ctx->callback (G_OBJECT (ctx->self), res, ctx->user_data);
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
 
-	g_object_unref (ctx->self);
-	g_slice_free (DeactivateContext, ctx);
+	g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error);
+	callback (NM_DEVICE (self), cancelled_error, callback_user_data);
 }
 
 static void
 deactivate_async (NMDevice *device,
                   GCancellable *cancellable,
-                  GAsyncReadyCallback callback,
-                  gpointer user_data)
+                  NMDeviceDeactivateCallback callback,
+                  gpointer callback_user_data)
 {
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
-	DeactivateContext *ctx;
+	gpointer user_data;
 
-	ctx = g_slice_new0 (DeactivateContext);
-	ctx->self = g_object_ref (self);
-	ctx->callback = callback;
-	ctx->user_data = user_data;
+	nm_assert (G_IS_CANCELLABLE (cancellable));
+	nm_assert (callback);
 
-	g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect", g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, -1, cancellable, disconnect_cb, ctx);
+	user_data = nm_utils_user_data_pack (g_object_ref (self), callback, callback_user_data);
+
+	if (!priv->dbus_obj) {
+		nm_utils_invoke_on_idle (disconnect_cb_on_idle, user_data, cancellable);
+		return;
+	}
+
+	cleanup_association_attempt (self, FALSE);
+	priv->act_mode_switch = FALSE;
+
+	if (priv->dbus_station_proxy) {
+		g_dbus_proxy_call (priv->dbus_station_proxy,
+		                   "Disconnect",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   cancellable,
+		                   disconnect_cb,
+		                   user_data);
+	} else
+		reset_mode (self, cancellable, disconnect_cb, user_data);
 }
 
 static gboolean
@@ -510,13 +579,37 @@ is_connection_known_network (NMConnection *connection)
 }
 
 static gboolean
+is_ap_known_network (NMWifiAP *ap)
+{
+	GDBusProxy *network_proxy;
+	gs_unref_variant GVariant *known_network = NULL;
+
+	network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
+	                                                   nm_wifi_ap_get_supplicant_path (ap),
+	                                                   NM_IWD_NETWORK_INTERFACE);
+	if (!network_proxy)
+		return FALSE;
+
+	known_network = g_dbus_proxy_get_cached_property (network_proxy, "KnownNetwork");
+	g_object_unref (network_proxy);
+
+	return    known_network
+	       && g_variant_is_of_type (known_network, G_VARIANT_TYPE_OBJECT_PATH);
+}
+
+static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMSettingWireless *s_wireless;
 	const char *mac;
 	const char * const *mac_blacklist;
 	int i;
 	const char *perm_hw_addr;
+	const char *mode;
+	NMIwdNetworkSecurity security;
+	gboolean mapped;
 
 	if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
@@ -549,23 +642,65 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 		return FALSE;
 	}
 
-	if (!NM_IN_STRSET (nm_setting_wireless_get_mode (s_wireless),
-	                   NULL,
-	                   NM_SETTING_WIRELESS_MODE_INFRA)) {
+	/* Hidden SSIDs not supported in any mode (client or AP) */
+	if (nm_setting_wireless_get_hidden (s_wireless)) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "IWD only support infrastructure type profiles");
+		                            "hidden networks not supported by the IWD backend");
 		return FALSE;
 	}
 
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection)) {
+	security = nm_wifi_connection_get_iwd_security (connection, &mapped);
+	if (!mapped) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "connection authentication type not supported by IWD backend");
+		return FALSE;
+	}
+
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		/* 8021x networks can only be used if they've been provisioned on the IWD side and
+		 * thus are Known Networks.
+		 */
+		if (security == NM_IWD_NETWORK_SECURITY_8021X) {
+			if (!is_connection_known_network (connection)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "802.1x connections must have IWD provisioning files");
+				return FALSE;
+			}
+		} else if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_NONE, NM_IWD_NETWORK_SECURITY_PSK)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "IWD backend only supports Open, PSK and 802.1x network "
+			                            "authentication in Infrastructure mode");
+			return FALSE;
+		}
+	} else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+		if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "device does not support Access Point mode");
+			return FALSE;
+		}
+
+		if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_PSK)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "IWD backend only supports PSK authentication in AP mode");
+			return FALSE;
+		}
+	} else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+		if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "device does not support Ad-Hoc mode");
+			return FALSE;
+		}
+
+		if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_NONE, NM_IWD_NETWORK_SECURITY_PSK)) {
 			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-			                            "802.1x profile is not a known network");
+			                            "IWD backend only supports Open and PSK authentication in Ad-Hoc mode");
 			return FALSE;
 		}
+	} else {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "%s type profiles not supported by IWD backend");
+		return FALSE;
 	}
 
 	return TRUE;
@@ -582,42 +717,15 @@ check_connection_available (NMDevice *device,
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
 	const char *mode;
+	NMWifiAP *ap = NULL;
 
 	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 (!NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "iwd only supports infrastructure mode connections");
-		return FALSE;
-	}
-
-	/* Hidden SSIDs not supported yet */
-	if (nm_setting_wireless_get_hidden (s_wifi)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "hidden networks not supported by iwd");
-		return FALSE;
-	}
-
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection)) {
-			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-			                            "network is not known to iwd");
-			return FALSE;
-		}
-	}
-
 	/* a connection that is available for a certain @specific_object, MUST
 	 * 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);
 		if (!ap) {
 			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
@@ -629,18 +737,36 @@ check_connection_available (NMDevice *device,
 			                            "requested access point is not compatible with profile");
 			return FALSE;
 		}
-		return TRUE;
 	}
 
+	/* AP and Ad-Hoc connections can be activated independent of the scan list */
+	mode = nm_setting_wireless_get_mode (s_wifi);
+	if (NM_IN_STRSET (mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC))
+		return TRUE;
+
 	if (NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP))
 		return TRUE;
 
-	if (!nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection)) {
+	if (!ap)
+		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+
+	if (!ap) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
 		                            "no compatible access point found");
 		return FALSE;
 	}
 
+	/* 8021x networks can only be used if they've been provisioned on the IWD side and
+	 * thus are Known Networks.
+	 */
+	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_ap_known_network (ap)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "802.1x network is not an IWD Known Network (missing provisioning file?)");
+			return FALSE;
+		}
+	}
+
 	return TRUE;
 }
 
@@ -666,15 +792,11 @@ complete_connection (NMDevice *device,
 
 	mode = s_wifi ? nm_setting_wireless_get_mode (s_wifi) : NULL;
 
-	if (mode && !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 (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+		if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+			return FALSE;
+		ap = NULL;
+	} else if (!specific_object) {
 		/* If not given a specific object, we need at minimum an SSID */
 		if (!s_wifi) {
 			g_set_error_literal (error,
@@ -696,11 +818,16 @@ complete_connection (NMDevice *device,
 		/* 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;
+			if (!nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+				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;
+			}
+
+			if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+				return FALSE;
 		}
 	} else {
 		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
@@ -720,7 +847,10 @@ complete_connection (NMDevice *device,
 		nm_connection_add_setting (connection, NM_SETTING (s_wifi));
 	}
 
-	ssid = nm_wifi_ap_get_ssid (ap);
+	ssid = nm_setting_wireless_get_ssid (s_wifi);
+	if (!ssid && ap)
+		ssid = nm_wifi_ap_get_ssid (ap);
+
 	if (!ssid) {
 		g_set_error_literal (error,
 		                     NM_DEVICE_ERROR,
@@ -729,11 +859,13 @@ complete_connection (NMDevice *device,
 		return FALSE;
 	}
 
-	if (!nm_wifi_ap_complete_connection (ap,
-	                                     connection,
-	                                     nm_wifi_utils_is_manf_default_ssid (ssid),
-	                                     error))
-		return FALSE;
+	if (ap) {
+		if (!nm_wifi_ap_complete_connection (ap,
+		                                     connection,
+		                                     nm_wifi_utils_is_manf_default_ssid (ssid),
+		                                     error))
+			return FALSE;
+	}
 
 	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
 	nm_utils_complete_generic (nm_device_get_platform (device),
@@ -745,19 +877,6 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           TRUE);
 
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection)) {
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     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);
@@ -820,8 +939,21 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
-
-	return priv->enabled && priv->dbus_station_proxy;
+	NMDeviceState state = nm_device_get_state (device);
+
+	/* Available if either the device is UP and in station mode
+	 * or in AP/Ad-Hoc modes while activating or activated.  Device
+	 * may be temporarily DOWN while activating or deactivating and
+	 * we don't want it to be marked unavailable because of this.
+	 *
+	 * For reference:
+	 * We call nm_device_queue_recheck_available whenever
+	 * priv->enabled changes or priv->dbus_station_proxy changes.
+	 */
+	return    priv->dbus_obj
+	       && priv->enabled
+	       && (   priv->dbus_station_proxy
+	           || (state >= NM_DEVICE_STATE_CONFIG && state <= NM_DEVICE_STATE_DEACTIVATING));
 }
 
 static gboolean
@@ -829,8 +961,7 @@ 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;
+	return priv->can_connect;
 }
 
 static gboolean
@@ -856,7 +987,9 @@ can_auto_connect (NMDevice *device,
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wifi, FALSE);
 
-	/* Only Infrastrusture mode */
+	/* Don't auto-activate AP or Ad-Hoc connections.
+	 * Note the wpa_supplicant backend has the opposite policy.
+	 */
 	mode = nm_setting_wireless_get_mode (s_wifi);
 	if (mode && g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
 		return FALSE;
@@ -870,14 +1003,6 @@ can_auto_connect (NMDevice *device,
 			return FALSE;
 	}
 
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == 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 */
@@ -908,11 +1033,11 @@ scan_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
 	NMDeviceIwdPrivate *priv;
+	gs_unref_variant GVariant *variant = NULL;
 	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))
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant && nm_utils_error_is_cancelled (error, FALSE))
 		return;
 
 	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
@@ -925,11 +1050,8 @@ scan_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	 * 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);
-	}
+	if (error && !priv->scanning)
+		schedule_periodic_scan (self, FALSE);
 }
 
 static void
@@ -958,9 +1080,7 @@ dbus_request_scan_cb (NMDevice *device,
 
 	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
-	if (   !priv->can_scan
-	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
-	    || nm_device_is_activating (device)) {
+	if (!priv->can_scan) {
 		g_dbus_method_invocation_return_error_literal (context,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -982,8 +1102,7 @@ dbus_request_scan_cb (NMDevice *device,
 
 	if (!priv->scanning && !priv->scan_requested) {
 		g_dbus_proxy_call (priv->dbus_station_proxy, "Scan",
-		                   g_variant_new ("()"),
-		                   G_DBUS_CALL_FLAGS_NONE, -1,
+		                   NULL, G_DBUS_CALL_FLAGS_NONE, -1,
 		                   priv->cancellable, scan_cb, self);
 		priv->scan_requested = TRUE;
 	}
@@ -999,9 +1118,7 @@ _nm_device_iwd_request_scan (NMDeviceIwd *self,
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
 
-	if (   !priv->can_scan
-	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
-	    || nm_device_is_activating (device)) {
+	if (!priv->can_scan) {
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -1013,7 +1130,7 @@ _nm_device_iwd_request_scan (NMDeviceIwd *self,
 	                       NM_DEVICE_AUTH_REQUEST,
 	                       invocation,
 	                       NULL,
-	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                       NM_AUTH_PERMISSION_WIFI_SCAN,
 	                       TRUE,
 	                       dbus_request_scan_cb,
 	                       options ? g_variant_ref (options) : NULL);
@@ -1041,8 +1158,6 @@ scanning_prohibited (NMDeviceIwd *self, gboolean periodic)
 		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;
 	}
@@ -1199,7 +1314,7 @@ wifi_secrets_cb (NMActRequest *req,
 
 	priv->wifi_secrets_id = NULL;
 
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+	if (nm_utils_error_is_cancelled (error, FALSE)) {
 		g_dbus_method_invocation_return_error_literal (invocation, NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                                               "NM secrets request cancelled");
@@ -1263,7 +1378,7 @@ wifi_secrets_get_one (NMDeviceIwd *self,
 	                                                    TRUE,
 	                                                    setting_name,
 	                                                    flags,
-	                                                    setting_key,
+	                                                    NM_MAKE_STRV (setting_key),
 	                                                    wifi_secrets_cb,
 	                                                    nm_utils_user_data_pack (self, invocation));
 }
@@ -1274,6 +1389,7 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	NMDeviceIwd *self = user_data;
 	NMDevice *device = NM_DEVICE (self);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	gs_unref_variant GVariant *variant = NULL;
 	gs_free_error GError *error = NULL;
 	NMConnection *connection;
 	NMSettingWireless *s_wifi;
@@ -1282,9 +1398,8 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED;
 	GVariant *value;
 
-	if (!_nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
-	                                 G_VARIANT_TYPE ("()"),
-	                                 &error)) {
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant) {
 		gs_free char *dbus_error = NULL;
 
 		/* Connection failed; radio problems or if the network wasn't
@@ -1295,7 +1410,7 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 		       "Activation: (wifi) Network.Connect failed: %s",
 		       error->message);
 
-		if (nm_utils_error_is_cancelled (error, TRUE))
+		if (nm_utils_error_is_cancelled (error, FALSE))
 			return;
 
 		if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH))
@@ -1359,12 +1474,262 @@ failed:
 }
 
 static void
+act_failed_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDevice *device = NM_DEVICE (self);
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant && nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	/* Change state to FAILED unless already done by state_changed
+	 * which may have been triggered by the station interface
+	 * appearing on DBus.
+	 */
+	if (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG)
+		nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+}
+
+static void
+act_start_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+	NMSettingWireless *s_wireless;
+	GBytes *ssid;
+	gs_free char *ssid_utf8 = NULL;
+
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant) {
+		_LOGE (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) Network.Connect failed: %s",
+		       error->message);
+
+		if (nm_utils_error_is_cancelled (error, FALSE))
+			return;
+
+		if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG))
+			return;
+
+		goto error;
+	}
+
+	nm_assert (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG);
+
+	s_wireless = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+	if (!s_wireless)
+		goto error;
+
+	ssid = nm_setting_wireless_get_ssid (s_wireless);
+	if (!ssid)
+		goto error;
+
+	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) Stage 2 of 5 (Device Configure) successful.  Started '%s'.",
+	       ssid_utf8);
+
+	nm_device_activate_schedule_stage3_ip_config_start (device);
+	return;
+
+error:
+	reset_mode (self, priv->cancellable, act_failed_cb, self);
+}
+
+/* Check if we're activating an AP/AdHoc connection and if the target
+ * DBus interface has appeared already.  If so proceed to call Start or
+ * StartOpen on that interface.
+ */
+static void
+act_check_interface (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	NMSettingWireless *s_wireless;
+	NMSettingWirelessSecurity *s_wireless_sec;
+	GDBusProxy *proxy = NULL;
+	GBytes *ssid;
+	gs_free char *ssid_utf8 = NULL;
+	const char *mode;
+
+	if (!priv->act_mode_switch)
+		return;
+
+	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP))
+		proxy = priv->dbus_ap_proxy;
+	else if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC))
+		proxy = priv->dbus_adhoc_proxy;
+
+	if (!proxy)
+		return;
+
+	priv->act_mode_switch = FALSE;
+
+	if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG))
+		return;
+
+	ssid = nm_setting_wireless_get_ssid (s_wireless);
+	if (!ssid)
+		goto failed;
+
+	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
+
+	s_wireless_sec = (NMSettingWirelessSecurity *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS_SECURITY);
+
+	if (!s_wireless_sec) {
+		g_dbus_proxy_call (proxy, "StartOpen",
+		                   g_variant_new ("(s)", ssid_utf8),
+		                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+		                   priv->cancellable, act_start_cb, self);
+	} else {
+		const char *psk = nm_setting_wireless_security_get_psk (s_wireless_sec);
+
+		if (!psk) {
+			_LOGE (LOGD_DEVICE | LOGD_WIFI,
+			       "Activation: (wifi) No PSK for '%s'.",
+			       ssid_utf8);
+			goto failed;
+		}
+
+		g_dbus_proxy_call (proxy, "Start",
+		                   g_variant_new ("(ss)", ssid_utf8, psk),
+		                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+		                   priv->cancellable, act_start_cb, self);
+	}
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) Called Start('%s').",
+	       ssid_utf8);
+	return;
+
+failed:
+	reset_mode (self, priv->cancellable, act_failed_cb, self);
+}
+
+static void
+act_set_mode_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant) {
+		_LOGE (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) Setting Device.Mode failed: %s",
+		       error->message);
+
+		if (nm_utils_error_is_cancelled (error, FALSE))
+			return;
+
+		if (   !NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG)
+		    || !priv->act_mode_switch)
+			return;
+
+		priv->act_mode_switch = FALSE;
+		nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return;
+	}
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) IWD Device.Mode set successfully");
+
+	act_check_interface (self);
+}
+
+static void
+act_set_mode (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	const char *iwd_mode;
+	const char *mode;
+	NMSettingWireless *s_wireless;
+
+	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+	mode = nm_setting_wireless_get_mode (s_wireless);
+
+	/* We need to first set interface mode (Device.Mode) to ap or ad-hoc.
+	 * We can't directly queue a call to the Start/StartOpen method on
+	 * the DBus interface that's going to be created after the property
+	 * set call returns.
+	 */
+	iwd_mode = nm_streq (mode, NM_SETTING_WIRELESS_MODE_AP) ? "ap" : "ad-hoc";
+
+	if (!priv->cancellable)
+		priv->cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_call (priv->dbus_device_proxy,
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
+	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
+	                                  "Mode",
+	                                  g_variant_new ("s", iwd_mode)),
+	                   G_DBUS_CALL_FLAGS_NONE, 2000,
+	                   priv->cancellable, act_set_mode_cb, self);
+	priv->act_mode_switch = TRUE;
+}
+
+static void
+act_psk_cb (NMActRequest *req,
+            NMActRequestGetSecretsCallId *call_id,
+            NMSettingsConnection *s_connection,
+            GError *error,
+            gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv;
+	NMDevice *device;
+
+	if (nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	device = NM_DEVICE (self);
+
+	g_return_if_fail (priv->wifi_secrets_id == call_id);
+	priv->wifi_secrets_id = NULL;
+
+	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;
+	}
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) missing PSK request completed");
+
+	/* Change state back to what it was before NEED_AUTH */
+	nm_device_state_changed (device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+	act_set_mode (self);
+	return;
+
+secrets_error:
+	nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS);
+	cleanup_association_attempt (self, FALSE);
+}
+
+static void
 set_powered (NMDeviceIwd *self, gboolean powered)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
 	g_dbus_proxy_call (priv->dbus_device_proxy,
-	                   "org.freedesktop.DBus.Properties.Set",
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
 	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
 	                                  "Powered",
 	                                  g_variant_new ("b", powered)),
@@ -1384,6 +1749,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMActRequest *req;
 	NMConnection *connection;
 	NMSettingWireless *s_wireless;
+	const char *mode;
 	const char *ap_path;
 
 	ret = NM_DEVICE_CLASS (nm_device_iwd_parent_class)->act_stage1_prepare (device, out_failure_reason);
@@ -1399,20 +1765,51 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
 
+	/* AP mode never uses a specific object or existing scanned AP */
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP))
+		goto add_new;
+
 	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);
-		if (!ap) {
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-			return NM_ACT_STAGE_RETURN_FAILURE;
-		}
+	if (ap) {
+		set_current_ap (self, ap, TRUE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
 
+	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_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
+		set_current_ap (self, ap, TRUE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		/* Hidden networks not supported at this time */
+		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
+add_new:
+	/* If the user is trying to connect to an AP that NM doesn't yet know about
+	 * (hidden network or something) or starting a Hotspot, create an fake AP
+	 * from the security settings in the connection.  This "fake" AP gets used
+	 * until the real one is found in the scan list (Ad-Hoc or Hidden), or until
+	 * the device is deactivated (Ad-Hoc or Hotspot).
+	 */
+	ap = nm_wifi_ap_new_fake_from_connection (connection);
+	g_return_val_if_fail (ap != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+	if (nm_wifi_ap_is_hotspot (ap))
+		nm_wifi_ap_set_address (ap, nm_device_get_hw_address (device));
+
+	g_object_freeze_notify (G_OBJECT (self));
+	ap_add_remove (self, TRUE, ap, FALSE);
+	g_object_thaw_notify (G_OBJECT (self));
 	set_current_ap (self, ap, FALSE);
+	nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
+	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
+	g_object_unref (ap);
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
@@ -1423,60 +1820,85 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMActRequest *req;
-	NMWifiAP *ap;
 	NMConnection *connection;
-	GDBusProxy *network_proxy;
+	NMSettingWireless *s_wireless;
+	const char *mode;
 
 	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));
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
 
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-		ret = NM_ACT_STAGE_RETURN_FAILURE;
-		goto out;
-	}
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		GDBusProxy *network_proxy;
+		NMWifiAP *ap = priv->current_ap;
 
-	network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
-	                                                   nm_wifi_ap_get_supplicant_path (ap),
-	                                                   NM_IWD_NETWORK_INTERFACE);
-	if (!network_proxy) {
-		_LOGE (LOGD_DEVICE | LOGD_WIFI,
-		       "Activation: (wifi) could not get Network interface proxy for %s",
-		       nm_wifi_ap_get_supplicant_path (ap));
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		goto out;
-	}
+		if (!ap) {
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+			goto out;
+		}
 
-	if (!priv->cancellable)
-		priv->cancellable = g_cancellable_new ();
+		/* 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 (   nm_connection_get_setting_802_1x (connection)
+		    && !is_ap_known_network (ap)) {
+			_LOGI (LOGD_DEVICE | LOGD_WIFI,
+			       "Activation: (wifi) access point '%s' has 802.1x security but is not configured in IWD.",
+			       nm_connection_get_id (connection));
+
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+			goto out;
+		}
 
-	/* 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);
+		network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
+		                                                   nm_wifi_ap_get_supplicant_path (ap),
+		                                                   NM_IWD_NETWORK_INTERFACE);
+		if (!network_proxy) {
+			_LOGE (LOGD_DEVICE | LOGD_WIFI,
+			       "Activation: (wifi) could not get Network interface proxy for %s",
+			       nm_wifi_ap_get_supplicant_path (ap));
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+			goto out;
+		}
 
-	g_object_unref (network_proxy);
+		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",
+		                   NULL, G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+		                   priv->cancellable, network_connect_cb, self);
+
+		g_object_unref (network_proxy);
+	} else if (NM_IN_STRSET (mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+		NMSettingWirelessSecurity *s_wireless_sec;
+
+		s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
+		if (s_wireless_sec && !nm_setting_wireless_security_get_psk (s_wireless_sec)) {
+			/* PSK is missing from the settings, have to request it */
+
+			wifi_secrets_cancel (self);
+
+			priv->wifi_secrets_id = nm_act_request_get_secrets (req,
+			                                                    TRUE,
+			                                                    NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+			                                                    NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION,
+			                                                    NM_MAKE_STRV (NM_SETTING_WIRELESS_SECURITY_PSK),
+			                                                    act_psk_cb,
+			                                                    self);
+			nm_device_state_changed (device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
+		} else
+			act_set_mode (self);
+	}
 
 	/* We'll get stage3 started when the supplicant connects */
 	ret = NM_ACT_STAGE_RETURN_POSTPONE;
@@ -1507,8 +1929,8 @@ periodic_scan_timeout_cb (gpointer user_data)
 	if (priv->scanning || priv->scan_requested)
 		return FALSE;
 
-	g_dbus_proxy_call (priv->dbus_station_proxy, "Scan", g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, -1,
+	g_dbus_proxy_call (priv->dbus_station_proxy, "Scan",
+	                   NULL, G_DBUS_CALL_FLAGS_NONE, -1,
 	                   priv->cancellable, scan_cb, self);
 	priv->scan_requested = TRUE;
 
@@ -1516,18 +1938,37 @@ periodic_scan_timeout_cb (gpointer user_data)
 }
 
 static void
-schedule_periodic_scan (NMDeviceIwd *self, NMDeviceState current_state)
+schedule_periodic_scan (NMDeviceIwd *self, gboolean initial_scan)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	GVariant *value;
+	gboolean disconnected;
 	guint interval;
 
-	if (current_state <= NM_DEVICE_STATE_UNAVAILABLE)
+	if (!priv->can_scan || priv->scan_requested)
 		return;
 
-	if (current_state == NM_DEVICE_STATE_DISCONNECTED)
-		interval = 10;
+	value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "State");
+	disconnected = nm_streq0 (get_variant_state (value), "disconnected");
+	g_variant_unref (value);
+
+	/* Start scan immediately after a disconnect, mode change or
+	 * device UP, otherwise wait a period dependent on the current
+	 * state.
+	 *
+	 * (initial_scan && disconnected) override priv->scanning below
+	 * because of an IWD quirk where a device will often be in the
+	 * autoconnect state and scanning at the time of our initial_scan,
+	 * but our logic will the send it a Disconnect() causeing IWD to
+	 * exit autoconnect and interrupt the ongoing scan, meaning that
+	 * we still want a new scan ASAP.
+	 */
+	if (initial_scan && disconnected)
+		interval = 0;
+	else if (!priv->periodic_scan_id && !priv->scanning)
+		interval = disconnected ? 10 : 20;
 	else
-		interval = 20;
+		return;
 
 	nm_clear_g_source (&priv->periodic_scan_id);
 	priv->periodic_scan_id = g_timeout_add_seconds (interval,
@@ -1536,6 +1977,19 @@ schedule_periodic_scan (NMDeviceIwd *self, NMDeviceState current_state)
 }
 
 static void
+set_can_scan (NMDeviceIwd *self, gboolean can_scan)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (priv->can_scan == can_scan)
+		return;
+
+	priv->can_scan = can_scan;
+
+	schedule_periodic_scan (self, TRUE);
+}
+
+static void
 device_state_changed (NMDevice *device,
                       NMDeviceState new_state,
                       NMDeviceState old_state,
@@ -1544,14 +1998,6 @@ device_state_changed (NMDevice *device,
 	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;
@@ -1669,10 +2115,13 @@ get_property (GObject *object, guint prop_id,
 
 	switch (prop_id) {
 	case PROP_MODE:
-		if (priv->current_ap)
-			g_value_set_uint (value, NM_802_11_MODE_INFRA);
-		else
+		if (!priv->current_ap)
 			g_value_set_uint (value, NM_802_11_MODE_UNKNOWN);
+		else if (nm_wifi_ap_is_hotspot (priv->current_ap))
+			g_value_set_uint (value, NM_802_11_MODE_AP);
+		else
+			g_value_set_uint (value, nm_wifi_ap_get_mode (priv->current_ap));
+
 		break;
 	case PROP_BITRATE:
 		g_value_set_uint (value, 65000);
@@ -1702,24 +2151,6 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
-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
@@ -1729,7 +2160,7 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 	NMDevice *device = NM_DEVICE (self);
 	NMDeviceState dev_state = nm_device_get_state (device);
 	gboolean iwd_connection = FALSE;
-	gboolean can_connect;
+	gboolean can_connect = priv->can_connect;
 
 	_LOGI (LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state);
 
@@ -1738,7 +2169,9 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 		iwd_connection = TRUE;
 
 	/* Don't allow scanning while connecting, disconnecting or roaming */
-	priv->can_scan = NM_IN_STRSET (new_state, "connected", "disconnected");
+	set_can_scan (self, NM_IN_STRSET (new_state, "connected", "disconnected"));
+
+	priv->can_connect = FALSE;
 
 	if (NM_IN_STRSET (new_state, "connecting", "connected", "roaming")) {
 		/* If we were connecting, do nothing, the confirmation of
@@ -1766,7 +2199,7 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 		 * callback will have more information on the specific failure
 		 * reason.
 		 */
-		if (dev_state == NM_DEVICE_STATE_CONFIG)
+		if (NM_IN_SET (dev_state, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH))
 			return;
 
 		if (iwd_connection)
@@ -1781,10 +2214,10 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 	/* Don't allow new connection until iwd exits disconnecting and no
 	 * Connect callback is pending.
 	 */
-	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, "disconnected")) {
+		priv->can_connect = TRUE;
+		if (!can_connect)
+			nm_device_emit_recheck_auto_activate (device);
 	}
 }
 
@@ -1792,7 +2225,6 @@ 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;
@@ -1805,7 +2237,7 @@ scanning_changed (NMDeviceIwd *self, gboolean new_scanning)
 		update_aps (self);
 
 		if (!priv->scan_requested)
-			schedule_periodic_scan (self, state);
+			schedule_periodic_scan (self, FALSE);
 	}
 }
 
@@ -1814,40 +2246,81 @@ station_properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
                             GStrv invalidate_properties, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
-	GVariantIter *iter;
-	const char *key;
-	GVariant *value;
+	const char *new_str;
+	gboolean new_bool;
 
-	g_variant_get (changed_properties, "a{sv}", &iter);
-	while (g_variant_iter_next (iter, "{&sv}", &key, &value)) {
-		if (!strcmp (key, "State"))
-			state_changed (self, get_variant_state (value));
+	if (g_variant_lookup (changed_properties, "State", "&s", &new_str))
+		state_changed (self, new_str);
 
-		if (!strcmp (key, "Scanning"))
-			scanning_changed (self, get_variant_boolean (value, "Scanning"));
+	if (g_variant_lookup (changed_properties, "Scanning", "b", &new_bool))
+		scanning_changed (self, new_bool);
+}
 
-		g_variant_unref (value);
-	}
+static void
+ap_adhoc_properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
+                             GStrv invalidate_properties, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	gboolean new_bool;
 
-	g_variant_iter_free (iter);
+	if (g_variant_lookup (changed_properties, "Started", "b", &new_bool))
+		_LOGI (LOGD_DEVICE | LOGD_WIFI, "IWD AP/AdHoc state is now %s", new_bool ? "Started" : "Stopped");
 }
 
 static void
 powered_changed (NMDeviceIwd *self, gboolean new_powered)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	GDBusInterface *interface;
+	GVariant *value;
 
 	nm_device_queue_recheck_available (NM_DEVICE (self),
 	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
 	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
 
-	if (new_powered) {
-		GDBusInterface *interface;
-		GVariant *value;
+	interface = new_powered ? g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_AP_INTERFACE) : NULL;
 
-		if (priv->dbus_station_proxy)
-			return;
+	if (priv->dbus_ap_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_ap_proxy,
+		                                      ap_adhoc_properties_changed, self);
+		g_clear_object (&priv->dbus_ap_proxy);
+	}
+
+	if (interface) {
+		priv->dbus_ap_proxy = G_DBUS_PROXY (interface);
+		g_signal_connect (priv->dbus_ap_proxy, "g-properties-changed",
+		                  G_CALLBACK (ap_adhoc_properties_changed), self);
+
+		if (priv->act_mode_switch)
+			act_check_interface (self);
+		else
+			reset_mode (self, NULL, NULL, NULL);
+	}
+
+	interface = new_powered ? g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_ADHOC_INTERFACE) : NULL;
+
+	if (priv->dbus_adhoc_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_adhoc_proxy,
+		                                      ap_adhoc_properties_changed, self);
+		g_clear_object (&priv->dbus_adhoc_proxy);
+	}
+
+	if (interface) {
+		priv->dbus_adhoc_proxy = G_DBUS_PROXY (interface);
+		g_signal_connect (priv->dbus_adhoc_proxy, "g-properties-changed",
+		                  G_CALLBACK (ap_adhoc_properties_changed), self);
 
+		if (priv->act_mode_switch)
+			act_check_interface (self);
+		else
+			reset_mode (self, NULL, NULL, NULL);
+	}
+
+	/* We expect one of the three interfaces to always be present when
+	 * device is Powered so if AP and AdHoc are not present we should
+	 * be in station mode.
+	 */
+	if (new_powered && !priv->dbus_ap_proxy && !priv->dbus_adhoc_proxy) {
 		interface = g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_STATION_INTERFACE);
 		if (!interface) {
 			/* No Station interface on the device object.  Check if the
@@ -1858,18 +2331,30 @@ powered_changed (NMDeviceIwd *self, gboolean new_powered)
 			 * priv->dbus_station_proxy both point at the Device interface.
 			 */
 			value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "State");
-			if (!value) {
+			if (value) {
+				g_variant_unref (value);
+				interface = g_object_ref (G_DBUS_INTERFACE (priv->dbus_device_proxy));
+			} else {
 				_LOGE (LOGD_WIFI, "Interface %s not found on obj %s",
 				       NM_IWD_STATION_INTERFACE,
 				       g_dbus_object_get_object_path (priv->dbus_obj));
-				return;
+				interface = NULL;
 			}
 
-			g_variant_unref (value);
-			interface = g_object_ref (priv->dbus_device_proxy);
 		}
+	} else
+		interface = NULL;
+
+	if (priv->dbus_station_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_station_proxy,
+		                                      station_properties_changed, self);
+		g_clear_object (&priv->dbus_station_proxy);
+	}
 
+	if (interface) {
 		priv->dbus_station_proxy = G_DBUS_PROXY (interface);
+		g_signal_connect (priv->dbus_station_proxy, "g-properties-changed",
+		                  G_CALLBACK (station_properties_changed), self);
 
 		value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "Scanning");
 		priv->scanning = get_variant_boolean (value, "Scanning");
@@ -1879,27 +2364,15 @@ powered_changed (NMDeviceIwd *self, gboolean new_powered)
 		state_changed (self, get_variant_state (value));
 		g_variant_unref (value);
 
-		g_signal_connect (priv->dbus_station_proxy, "g-properties-changed",
-		                  G_CALLBACK (station_properties_changed), self);
-
-		/* 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);
+		update_aps (self);
 	} else {
-		if (!priv->dbus_station_proxy)
-			return;
-
-		g_signal_handlers_disconnect_by_func (priv->dbus_station_proxy,
-		                                      station_properties_changed, self);
-		g_clear_object (&priv->dbus_station_proxy);
-
-		priv->can_scan = FALSE;
+		set_can_scan (self, FALSE);
+		nm_clear_g_source (&priv->periodic_scan_id);
 		priv->scanning = FALSE;
 		priv->scan_requested = FALSE;
 		priv->can_connect = FALSE;
 		cleanup_association_attempt (self, FALSE);
+		remove_all_aps (self);
 	}
 }
 
@@ -1908,19 +2381,10 @@ device_properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
                            GStrv invalidate_properties, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
-	GVariantIter *iter;
-	const char *key;
-	GVariant *value;
-
-	g_variant_get (changed_properties, "a{sv}", &iter);
-	while (g_variant_iter_next (iter, "{&sv}", &key, &value)) {
-		if (!strcmp (key, "Powered"))
-			powered_changed (self, get_variant_boolean (value, "Powered"));
+	gboolean new_bool;
 
-		g_variant_unref (value);
-	}
-
-	g_variant_iter_free (iter);
+	if (g_variant_lookup (changed_properties, "Powered", "b", &new_bool))
+		powered_changed (self, new_bool);
 }
 
 void
@@ -1928,24 +2392,24 @@ nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	GDBusInterface *interface;
-	GVariant *value;
+	gs_unref_variant GVariant *value = NULL;
+	gs_unref_object GDBusProxy *adapter_proxy = NULL;
+	GVariantIter *iter;
+	const char *mode;
 	gboolean powered;
+	NMDeviceWifiCapabilities capabilities;
 
-	if (!nm_g_object_ref_set ((GObject **) &priv->dbus_obj, (GObject *) object))
+	if (!nm_g_object_ref_set (&priv->dbus_obj, object))
 		return;
 
-	if (priv->enabled && priv->dbus_station_proxy) {
-		nm_device_queue_recheck_available (NM_DEVICE (self),
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-	}
-
 	if (priv->dbus_device_proxy) {
 		g_signal_handlers_disconnect_by_func (priv->dbus_device_proxy,
 		                                      device_properties_changed, self);
 		g_clear_object (&priv->dbus_device_proxy);
 
 		powered_changed (self, FALSE);
+
+		priv->act_mode_switch = FALSE;
 	}
 
 	if (!object)
@@ -1965,14 +2429,63 @@ nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
 	g_signal_connect (priv->dbus_device_proxy, "g-properties-changed",
 	                  G_CALLBACK (device_properties_changed), self);
 
+	/* Parse list of interface modes supported by adapter (wiphy) */
+
+	value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "Adapter");
+	if (!value || !g_variant_is_of_type (value, G_VARIANT_TYPE_OBJECT_PATH)) {
+		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+		             "Adapter property not cached or not an object path");
+		goto error;
+	}
+
+	adapter_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
+	                                                   g_variant_get_string (value, NULL),
+	                                                   NM_IWD_WIPHY_INTERFACE);
+	if (!adapter_proxy) {
+		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+		             "Can't get DBus proxy for IWD Adapter for IWD Device");
+		goto error;
+	}
+
+	g_variant_unref (value);
+	value = g_dbus_proxy_get_cached_property (adapter_proxy, "SupportedModes");
+	if (!value || !g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) {
+		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+		             "SupportedModes property not cached or not a string array");
+		goto error;
+	}
+
+	capabilities = NM_WIFI_DEVICE_CAP_CIPHER_CCMP | NM_WIFI_DEVICE_CAP_RSN;
+
+	g_variant_get (value, "as", &iter);
+	while (g_variant_iter_next (iter, "&s", &mode)) {
+		if (nm_streq (mode, "ap"))
+			capabilities |= NM_WIFI_DEVICE_CAP_AP;
+		else if (nm_streq (mode, "ad-hoc"))
+			capabilities |= NM_WIFI_DEVICE_CAP_ADHOC;
+	}
+	g_variant_iter_free (iter);
+
+	if (priv->capabilities != capabilities) {
+		priv->capabilities = capabilities;
+		_notify (self, PROP_CAPABILITIES);
+	}
+
+	g_variant_unref (value);
 	value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "Powered");
 	powered = get_variant_boolean (value, "Powered");
-	g_variant_unref (value);
 
 	if (powered != priv->enabled)
 		set_powered (self, priv->enabled);
 	else if (powered)
 		powered_changed (self, TRUE);
+
+	return;
+
+error:
+	g_signal_handlers_disconnect_by_func (priv->dbus_device_proxy,
+	                                      device_properties_changed, self);
+	g_clear_object (&priv->dbus_device_proxy);
 }
 
 gboolean
@@ -2041,15 +2554,14 @@ nm_device_iwd_init (NMDeviceIwd *self)
 }
 
 NMDevice *
-nm_device_iwd_new (const char *iface, NMDeviceWifiCapabilities capabilities)
+nm_device_iwd_new (const char *iface)
 {
 	return g_object_new (NM_TYPE_DEVICE_IWD,
 	                     NM_DEVICE_IFACE, iface,
-	                     NM_DEVICE_TYPE_DESC, "802.11 WiFi",
+	                     NM_DEVICE_TYPE_DESC, "802.11 Wi-Fi",
 	                     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);
 }
 
@@ -2067,6 +2579,8 @@ dispose (GObject *object)
 
 	g_clear_object (&priv->dbus_device_proxy);
 	g_clear_object (&priv->dbus_station_proxy);
+	g_clear_object (&priv->dbus_ap_proxy);
+	g_clear_object (&priv->dbus_adhoc_proxy);
 	g_clear_object (&priv->dbus_obj);
 
 	remove_all_aps (self);
@@ -2084,7 +2598,6 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	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);
@@ -2108,7 +2621,6 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 	device_class->get_configured_mtu = get_configured_mtu;
 	device_class->deactivate = deactivate;
 	device_class->deactivate_async = deactivate_async;
-	device_class->deactivate_async_finish = deactivate_async_finish;
 	device_class->can_reapply_change = can_reapply_change;
 
 	device_class->state_changed = device_state_changed;
@@ -2144,8 +2656,7 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 	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_READABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_SCANNING] =
diff --git a/src/devices/wifi/nm-device-iwd.h b/src/devices/wifi/nm-device-iwd.h
index 825123b1..4a2bd31e 100644
--- a/src/devices/wifi/nm-device-iwd.h
+++ b/src/devices/wifi/nm-device-iwd.h
@@ -47,7 +47,7 @@ typedef struct _NMDeviceIwdClass NMDeviceIwdClass;
 
 GType nm_device_iwd_get_type (void);
 
-NMDevice *nm_device_iwd_new (const char *iface, NMDeviceWifiCapabilities capabilities);
+NMDevice *nm_device_iwd_new (const char *iface);
 
 void nm_device_iwd_set_dbus_object (NMDeviceIwd *device, GDBusObject *object);
 
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 4c42e7d3..1172a613 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -29,13 +29,11 @@
 #include "nm-device-olpc-mesh.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <signal.h>
 #include <unistd.h>
 #include <sys/ioctl.h>
-#include <errno.h>
 
 #include "devices/nm-device.h"
 #include "nm-device-wifi.h"
@@ -166,7 +164,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		       nm_device_get_iface (priv->companion));
 	}
 
-	/* wait with continuing configuration untill the companion device is done scanning */
+	/* wait with continuing configuration until the companion device is done scanning */
 	g_object_get (priv->companion, NM_DEVICE_WIFI_SCANNING, &scanning, NULL);
 	if (scanning) {
 		priv->stage1_waiting = TRUE;
@@ -193,16 +191,13 @@ static NMActStageReturn
 act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (device);
-	NMConnection *connection;
 	NMSettingOlpcMesh *s_mesh;
 	guint32 channel;
 	GBytes *ssid;
 	const char *anycast_addr;
 
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+	s_mesh = nm_device_get_applied_setting (device, NM_TYPE_SETTING_OLPC_MESH);
 
-	s_mesh = nm_connection_get_setting_olpc_mesh (connection);
 	g_return_val_if_fail (s_mesh, NM_ACT_STAGE_RETURN_FAILURE);
 
 	channel = nm_setting_olpc_mesh_get_channel (s_mesh);
@@ -333,7 +328,7 @@ check_companion (NMDeviceOlpcMesh *self, NMDevice *other)
 	g_assert (priv->companion == NULL);
 	priv->companion = g_object_ref (other);
 
-	_LOGI (LOGD_OLPC, "found companion WiFi device %s",
+	_LOGI (LOGD_OLPC, "found companion Wi-Fi device %s",
 	       nm_device_get_iface (other));
 
 	g_signal_connect (G_OBJECT (other), NM_DEVICE_STATE_CHANGED,
diff --git a/src/devices/wifi/nm-device-wifi-p2p.c b/src/devices/wifi/nm-device-wifi-p2p.c
new file mode 100644
index 00000000..8381ebc7
--- /dev/null
+++ b/src/devices/wifi/nm-device-wifi-p2p.c
@@ -0,0 +1,1316 @@
+/* NetworkManager -- Wi-Fi P2P Device
+ *
+ * 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-device-wifi-p2p.h"
+
+#include <sys/socket.h>
+
+#include "supplicant/nm-supplicant-manager.h"
+#include "supplicant/nm-supplicant-interface.h"
+
+#include "nm-manager.h"
+#include "nm-utils.h"
+#include "nm-wifi-p2p-peer.h"
+#include "NetworkManagerUtils.h"
+#include "devices/nm-device-private.h"
+#include "settings/nm-settings.h"
+#include "nm-setting-wifi-p2p.h"
+#include "nm-act-request.h"
+#include "nm-ip4-config.h"
+#include "platform/nm-platform.h"
+#include "nm-manager.h"
+#include "nm-core-internal.h"
+#include "platform/nmp-object.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceWifiP2P);
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifiP2P,
+	PROP_PEERS,
+);
+
+typedef struct {
+	NMSupplicantManager *sup_mgr;
+
+	/* NOTE: In theory management and group ifaces could be identical. However,
+	 * in practice, this cannot happen currently as NMDeviceWifiP2P is only
+	 * created for existing non-P2P interfaces.
+	 * (i.e. a single standalone P2P interface is not supported at this point)
+	 */
+	NMSupplicantInterface *mgmt_iface;
+	NMSupplicantInterface *group_iface;
+
+	CList peers_lst_head;
+
+	guint sup_timeout_id;
+	guint peer_dump_id;
+	guint peer_missing_id;
+
+	bool is_waiting_for_supplicant:1;
+} NMDeviceWifiP2PPrivate;
+
+struct _NMDeviceWifiP2P {
+	NMDevice parent;
+	NMDeviceWifiP2PPrivate _priv;
+};
+
+struct _NMDeviceWifiP2PClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceWifiP2P, nm_device_wifi_p2p, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_WIFI_P2P_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceWifiP2P, NM_IS_DEVICE_WIFI_P2P, NMDevice)
+
+/*****************************************************************************/
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p;
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added;
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed;
+
+static void supplicant_group_interface_release (NMDeviceWifiP2P *self);
+static void supplicant_interfaces_release (NMDeviceWifiP2P *self, gboolean set_is_waiting);
+
+/*****************************************************************************/
+
+static void
+_peer_dump (NMDeviceWifiP2P *self,
+            NMLogLevel log_level,
+            const NMWifiP2PPeer *peer,
+            const char *prefix,
+            gint32 now_s)
+{
+	char buf[1024];
+
+	_NMLOG (log_level, LOGD_WIFI_SCAN, "wifi-peer: %-7s %s",
+	        prefix,
+	        nm_wifi_p2p_peer_to_string (peer, buf, sizeof (buf), now_s));
+}
+
+static gboolean
+peer_list_dump (gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->peer_dump_id = 0;
+
+	if (_LOGD_ENABLED (LOGD_WIFI_SCAN)) {
+		NMWifiP2PPeer *peer;
+		gint32 now_s = nm_utils_get_monotonic_timestamp_s ();
+
+		_LOGD (LOGD_WIFI_SCAN, "P2P Peers: [now:%u]", now_s);
+		c_list_for_each_entry (peer, &priv->peers_lst_head, peers_lst)
+			_peer_dump (self, LOGL_DEBUG, peer, "dump", now_s);
+	}
+	return G_SOURCE_REMOVE;
+}
+
+static void
+schedule_peer_list_dump (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (   !priv->peer_dump_id
+	    && _LOGD_ENABLED (LOGD_WIFI_SCAN))
+		priv->peer_dump_id = g_timeout_add_seconds (1, peer_list_dump, self);
+}
+
+/*****************************************************************************/
+
+static void
+_set_is_waiting_for_supplicant (NMDeviceWifiP2P *self, gboolean is_waiting)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (priv->is_waiting_for_supplicant == (!!is_waiting))
+		return;
+
+	priv->is_waiting_for_supplicant = is_waiting;
+
+	if (is_waiting)
+		nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+	else
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+}
+
+/*****************************************************************************/
+
+static gboolean
+check_connection_peer_joined (NMDeviceWifiP2P *device)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (device);
+	NMConnection *conn = nm_device_get_applied_connection (NM_DEVICE (device));
+	NMWifiP2PPeer *peer;
+	const char* group;
+	const char * const * groups;
+
+	if (!conn || !priv->group_iface)
+		return FALSE;
+
+	/* Comparing the object path found on the group_iface with the peers
+	 * found on the mgmt_iface is legal. */
+	group = nm_supplicant_interface_get_p2p_group_path (priv->group_iface);
+	if (!group)
+		return FALSE;
+
+	/* NOTE: We currently only support connections to a specific peer */
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, conn);
+	if (!peer)
+		return FALSE;
+
+	groups = nm_wifi_p2p_peer_get_groups (peer);
+	if (   !groups
+	    || !g_strv_contains (groups, group))
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+disconnect_on_connection_peer_missing_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	_LOGW (LOGD_WIFI, "Peer requested in connection is missing for too long, failing connection.");
+
+	priv->peer_missing_id = 0;
+
+	nm_device_state_changed (device,
+	                         NM_DEVICE_STATE_FAILED,
+	                         NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+	return FALSE;
+}
+
+static void
+update_disconnect_on_connection_peer_missing (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMDeviceState state;
+
+	state = nm_device_get_state (NM_DEVICE (self));
+	if (   state < NM_DEVICE_STATE_IP_CONFIG
+	    || state > NM_DEVICE_STATE_ACTIVATED) {
+		nm_clear_g_source (&priv->peer_missing_id);
+		return;
+	}
+
+	if (check_connection_peer_joined (self)) {
+		if (nm_clear_g_source (&priv->peer_missing_id))
+			_LOGD (LOGD_WIFI, "Peer requested in connection is joined, removing timeout");
+		return;
+	}
+
+	if (priv->peer_missing_id == 0) {
+		_LOGD (LOGD_WIFI, "Peer requested in connection is missing, adding timeout");
+		priv->peer_missing_id = g_timeout_add_seconds (5, disconnect_on_connection_peer_missing_cb, self);
+	}
+}
+
+static gboolean
+is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMSupplicantInterfaceState supplicant_state;
+
+	if (!priv->mgmt_iface)
+		return FALSE;
+
+	supplicant_state = nm_supplicant_interface_get_state (priv->mgmt_iface);
+	if (   supplicant_state < NM_SUPPLICANT_INTERFACE_STATE_READY
+	    || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
+{
+	if (!NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->check_connection_compatible (device, connection, error))
+		return FALSE;
+
+	/* TODO: Allow limitting the interface using the HW-address? */
+
+	/* We don't need to check anything else here. The P2P device will only
+	 * exists if we are able to establish a P2P connection, and there should
+	 * be no further restrictions necessary.
+	 */
+
+	return TRUE;
+}
+
+static gboolean
+complete_connection (NMDevice *device,
+                     NMConnection *connection,
+                     const char *specific_object,
+                     NMConnection *const*existing_connections,
+                     GError **error)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	gs_free char *setting_name = NULL;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+	const char *setting_peer;
+
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+
+	if (!specific_object) {
+		/* If not given a specific object, we need at minimum a peer address */
+		if (!s_wifi_p2p) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_INVALID_CONNECTION,
+			             "A '%s' setting is required if no Peer path was given",
+			             NM_SETTING_WIFI_P2P_SETTING_NAME);
+			return FALSE;
+		}
+
+		setting_peer = nm_setting_wifi_p2p_get_peer (s_wifi_p2p);
+		if (!setting_peer) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_INVALID_CONNECTION,
+			             "A '%s' setting with a valid Peer is required if no Peer path was given",
+			             NM_SETTING_WIFI_P2P_SETTING_NAME);
+			return FALSE;
+		}
+
+	} else {
+		peer = nm_wifi_p2p_peer_lookup_for_device (NM_DEVICE (self), specific_object);
+		if (!peer) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND,
+			             "The P2P peer %s is unknown",
+			             specific_object);
+			return FALSE;
+		}
+
+		setting_peer = nm_wifi_p2p_peer_get_address (peer);
+		g_return_val_if_fail (setting_peer, FALSE);
+	}
+
+	/* Add a Wi-Fi P2P setting if one doesn't exist yet */
+	if (!s_wifi_p2p) {
+		s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_setting_wifi_p2p_new ());
+		nm_connection_add_setting (connection, NM_SETTING (s_wifi_p2p));
+	}
+
+	g_object_set (G_OBJECT (s_wifi_p2p), NM_SETTING_WIFI_P2P_PEER, setting_peer, NULL);
+
+	setting_name = g_strdup_printf ("Wi-Fi P2P Peer %s", setting_peer);
+	nm_utils_complete_generic (nm_device_get_platform (device),
+	                           connection,
+	                           NM_SETTING_WIFI_P2P_SETTING_NAME,
+	                           existing_connections,
+	                           setting_name,
+	                           setting_name,
+	                           NULL,
+	                           TRUE);
+
+	return TRUE;
+}
+
+/*
+ * supplicant_find_timeout_cb
+ *
+ * Called when the supplicant has been unable to find the peer we want to connect to.
+ */
+static gboolean
+supplicant_find_timeout_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->sup_timeout_id = 0;
+
+	nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (nm_device_is_activating (device)) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi-p2p) could not find peer, failing activation");
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMActStageReturn ret;
+	NMActRequest *req;
+	NMConnection *connection;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+
+	ret = NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->act_stage1_prepare (device, out_failure_reason);
+	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
+		return ret;
+
+	if (!priv->mgmt_iface) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	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_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	g_return_val_if_fail (s_wifi_p2p, NM_ACT_STAGE_RETURN_FAILURE);
+
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+	if (!peer) {
+		/* Set up a timeout on the find attempt and run a find for the same period of time */
+		priv->sup_timeout_id = g_timeout_add_seconds (10,
+		                                              supplicant_find_timeout_cb,
+		                                              self);
+
+		nm_supplicant_interface_p2p_start_find (priv->mgmt_iface, 10);
+
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+cleanup_p2p_connect_attempt (NMDeviceWifiP2P *self, gboolean disconnect)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+	nm_clear_g_source (&priv->peer_missing_id);
+
+	if (priv->mgmt_iface)
+		nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (disconnect && priv->group_iface)
+		nm_supplicant_interface_p2p_disconnect (priv->group_iface);
+}
+
+/*
+ * supplicant_connection_timeout_cb
+ *
+ * Called when the supplicant has been unable to connect to a peer
+ * within a specified period of time.
+ */
+static gboolean
+supplicant_connection_timeout_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->sup_timeout_id = 0;
+
+	nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (nm_device_is_activating (device)) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi-p2p) connecting took too long, failing activation");
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+	GBytes *wfd_ies;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+
+	if (!priv->mgmt_iface) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	connection = nm_device_get_applied_connection (device);
+	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+	nm_assert (NM_IS_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P)));
+
+	/* The prepare stage ensures that the peer has been found */
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+	if (!peer) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	/* Set the WFD IEs before trying to establish the connection. */
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	wfd_ies = nm_setting_wifi_p2p_get_wfd_ies (s_wifi_p2p);
+	nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, wfd_ies);
+
+	/* TODO: Grab secrets if we don't have them yet! */
+
+	/* TODO: Fix "pbc" being hardcoded here! */
+	nm_supplicant_interface_p2p_connect (priv->mgmt_iface,
+	                                     nm_wifi_p2p_peer_get_supplicant_path (peer),
+	                                     "pbc", NULL);
+
+	/* Set up a timeout on the connect attempt */
+	priv->sup_timeout_id = g_timeout_add_seconds (45,
+	                                              supplicant_connection_timeout_cb,
+	                                              self);
+
+	/* We'll get stage3 started when the P2P group has been started */
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+/*****************************************************************************/
+
+static void
+emit_signal_p2p_peer_add_remove (NMDeviceWifiP2P *device,
+                                 NMWifiP2PPeer *peer,
+                                 gboolean is_added /* or else is_removed */)
+{
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (device),
+	                            &interface_info_device_wifi_p2p,
+	                            is_added
+	                              ? &nm_signal_info_wifi_p2p_peer_added
+	                              : &nm_signal_info_wifi_p2p_peer_removed,
+	                            "(o)",
+	                            nm_dbus_object_get_path (NM_DBUS_OBJECT (peer)));
+}
+
+static void
+peer_add_remove (NMDeviceWifiP2P *self,
+                 gboolean is_adding, /* or else removing */
+                 NMWifiP2PPeer *peer,
+                 gboolean recheck_available_connections)
+{
+	NMDevice *device = NM_DEVICE (self);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (is_adding) {
+		g_object_ref (peer);
+		peer->wifi_device = device;
+		c_list_link_tail (&priv->peers_lst_head, &peer->peers_lst);
+		nm_dbus_object_export (NM_DBUS_OBJECT (peer));
+		_peer_dump (self, LOGL_DEBUG, peer, "added", 0);
+
+		emit_signal_p2p_peer_add_remove (self, peer, TRUE);
+	} else {
+		peer->wifi_device = NULL;
+		c_list_unlink (&peer->peers_lst);
+		_peer_dump (self, LOGL_DEBUG, peer, "removed", 0);
+	}
+
+	_notify (self, PROP_PEERS);
+
+	if (!is_adding) {
+		emit_signal_p2p_peer_add_remove (self, peer, FALSE);
+		nm_dbus_object_clear_and_unexport (&peer);
+	}
+
+	if (is_adding) {
+		/* If we are in prepare state, then we are currently runnign a find
+		 * to search for the requested peer. */
+		if (nm_device_get_state (device) == NM_DEVICE_STATE_PREPARE) {
+			NMConnection *connection;
+
+			connection = nm_device_get_applied_connection (device);
+			g_assert (connection);
+
+			peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+			if (peer) {
+				/* A peer for the connection was found, cancel the timeout and go to configure state. */
+				nm_clear_g_source (&priv->sup_timeout_id);
+				nm_device_activate_schedule_stage2_device_config (device);
+			}
+		}
+
+		/* TODO: We may want to re-check auto-activation here, otherwise it will never work. */
+	}
+
+	update_disconnect_on_connection_peer_missing (self);
+}
+
+static void
+remove_all_peers (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMWifiP2PPeer *peer;
+
+	if (c_list_is_empty (&priv->peers_lst_head))
+		return;
+
+	while ((peer = c_list_first_entry (&priv->peers_lst_head, NMWifiP2PPeer, peers_lst)))
+		peer_add_remove (self, FALSE, peer, FALSE);
+
+	nm_device_recheck_available_connections (NM_DEVICE (self));
+}
+
+/*****************************************************************************/
+
+
+static NMActStageReturn
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
+{
+	gboolean indicate_addressing_running;
+	NMConnection *connection;
+	const char *method;
+
+	connection = nm_device_get_applied_connection (device);
+
+	method = nm_utils_get_ip_config_method (connection, addr_family);
+
+	if (addr_family == AF_INET)
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	else {
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		                                                    NM_SETTING_IP6_CONFIG_METHOD_DHCP);
+	}
+
+	if (indicate_addressing_running)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), TRUE);
+
+	return NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
+}
+
+static void
+deactivate (NMDevice *device)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	int ifindex = nm_device_get_ip_ifindex (device);
+
+	cleanup_p2p_connect_attempt (self, TRUE);
+
+	/* Clear any critical protocol notification in the Wi-Fi stack */
+	if (ifindex > 0)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), ifindex, FALSE);
+}
+
+static guint32
+get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+{
+	*out_source = NM_DEVICE_MTU_SOURCE_NONE;
+	return 0;
+}
+
+static const char *
+get_auto_ip_config_method (NMDevice *device, int addr_family)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	/* Override the AUTO method to mean shared if we are group owner. */
+	if (   priv->group_iface
+	    && nm_supplicant_interface_get_p2p_group_owner (priv->group_iface)) {
+		if (addr_family == AF_INET)
+			return NM_SETTING_IP4_CONFIG_METHOD_SHARED;
+
+		if (addr_family == AF_INET6)
+			return NM_SETTING_IP6_CONFIG_METHOD_SHARED;
+	}
+
+	return NULL;
+}
+
+static gboolean
+unmanaged_on_quit (NMDevice *self)
+{
+	return TRUE;
+}
+
+static void
+supplicant_iface_state_cb (NMSupplicantInterface *iface,
+                           int new_state_i,
+                           int old_state_i,
+                           int disconnect_reason,
+                           gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDevice *device = NM_DEVICE (self);
+	NMSupplicantInterfaceState new_state = new_state_i;
+	NMSupplicantInterfaceState old_state = old_state_i;
+
+	if (new_state == old_state)
+		return;
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "supplicant management interface state: %s -> %s",
+	       nm_supplicant_interface_state_to_string (old_state),
+	       nm_supplicant_interface_state_to_string (new_state));
+
+	switch (new_state) {
+	case NM_SUPPLICANT_INTERFACE_STATE_READY:
+		_LOGD (LOGD_WIFI, "supplicant ready");
+		nm_device_queue_recheck_available (device,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+
+		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, FALSE);
+		break;
+	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+		supplicant_interfaces_release (self, TRUE);
+		nm_device_queue_recheck_available (device,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+supplicant_iface_peer_updated_cb (NMSupplicantInterface *iface,
+                                  const char *object_path,
+                                  GVariant *properties,
+                                  NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+	NMWifiP2PPeer *found_peer;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (object_path != NULL);
+
+	priv  = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	found_peer = nm_wifi_p2p_peers_find_by_supplicant_path (&priv->peers_lst_head, object_path);
+	if (found_peer) {
+		if (!nm_wifi_p2p_peer_update_from_properties (found_peer, object_path, properties))
+			return;
+
+		update_disconnect_on_connection_peer_missing (self);
+		_peer_dump (self, LOGL_DEBUG, found_peer, "updated", 0);
+	} else {
+		gs_unref_object NMWifiP2PPeer *peer = NULL;
+
+		peer = nm_wifi_p2p_peer_new_from_properties (object_path, properties);
+		if (!peer) {
+			_LOGD (LOGD_WIFI, "invalid P2P peer properties received for %s", object_path);
+			return;
+		}
+
+		peer_add_remove (self, TRUE, peer, TRUE);
+	}
+
+	schedule_peer_list_dump (self);
+}
+
+static void
+supplicant_iface_peer_removed_cb (NMSupplicantInterface *iface,
+                                  const char *object_path,
+                                  NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+	NMWifiP2PPeer *peer;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (object_path != NULL);
+
+	priv  = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	peer = nm_wifi_p2p_peers_find_by_supplicant_path (&priv->peers_lst_head, object_path);
+	if (!peer)
+		return;
+
+	peer_add_remove (self, FALSE, peer, TRUE);
+	schedule_peer_list_dump (self);
+}
+
+static void
+check_group_iface_ready (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);;
+
+	if (!priv->group_iface)
+		return;
+
+	if (nm_supplicant_interface_get_state (priv->group_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		return;
+
+	if (!nm_supplicant_interface_get_p2p_group_joined (priv->group_iface))
+		return;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+	update_disconnect_on_connection_peer_missing (self);
+
+	nm_device_activate_schedule_stage3_ip_config_start (NM_DEVICE (self));
+}
+
+static void
+supplicant_group_iface_state_cb (NMSupplicantInterface *iface,
+                                 int new_state_i,
+                                 int old_state_i,
+                                 int disconnect_reason,
+                                 gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	NMSupplicantInterfaceState new_state = new_state_i;
+	NMSupplicantInterfaceState old_state = old_state_i;
+
+	if (new_state == old_state)
+		return;
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "P2P Group supplicant interface state: %s -> %s",
+	       nm_supplicant_interface_state_to_string (old_state),
+	       nm_supplicant_interface_state_to_string (new_state));
+
+	switch (new_state) {
+	case NM_SUPPLICANT_INTERFACE_STATE_READY:
+		_LOGD (LOGD_WIFI, "P2P Group supplicant ready");
+
+		if (!nm_device_set_ip_iface (device, nm_supplicant_interface_get_ifname (priv->group_iface))) {
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+			break;
+		}
+
+		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, FALSE);
+
+		check_group_iface_ready (self);
+		break;
+	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+		supplicant_group_interface_release (self);
+
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_DISCONNECTED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+supplicant_group_iface_group_finished_cb (NMSupplicantInterface *iface,
+                                          void *user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+
+	supplicant_group_interface_release (self);
+
+	nm_device_state_changed (NM_DEVICE (self),
+	                         NM_DEVICE_STATE_DISCONNECTED,
+	                         NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+}
+
+static void
+supplicant_iface_group_joined_updated_cb (NMSupplicantInterface *iface,
+                                          GParamSpec *pspec,
+                                          void *user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+
+	check_group_iface_ready (self);
+}
+
+static void
+supplicant_iface_group_started_cb (NMSupplicantInterface *iface,
+                                   NMSupplicantInterface *group_iface,
+                                   NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+
+	g_return_if_fail (self != NULL);
+
+	if (!nm_device_is_activating (NM_DEVICE (self))) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI, "P2P: WPA supplicant notified a group start but we are not trying to connect! Ignoring the event.");
+		return;
+	}
+
+	priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	supplicant_group_interface_release (self);
+	priv->group_iface = g_object_ref (group_iface);
+
+	/* We need to wait for the interface to be ready and the group
+	 * information to be resolved. */
+	g_signal_connect (priv->group_iface,
+	                  "notify::" NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED,
+	                  G_CALLBACK (supplicant_iface_group_joined_updated_cb),
+	                  self);
+
+	g_signal_connect (priv->group_iface,
+	                  NM_SUPPLICANT_INTERFACE_STATE,
+	                  G_CALLBACK (supplicant_group_iface_state_cb),
+	                  self);
+
+	g_signal_connect (priv->group_iface, NM_SUPPLICANT_INTERFACE_GROUP_FINISHED,
+	                  G_CALLBACK (supplicant_group_iface_group_finished_cb),
+	                  self);
+
+	if (nm_supplicant_interface_get_state (priv->group_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		_set_is_waiting_for_supplicant (self, TRUE);
+
+	check_group_iface_ready (self);
+}
+
+static void
+supplicant_group_interface_release (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (!priv->group_iface)
+		return;
+
+	g_signal_handlers_disconnect_by_data (priv->group_iface, self);
+
+	nm_supplicant_interface_p2p_disconnect (priv->group_iface);
+
+	g_clear_object (&priv->group_iface);
+}
+
+static void
+supplicant_interfaces_release (NMDeviceWifiP2P *self, gboolean set_is_waiting)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->peer_dump_id);
+
+	remove_all_peers (self);
+
+	if (priv->mgmt_iface) {
+		_LOGD (LOGD_DEVICE | LOGD_WIFI, "P2P: Releasing WPA supplicant interface.");
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		g_signal_handlers_disconnect_by_data (priv->mgmt_iface, self);
+		g_clear_object (&priv->mgmt_iface);
+		nm_clear_g_source (&priv->sup_timeout_id);
+	}
+
+	supplicant_group_interface_release (self);
+
+	if (set_is_waiting)
+		_set_is_waiting_for_supplicant (self, TRUE);
+}
+
+static void
+device_state_changed (NMDevice *device,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	update_disconnect_on_connection_peer_missing (self);
+
+	if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) {
+		/* Clean up the supplicant interface because in these states the
+		 * device cannot be used.
+		 * Do not clean up for the UNMANAGED to UNAVAILABLE transition which
+		 * will happen during initialization.
+		 */
+		if (priv->mgmt_iface && old_state > new_state)
+			supplicant_interfaces_release (self, TRUE);
+
+		/* TODO: More cleanup needed? */
+	}
+
+	switch (new_state) {
+	case NM_DEVICE_STATE_UNMANAGED:
+		break;
+	case NM_DEVICE_STATE_UNAVAILABLE:
+		if (   !priv->mgmt_iface
+		    || nm_supplicant_interface_get_state (priv->mgmt_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, TRUE);
+
+		break;
+	case NM_DEVICE_STATE_NEED_AUTH:
+		/* Disconnect? */
+		break;
+	case NM_DEVICE_STATE_IP_CHECK:
+		/* Clear any critical protocol notification in the wifi stack */
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), FALSE);
+		break;
+	case NM_DEVICE_STATE_ACTIVATED:
+		//activation_success_handler (device);
+		break;
+	case NM_DEVICE_STATE_FAILED:
+		/* Clear any critical protocol notification in the wifi stack.
+		 * At this point the IP device may have been removed already. */
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		if (nm_device_get_ip_ifindex (device) > 0)
+			nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), FALSE);
+		break;
+	case NM_DEVICE_STATE_DISCONNECTED:
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+impl_device_wifi_p2p_start_find (NMDBusObject *obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *method_info,
+                                 GDBusConnection *connection,
+                                 const char *sender,
+                                 GDBusMethodInvocation *invocation,
+                                 GVariant *parameters)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (obj);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	gs_unref_variant GVariant *options = NULL;
+	const char *opts_key;
+	GVariant *opts_val;
+	GVariantIter iter;
+	gint32 timeout = 30;
+
+	g_variant_get (parameters, "(@a{sv})", &options);
+
+	g_variant_iter_init (&iter, options);
+	while (g_variant_iter_next (&iter, "{&sv}", &opts_key, &opts_val)) {
+		_nm_unused gs_unref_variant GVariant *opts_val_free = opts_val;
+
+		if (nm_streq (opts_key, "timeout")) {
+			if (!g_variant_is_of_type (opts_val, G_VARIANT_TYPE_INT32)) {
+				g_dbus_method_invocation_return_error_literal (invocation,
+				                                               NM_DEVICE_ERROR,
+				                                               NM_DEVICE_ERROR_INVALID_ARGUMENT,
+				                                               "\"timeout\" must be an integer \"i\"");
+				return;
+			}
+
+			timeout = g_variant_get_int32 (opts_val);
+			if (timeout <= 0 || timeout > 600) {
+				g_dbus_method_invocation_return_error_literal (invocation,
+				                                               NM_DEVICE_ERROR,
+				                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+				                                               "The timeout for a find operation needs to be in the range of 1-600s.");
+				return;
+			}
+
+			continue;
+		}
+
+		g_dbus_method_invocation_return_error (invocation,
+		                                       NM_DEVICE_ERROR,
+		                                       NM_DEVICE_ERROR_INVALID_ARGUMENT,
+		                                       "Unsupported options key \"%s\"",
+		                                       opts_key);
+		return;
+	}
+
+	if (!priv->mgmt_iface) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "WPA Supplicant management interface is currently unavailable.");
+		return;
+	}
+
+	nm_supplicant_interface_p2p_start_find (priv->mgmt_iface, timeout);
+
+	g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
+impl_device_wifi_p2p_stop_find (NMDBusObject *obj,
+                                const NMDBusInterfaceInfoExtended *interface_info,
+                                const NMDBusMethodInfoExtended *method_info,
+                                GDBusConnection *connection,
+                                const char *sender,
+                                GDBusMethodInvocation *invocation,
+                                GVariant *parameters)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (obj);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (!priv->mgmt_iface) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "WPA Supplicant management interface is currently unavailable.");
+		return;
+	}
+
+	nm_supplicant_interface_p2p_stop_find (priv->mgmt_iface);
+
+	g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+/*****************************************************************************/
+
+NMSupplicantInterface *
+nm_device_wifi_p2p_get_mgmt_iface (NMDeviceWifiP2P *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE_WIFI_P2P (self), NULL);
+
+	return NM_DEVICE_WIFI_P2P_GET_PRIVATE (self)->mgmt_iface;
+}
+
+void
+nm_device_wifi_p2p_set_mgmt_iface (NMDeviceWifiP2P *self,
+                                   NMSupplicantInterface *iface)
+{
+	NMDeviceWifiP2PPrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE_WIFI_P2P (self));
+	g_return_if_fail (!iface || NM_IS_SUPPLICANT_INTERFACE (iface));
+
+	priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (priv->mgmt_iface == iface)
+		goto done;
+
+	supplicant_interfaces_release (self, FALSE);
+
+	if (!iface)
+		goto done;
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI, "P2P: WPA supplicant management interface changed to %s.",
+	       nm_supplicant_interface_get_object_path (iface));
+
+	priv->mgmt_iface = g_object_ref (iface);
+
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_STATE,
+	                  G_CALLBACK (supplicant_iface_state_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_PEER_UPDATED,
+	                  G_CALLBACK (supplicant_iface_peer_updated_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_PEER_REMOVED,
+	                  G_CALLBACK (supplicant_iface_peer_removed_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_GROUP_STARTED,
+	                  G_CALLBACK (supplicant_iface_group_started_cb),
+	                  self);
+done:
+	nm_device_queue_recheck_available (NM_DEVICE (self),
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+	_set_is_waiting_for_supplicant (self,
+	                                   !priv->mgmt_iface
+	                                || (  nm_supplicant_interface_get_state (priv->mgmt_iface)
+	                                    < NM_SUPPLICANT_INTERFACE_STATE_READY));
+}
+
+void
+nm_device_wifi_p2p_remove (NMDeviceWifiP2P* self)
+{
+	g_signal_emit_by_name (self, NM_DEVICE_REMOVED);
+}
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	return "wifi-p2p";
+}
+
+/*****************************************************************************/
+
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"PeerAdded",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("peer", "o"),
+	),
+);
+
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"PeerRemoved",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("peer", "o"),
+	),
+);
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_WIFI_P2P,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"StartFind",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("options", "a{sv}"),
+					),
+				),
+				.handle = impl_device_wifi_p2p_start_find,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"StopFind",
+				),
+				.handle = impl_device_wifi_p2p_stop_find,
+			),
+		),
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_wifi_p2p_peer_added,
+			&nm_signal_info_wifi_p2p_peer_removed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("HwAddress",  "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Peers",      "ao", NM_DEVICE_WIFI_P2P_PEERS),
+		),
+	),
+	.legacy_property_changed = FALSE,
+};
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	const char **list;
+
+	switch (prop_id) {
+	case PROP_PEERS:
+		list = nm_wifi_p2p_peers_get_paths (&priv->peers_lst_head);
+		g_value_take_boxed (value, nm_utils_strv_make_deep_copied (list));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_wifi_p2p_init (NMDeviceWifiP2P * self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	c_list_init (&priv->peers_lst_head);
+
+	priv->sup_mgr = g_object_ref (nm_supplicant_manager_get ());
+}
+
+static void
+constructed (GObject *object)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->constructed (object);
+
+	_set_is_waiting_for_supplicant (self, TRUE);
+}
+
+NMDeviceWifiP2P *
+nm_device_wifi_p2p_new (const char *iface)
+{
+	return g_object_new (NM_TYPE_DEVICE_WIFI_P2P,
+	                     NM_DEVICE_IFACE, iface,
+	                     NM_DEVICE_TYPE_DESC, "802.11 Wi-Fi P2P",
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI_P2P,
+	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI,
+	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
+	                     NULL);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (object);
+
+	g_clear_object (&priv->sup_mgr);
+
+	supplicant_interfaces_release (self, FALSE);
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDeviceWifiP2P *peer = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (peer);
+
+	nm_assert (c_list_is_empty (&priv->peers_lst_head));
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->finalize (object);
+}
+
+static void
+nm_device_wifi_p2p_class_init (NMDeviceWifiP2PClass *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;
+	object_class->get_property = get_property;
+	object_class->dispose      = dispose;
+	object_class->finalize     = finalize;
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_wifi_p2p);
+
+	device_class->connection_type_supported        = NM_SETTING_WIFI_P2P_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_WIFI_P2P_SETTING_NAME;
+	device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIFI_P2P);
+	device_class->get_type_description             = get_type_description;
+
+	/* Do we need compatibility checking or is the default good enough? */
+	device_class->is_available                     = is_available;
+	device_class->check_connection_compatible      = check_connection_compatible;
+	device_class->complete_connection              = complete_connection;
+
+	device_class->act_stage1_prepare               = act_stage1_prepare;
+	device_class->act_stage2_config                = act_stage2_config;
+	device_class->get_configured_mtu               = get_configured_mtu;
+	device_class->get_auto_ip_config_method        = get_auto_ip_config_method;
+	device_class->act_stage3_ip_config_start      = act_stage3_ip_config_start;
+
+	device_class->deactivate                       = deactivate;
+	device_class->unmanaged_on_quit                = unmanaged_on_quit;
+
+	device_class->state_changed                    = device_state_changed;
+
+	obj_properties[PROP_PEERS] =
+	    g_param_spec_boxed (NM_DEVICE_WIFI_P2P_PEERS, "", "",
+	                        G_TYPE_STRV,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/devices/wifi/nm-device-wifi-p2p.h b/src/devices/wifi/nm-device-wifi-p2p.h
new file mode 100644
index 00000000..a13eef15
--- /dev/null
+++ b/src/devices/wifi/nm-device-wifi-p2p.h
@@ -0,0 +1,50 @@
+/* NetworkManager -- Wi-Fi P2P Device
+ *
+ * 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_DEVICE_WIFI_P2P_H__
+#define __NM_DEVICE_WIFI_P2P_H__
+
+#include "devices/nm-device.h"
+#include "supplicant/nm-supplicant-interface.h"
+
+#define NM_TYPE_DEVICE_WIFI_P2P            (nm_device_wifi_p2p_get_type ())
+#define NM_DEVICE_WIFI_P2P(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2P))
+#define NM_DEVICE_WIFI_P2P_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2PClass))
+#define NM_IS_DEVICE_WIFI_P2P(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_WIFI_P2P))
+#define NM_IS_DEVICE_WIFI_P2P_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_WIFI_P2P))
+#define NM_DEVICE_WIFI_P2P_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2PClass))
+
+#define NM_DEVICE_WIFI_P2P_PEERS       "peers"
+#define NM_DEVICE_WIFI_P2P_GROUPS      "groups"
+
+typedef struct _NMDeviceWifiP2P NMDeviceWifiP2P;
+typedef struct _NMDeviceWifiP2PClass NMDeviceWifiP2PClass;
+
+GType nm_device_wifi_p2p_get_type (void);
+
+NMDeviceWifiP2P *nm_device_wifi_p2p_new (const char *iface);
+
+NMSupplicantInterface * nm_device_wifi_p2p_get_mgmt_iface (NMDeviceWifiP2P *self);
+void                    nm_device_wifi_p2p_set_mgmt_iface (NMDeviceWifiP2P *self,
+                                                           NMSupplicantInterface  *iface);
+
+void nm_device_wifi_p2p_remove (NMDeviceWifiP2P *self);
+
+#endif /* __NM_DEVICE_WIFI_P2P_H__ */
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 2ce84618..64869672 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -24,10 +24,9 @@
 #include "nm-device-wifi.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 
+#include "nm-device-wifi-p2p.h"
 #include "nm-wifi-ap.h"
 #include "nm-common-macros.h"
 #include "devices/nm-device.h"
@@ -79,6 +78,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 
 enum {
 	SCANNING_PROHIBITED,
+	P2P_DEVICE_CREATED,
 
 	LAST_SIGNAL
 };
@@ -124,6 +124,8 @@ typedef struct {
 	guint             wps_timeout_id;
 
 	NMSettingWirelessWakeOnWLan wowlan_restore;
+
+	NMDeviceWifiP2P  *p2p_device;
 } NMDeviceWifiPrivate;
 
 struct _NMDeviceWifi
@@ -186,6 +188,10 @@ static void supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
                                                  GParamSpec *pspec,
                                                  NMDeviceWifi *self);
 
+static void supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
+                                                   GParamSpec *pspec,
+                                                   NMDeviceWifi *self);
+
 static void request_wireless_scan (NMDeviceWifi *self,
                                    gboolean periodic,
                                    gboolean force_if_scanning,
@@ -198,6 +204,8 @@ static void ap_add_remove (NMDeviceWifi *self,
 
 static void _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset);
 
+static void recheck_p2p_availability (NMDeviceWifi *self);
+
 /*****************************************************************************/
 
 static void
@@ -291,6 +299,10 @@ supplicant_interface_acquire (NMDeviceWifi *self)
 	                  "notify::" NM_SUPPLICANT_INTERFACE_CURRENT_BSS,
 	                  G_CALLBACK (supplicant_iface_notify_current_bss),
 	                  self);
+	g_signal_connect (priv->sup_iface,
+	                  "notify::" NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE,
+	                  G_CALLBACK (supplicant_iface_notify_p2p_available),
+	                  self);
 
 	_notify_scanning (self);
 
@@ -347,6 +359,11 @@ supplicant_interface_release (NMDeviceWifi *self)
 		g_clear_object (&priv->sup_iface);
 	}
 
+	if (priv->p2p_device) {
+		/* Signal to P2P device to also release its reference */
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, NULL);
+	}
+
 	_notify_scanning (self);
 }
 
@@ -732,7 +749,7 @@ check_connection_available (NMDevice *device,
 
 	/* Hidden SSIDs obviously don't always appear in the scan list either.
 	 *
-	 * For an explict user-activation-request, a connection is considered
+	 * For an explicit user-activation-request, a connection is considered
 	 * available because for hidden Wi-Fi, clients didn't consistently
 	 * set the 'hidden' property to indicate hidden SSID networks.  If
 	 * activating but the network isn't available let the device recheck
@@ -838,7 +855,7 @@ complete_connection (NMDevice *device,
 		ssid = nm_wifi_ap_get_ssid (ap);
 
 	if (ssid == NULL) {
-		/* The AP must be hidden.  Connecting to a WiFi AP requires the SSID
+		/* The AP must be hidden.  Connecting to a Wi-Fi AP requires the SSID
 		 * as part of the initial handshake, so check the connection details
 		 * for the SSID.  The AP object will still be used for encryption
 		 * settings and such.
@@ -909,18 +926,6 @@ complete_connection (NMDevice *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);
-			}
 		}
 	}
 
@@ -981,7 +986,7 @@ can_auto_connect (NMDevice *device,
 	g_return_val_if_fail (s_wifi, FALSE);
 
 	/* Always allow autoconnect for AP and non-autoconf Ad-Hoc */
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	method = nm_utils_get_ip_config_method (connection, AF_INET);
 	mode = nm_setting_wireless_get_mode (s_wifi);
 	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP))
 		return TRUE;
@@ -1197,7 +1202,7 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 	                       NM_DEVICE_AUTH_REQUEST,
 	                       invocation,
 	                       NULL,
-	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                       NM_AUTH_PERMISSION_WIFI_SCAN,
 	                       TRUE,
 	                       dbus_request_scan_cb,
 	                       options ? g_variant_ref (options) : NULL);
@@ -1236,7 +1241,7 @@ scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 		return FALSE;
 	case NM_DEVICE_STATE_ACTIVATED:
 		/* Prohibit periodic scans when connected; we ask the supplicant to
-		 * background scan for us, unless the connection is locked to a specifc
+		 * background scan for us, unless the connection is locked to a specific
 		 * BSSID.
 		 */
 		if (periodic)
@@ -1685,20 +1690,37 @@ wifi_secrets_cb (NMActRequest *req,
 	g_return_if_fail (nm_act_request_get_settings_connection (req) == connection);
 
 	if (error) {
-		_LOGW (LOGD_WIFI, "%s", error->message);
-
-		if (g_error_matches (error, NM_AGENT_MANAGER_ERROR,
-		                     NM_AGENT_MANAGER_ERROR_USER_CANCELED)) {
-			/* Don't wait for WPS timeout on an explicit cancel. */
-			nm_clear_g_source (&priv->wps_timeout_id);
-		}
+		_LOGW (LOGD_WIFI, "no secrets: %s", error->message);
 
-		if (!priv->wps_timeout_id) {
-			/* Fail the device only if the WPS period is over too. */
-			nm_device_state_changed (device,
-			                         NM_DEVICE_STATE_FAILED,
-			                         NM_DEVICE_STATE_REASON_NO_SECRETS);
-		}
+		/* Even if WPS is still pending, let's abort the activation when the secret
+		 * request returns.
+		 *
+		 * This means, a user can only effectively use WPS when also running a secret
+		 * agent, and pressing the push button while being prompted for the password.
+		 * Note, that in the secret prompt the user can see that WPS is in progress
+		 * (via the NM_SECRET_AGENT_GET_SECRETS_FLAG_WPS_PBC_ACTIVE flag).
+		 *
+		 * Previously, WPS was not cancelled when the secret request returns.
+		 * Note that in common use-cases WPS is enabled in the connection profile
+		 * but it won't succeed (because it's disabled in the AP or because the
+		 * user is not prepared to press the push button).
+		 * That means for example, during boot we would try to autoconnect with WPS.
+		 * At that point, there is no secret-agent running, and WPS is pending for
+		 * full 30 seconds. If in the meantime a secret agent registers (because
+		 * of logging into the DE), the profile is still busy waiting for WPS to time
+		 * out. Only after that delay, autoconnect starts again (note that autoconnect gets
+		 * not blocked in this case, because a secret agent registered in the meantime).
+		 *
+		 * It seems wrong to continue doing WPS if the user is not aware
+		 * that WPS is ongoing. The user is required to perform an action (push button),
+		 * and must be told via the secret prompt.
+		 * If no secret-agent is running, if the user cancels the secret-request, or any
+		 * other error to obtain secrets, the user apparently does not want WPS either.
+		 */
+		nm_clear_g_source (&priv->wps_timeout_id);
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
 	} else
 		nm_device_activate_schedule_stage1_device_prepare (device);
 }
@@ -1851,9 +1873,10 @@ need_new_8021x_secrets (NMDeviceWifi *self,
 	NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
 	NMConnection *connection;
 
-	g_assert (setting_name != NULL);
+	g_return_val_if_fail (setting_name, FALSE);
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
 	g_return_val_if_fail (connection != NULL, FALSE);
 
 	/* 802.1x stuff only happens in the supplicant's ASSOCIATED state when it's
@@ -1905,10 +1928,11 @@ need_new_wpa_psk (NMDeviceWifi *self,
 	NMConnection *connection;
 	const char *key_mgmt = NULL;
 
-	g_assert (setting_name != NULL);
+	g_return_val_if_fail (setting_name, FALSE);
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
-	g_return_val_if_fail (connection != NULL, FALSE);
+
+	g_return_val_if_fail (connection, FALSE);
 
 	/* A bad PSK will cause the supplicant to disconnect during the 4-way handshake */
 	if (old_state != NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE)
@@ -2021,6 +2045,10 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 	    && new_state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
 		priv->ssid_found = TRUE;
 
+	if (   old_state < NM_SUPPLICANT_INTERFACE_STATE_READY
+	    && new_state >= NM_SUPPLICANT_INTERFACE_STATE_READY)
+		recheck_p2p_availability (self);
+
 	switch (new_state) {
 	case NM_SUPPLICANT_INTERFACE_STATE_READY:
 		_LOGD (LOGD_WIFI, "supplicant ready");
@@ -2040,15 +2068,12 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 		 * schedule the next activation stage.
 		 */
 		if (devstate == NM_DEVICE_STATE_CONFIG) {
-			NMConnection *connection;
 			NMSettingWireless *s_wifi;
 			GBytes *ssid;
 			gs_free char *ssid_str = NULL;
 
-			connection = nm_device_get_applied_connection (NM_DEVICE (self));
-			g_return_if_fail (connection);
+			s_wifi = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_WIRELESS);
 
-			s_wifi = nm_connection_get_setting_wireless (connection);
 			g_return_if_fail (s_wifi);
 
 			ssid = nm_setting_wireless_get_ssid (s_wifi);
@@ -2200,6 +2225,67 @@ supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
 	}
 }
 
+/* We bind the existence of the P2P device to a wifi device that is being
+ * managed by NetworkManager and is capable of P2P operation.
+ * Note that some care must be taken here, because we don't want to re-create
+ * the device every time the supplicant interface is destroyed (e.g. due to
+ * a suspend/resume cycle).
+ * Therefore, this function will be called when a change in the P2P capability
+ * is detected and the supplicant interface has been initialised.
+ */
+static void
+recheck_p2p_availability (NMDeviceWifi *self)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	gboolean p2p_available;
+
+	g_object_get (priv->sup_iface,
+	              NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, &p2p_available,
+	              NULL);
+
+	if (p2p_available && !priv->p2p_device) {
+		gs_free char *iface_name = NULL;
+
+		/* Create a P2P device. "p2p-dev-" is the same prefix as chosen by
+		 * wpa_supplicant internally.
+		 */
+		iface_name = g_strconcat ("p2p-dev-", nm_device_get_iface (NM_DEVICE (self)), NULL);
+
+		priv->p2p_device = nm_device_wifi_p2p_new (iface_name);
+
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, priv->sup_iface);
+
+		g_signal_emit (self, signals[P2P_DEVICE_CREATED], 0, priv->p2p_device);
+		g_object_add_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		g_object_unref (priv->p2p_device);
+		return;
+	}
+
+	if (p2p_available && priv->p2p_device) {
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, priv->sup_iface);
+		return;
+	}
+
+	if (!p2p_available && priv->p2p_device) {
+		/* Destroy the P2P device. */
+		g_object_remove_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		nm_device_wifi_p2p_remove (g_steal_pointer (&priv->p2p_device));
+		return;
+	}
+}
+
+static void
+supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
+                                       GParamSpec *pspec,
+                                       NMDeviceWifi *self)
+{
+	/* Do not update when the interface is still initializing. */
+	if (nm_supplicant_interface_get_state (iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		return;
+
+	recheck_p2p_availability (self);
+}
+
 static gboolean
 handle_auth_or_fail (NMDeviceWifi *self,
                      NMActRequest *req,
@@ -2469,7 +2555,7 @@ wake_on_wlan_enable (NMDeviceWifi *self)
 	NMSettingWirelessWakeOnWLan wowl;
 	NMSettingWireless *s_wireless;
 
-	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_WIRELESS);
+	s_wireless = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_WIRELESS);
 	if (s_wireless) {
 		wowl = nm_setting_wireless_get_wake_on_wlan (s_wireless);
 		if (wowl != NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT)
@@ -2646,7 +2732,8 @@ set_powersave (NMDevice *device)
 	NMSettingWireless *s_wireless;
 	NMSettingWirelessPowersave val;
 
-	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+	s_wireless = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+
 	g_return_if_fail (s_wireless);
 
 	val = nm_setting_wireless_get_powersave (s_wireless);
@@ -2787,50 +2874,29 @@ out:
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip4;
-	const char *method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
-
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	if (s_ip4)
-		method = nm_setting_ip_config_get_method (s_ip4);
-
-	/* Indicate that a critical protocol is about to start */
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0)
-		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), TRUE);
-
-	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
+	gboolean indicate_addressing_running;
 	NMConnection *connection;
-	NMSettingIPConfig *s_ip6;
-	const char *method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
+	const char *method;
 
 	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	if (s_ip6)
-		method = nm_setting_ip_config_get_method (s_ip6);
+	method = nm_utils_get_ip_config_method (connection, addr_family);
+	if (addr_family == AF_INET)
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	else {
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		                                                    NM_SETTING_IP6_CONFIG_METHOD_DHCP);
+	}
 
-	/* Indicate that a critical protocol is about to start */
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0 ||
-	    strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0)
-		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), TRUE);
+	if (indicate_addressing_running)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), TRUE);
 
-	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static guint32
@@ -2865,90 +2931,52 @@ is_static_wep (NMConnection *connection)
 }
 
 static NMActStageReturn
-handle_ip_config_timeout (NMDeviceWifi *self,
-                          NMConnection *connection,
-                          gboolean may_fail,
-                          gboolean *chain_up,
-                          NMDeviceStateReason *out_failure_reason)
+act_stage4_ip_config_timeout (NMDevice *device,
+                              int addr_family,
+                              NMDeviceStateReason *out_failure_reason)
 {
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+	gboolean may_fail;
+
+	connection = nm_device_get_applied_connection (device);
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
+	may_fail = nm_setting_ip_config_get_may_fail (s_ip);
 
-	g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+	if (priv->mode == NM_802_11_MODE_AP)
+		goto call_parent;
 
-	if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP) {
-		*chain_up = TRUE;
-		return NM_ACT_STAGE_RETURN_FAILURE;
+	if (   may_fail
+	    || !is_static_wep (connection)) {
+		/* Not static WEP or failure allowed; let superclass handle it */
+		goto call_parent;
 	}
 
 	/* If IP configuration times out and it's a static WEP connection, that
 	 * usually means the WEP key is wrong.  WEP's Open System auth mode has
 	 * no provision for figuring out if the WEP key is wrong, so you just have
-	 * to wait for DHCP to fail to figure it out.  For all other WiFi security
+	 * to wait for DHCP to fail to figure it out.  For all other Wi-Fi security
 	 * types (open, WPA, 802.1x, etc) if the secrets/certs were wrong the
 	 * connection would have failed before IP configuration.
-	 */
-	if (!may_fail && is_static_wep (connection)) {
-		/* Activation failed, we must have bad encryption key */
-		_LOGW (LOGD_DEVICE | LOGD_WIFI,
-		       "Activation: (wifi) could not get IP configuration for connection '%s'.",
-		       nm_connection_get_id (connection));
+	 *
+	 * Activation failed, we must have bad encryption key */
+	_LOGW (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) could not get IP configuration for connection '%s'.",
+	       nm_connection_get_id (connection));
 
-		if (handle_auth_or_fail (self, NULL, TRUE)) {
-			_LOGI (LOGD_DEVICE | LOGD_WIFI,
-			       "Activation: (wifi) asking for new secrets");
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-		} else {
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-			ret = NM_ACT_STAGE_RETURN_FAILURE;
-		}
-	} else {
-		/* Not static WEP or failure allowed; let superclass handle it */
-		*chain_up = TRUE;
+	if (!handle_auth_or_fail (self, NULL, TRUE)) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	return ret;
-}
-
-static NMActStageReturn
-act_stage4_ip4_config_timeout (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip4;
-	gboolean may_fail = FALSE, chain_up = FALSE;
-	NMActStageReturn ret;
-
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	may_fail = nm_setting_ip_config_get_may_fail (s_ip4);
-
-	ret = handle_ip_config_timeout (NM_DEVICE_WIFI (device), connection, may_fail, &chain_up, out_failure_reason);
-	if (chain_up)
-		ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip4_config_timeout (device, out_failure_reason);
-
-	return ret;
-}
-
-static NMActStageReturn
-act_stage4_ip6_config_timeout (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip6;
-	gboolean may_fail = FALSE, chain_up = FALSE;
-	NMActStageReturn ret;
-
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	may_fail = nm_setting_ip_config_get_may_fail (s_ip6);
-
-	ret = handle_ip_config_timeout (NM_DEVICE_WIFI (device), connection, may_fail, &chain_up, out_failure_reason);
-	if (chain_up)
-		ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip6_config_timeout (device, out_failure_reason);
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) asking for new secrets");
+	return NM_ACT_STAGE_RETURN_POSTPONE;
 
-	return ret;
+call_parent:
+	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip_config_timeout (device, addr_family, out_failure_reason);
 }
 
 static void
@@ -3279,7 +3307,7 @@ nm_device_wifi_new (const char *iface, NMDeviceWifiCapabilities capabilities)
 {
 	return g_object_new (NM_TYPE_DEVICE_WIFI,
 	                     NM_DEVICE_IFACE, iface,
-	                     NM_DEVICE_TYPE_DESC, "802.11 WiFi",
+	                     NM_DEVICE_TYPE_DESC, "802.11 Wi-Fi",
 	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI,
 	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI,
 	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
@@ -3305,6 +3333,12 @@ dispose (GObject *object)
 
 	remove_all_aps (self);
 
+	if (priv->p2p_device) {
+		/* Destroy the P2P device. */
+		g_object_remove_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		nm_device_wifi_p2p_remove (g_steal_pointer (&priv->p2p_device));
+	}
+
 	G_OBJECT_CLASS (nm_device_wifi_parent_class)->dispose (object);
 }
 
@@ -3350,10 +3384,8 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
 	device_class->get_configured_mtu = get_configured_mtu;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
-	device_class->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
-	device_class->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
+	device_class->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
 	device_class->deactivate = deactivate;
 	device_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
 	device_class->unmanaged_on_quit = unmanaged_on_quit;
@@ -3417,4 +3449,12 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_prohibited),
 	                  NULL, NULL, NULL,
 	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
+
+	signals[P2P_DEVICE_CREATED] =
+	    g_signal_new (NM_DEVICE_WIFI_P2P_DEVICE_CREATED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0, NULL, NULL,
+	                  g_cclosure_marshal_VOID__OBJECT,
+	                  G_TYPE_NONE, 1, NM_TYPE_DEVICE);
 }
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 1c555f5f..116ad11e 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -40,6 +40,7 @@
 #define NM_DEVICE_WIFI_LAST_SCAN           "last-scan"
 
 #define NM_DEVICE_WIFI_SCANNING_PROHIBITED    "scanning-prohibited"
+#define NM_DEVICE_WIFI_P2P_DEVICE_CREATED     "p2p-device-created"
 
 typedef struct _NMDeviceWifi NMDeviceWifi;
 typedef struct _NMDeviceWifiClass NMDeviceWifiClass;
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index a3da9791..d668f0d8 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -22,7 +22,6 @@
 
 #include "nm-iwd-manager.h"
 
-#include <string.h>
 #include <net/if.h>
 
 #include "nm-logging.h"
@@ -48,6 +47,7 @@ typedef struct {
 
 typedef struct {
 	NMManager *manager;
+	NMSettings *settings;
 	GCancellable *cancellable;
 	gboolean running;
 	GDBusObjectManager *object_manager;
@@ -136,6 +136,7 @@ agent_dbus_method_cb (GDBusConnection *connection,
 	int ifindex;
 	NMDevice *device;
 	gs_free char *name_owner = NULL;
+	int errsv;
 
 	/* Be paranoid and check the sender address */
 	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager));
@@ -171,8 +172,9 @@ agent_dbus_method_cb (GDBusConnection *connection,
 
 	ifindex = if_nametoindex (ifname);
 	if (!ifindex) {
+		errsv = errno;
 		_LOGD ("agent-request: if_nametoindex failed for Name %s for Device at %s: %i",
-		       ifname, device_path, errno);
+		       ifname, device_path, errsv);
 		goto return_error;
 	}
 
@@ -338,6 +340,7 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	const char *ifname;
 	int ifindex;
 	NMDevice *device;
+	int errsv;
 
 	ifname = get_property_string_or_null (proxy, "Name");
 	if (!ifname) {
@@ -349,8 +352,9 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	ifindex = if_nametoindex (ifname);
 
 	if (!ifindex) {
+		errsv = errno;
 		_LOGE ("if_nametoindex failed for Name %s for Device at %s: %i",
-		       ifname, g_dbus_proxy_get_object_path (proxy), errno);
+		       ifname, g_dbus_proxy_get_object_path (proxy), errsv);
 		return;
 	}
 
@@ -363,31 +367,35 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object);
 }
 
-/* Create an in-memory NMConnection for a WPA2-Enterprise network that
- * has been preprovisioned with an IWD config file so that NM autoconnect
- * mechanism and the clients know this networks needs no additional EAP
- * configuration from the user.  Only do this if no existing connection
- * SSID and security type match that network yet.
+/* Look up an existing NMSettingsConnection for a WPA2-Enterprise network
+ * that has been preprovisioned with an IWD config file, or create a new
+ * in-memory connection object so that NM autoconnect mechanism and the
+ * clients know this networks needs no additional EAP configuration from
+ * the user.
  */
 static NMSettingsConnection *
 mirror_8021x_connection (NMIwdManager *self,
-                         const char *name)
+                         const char *name,
+                         gboolean create_new)
 {
-	NMSettings *settings = NM_SETTINGS_GET;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
 	NMSettingsConnection *const*iter;
 	gs_unref_object NMConnection *connection = NULL;
-	NMSettingsConnection *settings_connection;
+	NMSettingsConnection *settings_connection = NULL;
 	char uuid[37];
 	NMSetting *setting;
 	GError *error = NULL;
 	gs_unref_bytes GBytes *new_ssid = NULL;
 
-	for (iter = nm_settings_get_connections (settings, NULL); *iter; iter++) {
+	for (iter = nm_settings_get_connections (priv->settings, NULL); *iter; iter++) {
 		NMSettingsConnection *sett_conn = *iter;
 		NMConnection *conn = nm_settings_connection_get_connection (sett_conn);
 		NMIwdNetworkSecurity security;
 		gs_free char *ssid_name = NULL;
 		NMSettingWireless *s_wifi;
+		NMSetting8021x *s_8021x;
+		gboolean external = FALSE;
+		guint i;
 
 		security = nm_wifi_connection_get_iwd_security (conn, NULL);
 		if (security != NM_IWD_NETWORK_SECURITY_8021X)
@@ -399,14 +407,30 @@ mirror_8021x_connection (NMIwdManager *self,
 
 		ssid_name = _nm_utils_ssid_to_utf8 (nm_setting_wireless_get_ssid (s_wifi));
 
-		/* We already have an NMSettingsConnection matching this
-		 * KnownNetwork, whether it's saved or an in-memory connection
-		 * potentially created by ourselves.  Nothing to do here.
-		 */
-		if (nm_streq (ssid_name, name))
-			return NULL;
+		if (!nm_streq (ssid_name, name))
+			continue;
+
+		s_8021x = nm_connection_get_setting_802_1x (conn);
+		for (i = 0; i < nm_setting_802_1x_get_num_eap_methods (s_8021x); i++) {
+			if (nm_streq (nm_setting_802_1x_get_eap_method (s_8021x, i), "external")) {
+				external = TRUE;
+				break;
+			}
+		}
+
+		/* Prefer returning connections for EAP method "external" */
+		if (!settings_connection || external)
+			settings_connection = sett_conn;
 	}
 
+	/* If we already have an NMSettingsConnection matching this
+	 * KnownNetwork, whether it's saved or an in-memory connection
+	 * potentially created by ourselves then we have nothing left to
+	 * do here.
+	 */
+	if (settings_connection || !create_new)
+		return settings_connection;
+
 	connection = nm_simple_connection_new ();
 
 	setting = NM_SETTING (g_object_new (NM_TYPE_SETTING_CONNECTION,
@@ -446,7 +470,7 @@ mirror_8021x_connection (NMIwdManager *self,
 	if (!nm_connection_normalize (connection, NULL, NULL, NULL))
 		return NULL;
 
-	settings_connection = nm_settings_add_connection (settings, connection,
+	settings_connection = nm_settings_add_connection (priv->settings, connection,
 	                                                  FALSE, &error);
 	if (!settings_connection) {
 		_LOGW ("failed to add a mirror NMConnection for IWD's Known Network '%s': %s",
@@ -526,16 +550,19 @@ interface_added (GDBusObjectManager *object_manager, GDBusObject *object,
 		id = known_network_id_new (name, security);
 
 		data = g_hash_table_lookup (priv->known_networks, id);
-		if (data)
+		if (data) {
+			_LOGW ("DBus error: KnownNetwork already exists ('%s', %s)",
+			       name, type_str);
 			g_free (id);
-		else {
+			nm_g_object_ref_set (&data->known_network, proxy);
+		} else {
 			data = g_slice_new0 (KnownNetworkData);
 			data->known_network = g_object_ref (proxy);
 			g_hash_table_insert (priv->known_networks, id, data);
 		}
 
 		if (security == NM_IWD_NETWORK_SECURITY_8021X) {
-			sett_conn = mirror_8021x_connection (self, name);
+			sett_conn = mirror_8021x_connection (self, name, TRUE);
 
 			if (   sett_conn
 			    && sett_conn != data->mirror_connection) {
@@ -593,6 +620,55 @@ interface_removed (GDBusObjectManager *object_manager, GDBusObject *object,
 	}
 }
 
+static void
+connection_removed (NMSettings *settings,
+                    NMSettingsConnection *sett_conn,
+                    gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	NMConnection *conn = nm_settings_connection_get_connection (sett_conn);
+	NMSettingWireless *s_wireless;
+	gboolean mapped;
+	KnownNetworkData *data;
+	KnownNetworkId id;
+
+	id.security = nm_wifi_connection_get_iwd_security (conn, &mapped);
+	if (!mapped)
+		return;
+
+	s_wireless = nm_connection_get_setting_wireless (conn);
+	id.name = _nm_utils_ssid_to_utf8 (nm_setting_wireless_get_ssid (s_wireless));
+	data = g_hash_table_lookup (priv->known_networks, &id);
+	g_free ((char *) id.name);
+	if (!data)
+		return;
+
+	if (id.security == NM_IWD_NETWORK_SECURITY_8021X) {
+		NMSettingsConnection *new_mirror_conn;
+
+		if (data->mirror_connection != sett_conn)
+			return;
+
+		g_clear_object (&data->mirror_connection);
+
+		/* Don't call Forget for an 8021x network until there's no
+		 * longer *any* matching NMSettingsConnection (debatable)
+		 */
+		new_mirror_conn = mirror_8021x_connection (self, id.name, FALSE);
+		if (new_mirror_conn) {
+			data->mirror_connection = g_object_ref (new_mirror_conn);
+			return;
+		}
+	}
+
+	if (!priv->running)
+		return;
+
+	g_dbus_proxy_call (data->known_network, "Forget",
+	                   NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
 static gboolean
 _om_has_name_owner (GDBusObjectManager *object_manager)
 {
@@ -744,7 +820,7 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 	                                   &priv->agent_path,
 	                                   &error);
 	if (!priv->agent_id) {
-		_LOGE ("failed to export the IWD Agent: PSK/8021x WiFi networks may not work: %s",
+		_LOGE ("failed to export the IWD Agent: PSK/8021x Wi-Fi networks may not work: %s",
 		       error->message);
 		g_clear_error (&error);
 	}
@@ -824,6 +900,10 @@ nm_iwd_manager_init (NMIwdManager *self)
 	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED,
 	                  G_CALLBACK (device_added), self);
 
+	priv->settings = g_object_ref (nm_settings_get ());
+	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
+	                  G_CALLBACK (connection_removed), self);
+
 	priv->cancellable = g_cancellable_new ();
 
 	priv->known_networks = g_hash_table_new_full ((GHashFunc) known_network_id_hash,
@@ -844,6 +924,14 @@ dispose (GObject *object)
 
 	nm_clear_g_cancellable (&priv->cancellable);
 
+	if (priv->settings) {
+		g_signal_handlers_disconnect_by_data (priv->settings, self);
+		g_clear_object (&priv->settings);
+	}
+
+	/* This may trigger mirror connection removals so it happens
+	 * after the g_signal_handlers_disconnect_by_data above.
+	 */
 	nm_clear_pointer (&priv->known_networks, g_hash_table_destroy);
 
 	if (priv->manager) {
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index e5573383..87daff59 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -23,7 +23,6 @@
 
 #include "nm-wifi-ap.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-setting-wireless.h"
@@ -425,6 +424,8 @@ security_from_vardict (GVariant *security)
 		    g_strv_contains (array, "wpa-fils-sha256") ||
 		    g_strv_contains (array, "wpa-fils-sha384"))
 			flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X;
+		if (g_strv_contains (array, "sae"))
+			flags |= NM_802_11_AP_SEC_KEY_MGMT_SAE;
 		g_free (array);
 	}
 
@@ -1390,7 +1391,8 @@ nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 	| NM_802_11_AP_SEC_GROUP_TKIP \
 	| NM_802_11_AP_SEC_GROUP_CCMP \
 	| NM_802_11_AP_SEC_KEY_MGMT_PSK \
-	| NM_802_11_AP_SEC_KEY_MGMT_802_1X )
+	| NM_802_11_AP_SEC_KEY_MGMT_802_1X \
+	| NM_802_11_AP_SEC_KEY_MGMT_SAE )
 
 	GObjectClass *object_class = G_OBJECT_CLASS (ap_class);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (ap_class);
diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/devices/wifi/nm-wifi-factory.c
index 6b8e5fb8..9a89bbe9 100644
--- a/src/devices/wifi/nm-wifi-factory.c
+++ b/src/devices/wifi/nm-wifi-factory.c
@@ -26,6 +26,7 @@
 #include "nm-setting-wireless.h"
 #include "nm-setting-olpc-mesh.h"
 #include "nm-device-wifi.h"
+#include "nm-device-wifi-p2p.h"
 #include "nm-device-olpc-mesh.h"
 #include "nm-device-iwd.h"
 #include "settings/nm-settings-connection.h"
@@ -68,6 +69,18 @@ nm_device_factory_create (GError **error)
 
 /*****************************************************************************/
 
+static void
+p2p_device_created (NMDeviceWifi    *device,
+                    NMDeviceWifiP2P *p2p_device,
+                    NMDeviceFactory *self)
+{
+	nm_log_info (LOGD_PLATFORM | LOGD_WIFI,
+	             "Wi-Fi P2P device controlled by interface %s created",
+	             nm_device_get_iface (NM_DEVICE (device)));
+
+	g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, p2p_device);
+}
+
 static NMDevice *
 create_device (NMDeviceFactory *factory,
                const char *iface,
@@ -75,8 +88,6 @@ create_device (NMDeviceFactory *factory,
                NMConnection *connection,
                gboolean *out_ignore)
 {
-	NMDeviceWifiCapabilities capabilities;
-	NM80211Mode mode;
 	gs_free char *backend = NULL;
 
 	g_return_val_if_fail (iface != NULL, NULL);
@@ -84,23 +95,6 @@ create_device (NMDeviceFactory *factory,
 	g_return_val_if_fail (g_strcmp0 (iface, plink->name) == 0, NULL);
 	g_return_val_if_fail (NM_IN_SET (plink->type, NM_LINK_TYPE_WIFI, NM_LINK_TYPE_OLPC_MESH), NULL);
 
-	if (!nm_platform_wifi_get_capabilities (NM_PLATFORM_GET,
-	                                        plink->ifindex,
-	                                        &capabilities)) {
-		nm_log_warn (LOGD_PLATFORM | LOGD_WIFI, "(%s) failed to initialize Wi-Fi driver for ifindex %d", iface, plink->ifindex);
-		return NULL;
-	}
-
-	/* Ignore monitor-mode and other unhandled interface types.
-	 * FIXME: keep TYPE_MONITOR devices in UNAVAILABLE state and manage
-	 * them if/when they change to a handled type.
-	 */
-	mode = nm_platform_wifi_get_mode (NM_PLATFORM_GET, plink->ifindex);
-	if (mode == NM_802_11_MODE_UNKNOWN) {
-		*out_ignore = TRUE;
-		return NULL;
-	}
-
 	if (plink->type != NM_LINK_TYPE_WIFI)
 		return nm_device_olpc_mesh_new (iface);
 
@@ -116,11 +110,42 @@ create_device (NMDeviceFactory *factory,
 	            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 (!backend || !strcasecmp (backend, "wpa_supplicant")) {
+		NMDevice *device;
+		NMDeviceWifiCapabilities capabilities;
+		NM80211Mode mode;
+
+		if (!nm_platform_wifi_get_capabilities (NM_PLATFORM_GET,
+		                                        plink->ifindex,
+		                                        &capabilities)) {
+			nm_log_warn (LOGD_PLATFORM | LOGD_WIFI,
+			             "(%s) failed to initialize Wi-Fi driver for ifindex %d",
+			             iface, plink->ifindex);
+			return NULL;
+		}
+
+		/* Ignore monitor-mode and other unhandled interface types.
+		 * FIXME: keep TYPE_MONITOR devices in UNAVAILABLE state and manage
+		 * them if/when they change to a handled type.
+		 */
+		mode = nm_platform_wifi_get_mode (NM_PLATFORM_GET, plink->ifindex);
+		if (mode == NM_802_11_MODE_UNKNOWN) {
+			*out_ignore = TRUE;
+			return NULL;
+		}
+
+		device = nm_device_wifi_new (iface, capabilities);
+
+		g_signal_connect_object (device, NM_DEVICE_WIFI_P2P_DEVICE_CREATED,
+		                         G_CALLBACK (p2p_device_created),
+		                         factory,
+		                         0);
+
+		return device;
+	}
 #if WITH_IWD
 	else if (!strcasecmp (backend, "iwd"))
-		return nm_device_iwd_new (iface, capabilities);
+		return nm_device_iwd_new (iface);
 #endif
 
 	nm_log_warn (LOGD_PLATFORM | LOGD_WIFI, "(%s) config: unknown or unsupported wifi-backend %s", iface, backend);
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.c b/src/devices/wifi/nm-wifi-p2p-peer.c
new file mode 100644
index 00000000..4b524623
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-p2p-peer.c
@@ -0,0 +1,796 @@
+/* NetworkManager -- Wi-Fi P2P Peer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-wifi-p2p-peer.h"
+
+#include <stdlib.h>
+
+#include "nm-setting-wireless.h"
+
+#include "nm-wifi-utils.h"
+#include "NetworkManagerUtils.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "platform/nm-platform.h"
+#include "devices/nm-device.h"
+#include "nm-dbus-manager.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMWifiP2PPeer,
+	PROP_NAME,
+	PROP_MANUFACTURER,
+	PROP_MODEL,
+	PROP_MODEL_NUMBER,
+	PROP_SERIAL,
+	PROP_WFD_IES,
+	PROP_GROUPS,
+	PROP_HW_ADDRESS,
+	PROP_STRENGTH,
+	PROP_LAST_SEEN,
+	PROP_FLAGS,
+);
+
+struct _NMWifiP2PPeerPrivate {
+	char *supplicant_path;   /* D-Bus object path of this Peer from wpa_supplicant */
+
+	/* Scanned or cached values */
+	char *             name;
+	char *             manufacturer;
+	char *             model;
+	char *             model_number;
+	char *             serial;
+
+	char *             address;
+
+	GBytes *           wfd_ies;
+	char **            groups;
+
+	guint8             strength;
+
+	NM80211ApFlags     flags;      /* General flags */
+
+	/* Non-scanned attributes */
+	gint32             last_seen;    /* Timestamp when the Peer was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
+};
+
+typedef struct _NMWifiP2PPeerPrivate NMWifiP2PPeerPrivate;
+
+struct _NMWifiP2PPeerClass {
+	NMDBusObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMWifiP2PPeer, nm_wifi_p2p_peer, NM_TYPE_DBUS_OBJECT)
+
+#define NM_WIFI_P2P_PEER_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMWifiP2PPeer, NM_IS_WIFI_P2P_PEER)
+
+/*****************************************************************************/
+
+const char **
+nm_wifi_p2p_peers_get_paths (const CList *peers_lst_head)
+{
+	NMWifiP2PPeer *peer;
+	const char **list;
+	const char *path;
+	gsize i, n;
+
+	n = c_list_length (peers_lst_head);
+	list = g_new (const char *, n + 1);
+
+	i = 0;
+	if (n > 0) {
+		c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+			nm_assert (i < n);
+			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (peer));
+			nm_assert (path);
+
+			list[i++] = path;
+		}
+		nm_assert (i <= n);
+	}
+	list[i] = NULL;
+	return list;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peers_find_first_compatible (const CList *peers_lst_head,
+                                         NMConnection *connection)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (connection, NULL);
+
+	c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+		if (nm_wifi_p2p_peer_check_compatible (peer, connection))
+			return peer;
+	}
+	return NULL;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peers_find_by_supplicant_path (const CList *peers_lst_head, const char *path)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (path != NULL, NULL);
+
+	c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+		if (nm_streq0 (path, nm_wifi_p2p_peer_get_supplicant_path (peer)))
+			return peer;
+	}
+	return NULL;
+}
+
+/*****************************************************************************/
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peer_lookup_for_device (NMDevice *device, const char *exported_path)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
+
+	peer = (NMWifiP2PPeer *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (device)),
+	                                                        exported_path);
+	if (   !peer
+	    || !NM_IS_WIFI_P2P_PEER (peer)
+	    || peer->wifi_device != device)
+		return NULL;
+
+	return peer;
+}
+
+/*****************************************************************************/
+
+const char *
+nm_wifi_p2p_peer_get_supplicant_path (NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->supplicant_path;
+}
+
+const char *
+nm_wifi_p2p_peer_get_name (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->name;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_name (NMWifiP2PPeer *peer, const char *name)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (name, priv->name) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->name, g_free);
+	if (name)
+		priv->name = g_strdup (name);
+
+	_notify (peer, PROP_NAME);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_manufacturer (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->manufacturer;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_manufacturer (NMWifiP2PPeer *peer, const char *manufacturer)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (manufacturer, priv->manufacturer) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->manufacturer, g_free);
+	if (manufacturer)
+		priv->manufacturer = g_strdup (manufacturer);
+
+	_notify (peer, PROP_MANUFACTURER);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_model (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->model;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_model (NMWifiP2PPeer *peer, const char *model)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (model, priv->model) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->model, g_free);
+	if (model)
+		priv->model = g_strdup (model);
+
+	_notify (peer, PROP_MODEL);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_model_number (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->model_number;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_model_number (NMWifiP2PPeer *peer, const char *model_number)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (model_number, priv->model_number) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->model_number, g_free);
+	if (model_number)
+		priv->model_number = g_strdup (model_number);
+
+	_notify (peer, PROP_MODEL_NUMBER);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_serial (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->serial;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_serial (NMWifiP2PPeer *peer, const char *serial)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (serial, priv->serial) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->serial, g_free);
+	if (serial)
+		priv->serial = g_strdup (serial);
+
+	_notify (peer, PROP_SERIAL);
+	return TRUE;
+}
+
+GBytes *
+nm_wifi_p2p_peer_get_wfd_ies (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->wfd_ies;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_wfd_ies (NMWifiP2PPeer *peer, GBytes *wfd_ies)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (nm_gbytes_equal0 (priv->wfd_ies, wfd_ies))
+		return FALSE;
+
+	g_bytes_unref (priv->wfd_ies);
+	priv->wfd_ies = wfd_ies ? g_bytes_ref (wfd_ies) : NULL;
+
+	_notify (peer, PROP_WFD_IES);
+	return TRUE;
+}
+
+const char *const*
+nm_wifi_p2p_peer_get_groups (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return (const char * const*) NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->groups;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_groups (NMWifiP2PPeer *peer, const char** groups)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+	g_return_val_if_fail (groups != NULL, FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (_nm_utils_strv_equal (priv->groups, (char **) groups))
+		return FALSE;
+
+	g_strfreev (priv->groups);
+	priv->groups = g_strdupv ((char**) groups);
+
+	_notify (peer, PROP_GROUPS);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_address (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->address;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_address_bin (NMWifiP2PPeer *peer, const guint8 addr[static ETH_ALEN])
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (   priv->address
+	    && nm_utils_hwaddr_matches (addr, ETH_ALEN, priv->address, -1))
+		return FALSE;
+
+	g_free (priv->address);
+	priv->address = nm_utils_hwaddr_ntoa (addr, ETH_ALEN);
+	_notify (peer, PROP_HW_ADDRESS);
+	return TRUE;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_address (NMWifiP2PPeer *peer, const char *addr)
+{
+	guint8 addr_buf[ETH_ALEN];
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (   !addr
+	    || !nm_utils_hwaddr_aton (addr, addr_buf, sizeof (addr_buf)))
+		g_return_val_if_reached (FALSE);
+
+	return nm_wifi_p2p_peer_set_address_bin (peer, addr_buf);
+}
+
+gint8
+nm_wifi_p2p_peer_get_strength (NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), 0);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->strength;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_strength (NMWifiP2PPeer *peer, const gint8 strength)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (priv->strength != strength) {
+		priv->strength = strength;
+		_notify (peer, PROP_STRENGTH);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+NM80211ApFlags
+nm_wifi_p2p_peer_get_flags (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NM_802_11_AP_FLAGS_NONE);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->flags;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_last_seen (NMWifiP2PPeer *peer, gint32 last_seen)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (priv->last_seen != last_seen) {
+		priv->last_seen = last_seen;
+		_notify (peer, PROP_LAST_SEEN);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+
+/*****************************************************************************/
+
+gboolean
+nm_wifi_p2p_peer_update_from_properties (NMWifiP2PPeer *peer,
+                                         const char *supplicant_path,
+                                         GVariant *properties)
+{
+	NMWifiP2PPeerPrivate *priv;
+	const guint8 *bytes;
+	GVariant *v;
+	gsize len;
+	const char *s;
+	const char **sv;
+	gint32 i32;
+	gboolean changed = FALSE;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+	g_return_val_if_fail (properties, FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	g_object_freeze_notify (G_OBJECT (peer));
+
+	if (g_variant_lookup (properties, "level", "i", &i32))
+		changed |= nm_wifi_p2p_peer_set_strength (peer, nm_wifi_utils_level_to_quality (i32));
+
+	if (g_variant_lookup (properties, "DeviceName", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_name (peer, s);
+
+	if (g_variant_lookup (properties, "Manufacturer", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_manufacturer (peer, s);
+
+	if (g_variant_lookup (properties, "Model", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_model (peer, s);
+
+	if (g_variant_lookup (properties, "ModelNumber", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_model_number (peer, s);
+
+	if (g_variant_lookup (properties, "Serial", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_serial (peer, s);
+
+	v = g_variant_lookup_value (properties, "DeviceAddress", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		if (   len == ETH_ALEN
+		    && memcmp (bytes, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
+		    && memcmp (bytes, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0)
+			changed |= nm_wifi_p2p_peer_set_address_bin (peer, bytes);
+		g_variant_unref (v);
+	}
+
+	/* The IEs property contains the WFD R1 subelements */
+	v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		gs_unref_bytes GBytes *b = NULL;
+
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		b = g_bytes_new (bytes, len);
+		changed |= nm_wifi_p2p_peer_set_wfd_ies (peer, b);
+		g_variant_unref (v);
+	}
+
+	v = g_variant_lookup_value (properties, "Groups", G_VARIANT_TYPE_OBJECT_PATH_ARRAY);
+	if (v) {
+		sv = g_variant_get_objv (v, NULL);
+		changed |= nm_wifi_p2p_peer_set_groups (peer, sv);
+		g_free (sv);
+	}
+
+	/*if (max_rate)
+		changed |= nm_wifi_p2p_peer_set_max_bitrate (peer, max_rate / 1000);*/
+
+	if (!priv->supplicant_path) {
+		priv->supplicant_path = g_strdup (supplicant_path);
+		changed = TRUE;
+	}
+
+	changed |= nm_wifi_p2p_peer_set_last_seen (peer, nm_utils_get_monotonic_timestamp_s ());
+
+	g_object_thaw_notify (G_OBJECT (peer));
+
+	return changed;
+}
+
+const char *
+nm_wifi_p2p_peer_to_string (const NMWifiP2PPeer *self,
+                            char *str_buf,
+                            gsize buf_len,
+                            gint32 now_s)
+{
+	const NMWifiP2PPeerPrivate *priv;
+	const char *supplicant_id = "-";
+	const char* export_path;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (self), NULL);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	if (priv->supplicant_path)
+		supplicant_id = strrchr (priv->supplicant_path, '/') ?: supplicant_id;
+
+	export_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
+	if (export_path)
+		export_path = strrchr (export_path, '/') ?: export_path;
+	else
+		export_path = "/";
+
+	g_snprintf (str_buf, buf_len,
+	            "%17s [n:%s, m:%s, mod:%s, mod_num:%s, ser:%s] %3us sup:%s [nm:%s]",
+	            priv->address ?: "(none)",
+	            priv->name,
+	            priv->manufacturer,
+	            priv->model,
+	            priv->model_number,
+	            priv->serial,
+	            priv->last_seen > 0 ? ((now_s > 0 ? now_s : nm_utils_get_monotonic_timestamp_s ()) - priv->last_seen) : -1,
+	            supplicant_id,
+	            export_path);
+
+	return str_buf;
+}
+
+gboolean
+nm_wifi_p2p_peer_check_compatible (NMWifiP2PPeer *self,
+                                   NMConnection *connection)
+{
+	NMWifiP2PPeerPrivate *priv;
+	NMSettingWifiP2P *s_wifi_p2p;
+	const char *hwaddr;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (self), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	if (s_wifi_p2p == NULL)
+		return FALSE;
+
+	hwaddr = nm_setting_wifi_p2p_get_peer (s_wifi_p2p);
+	if (   hwaddr
+	    && (   !priv->address
+	        || !nm_utils_hwaddr_matches (hwaddr, -1, priv->address, -1)))
+		return FALSE;
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMWifiP2PPeer *self = NM_WIFI_P2P_PEER (object);
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	switch (prop_id) {
+	case PROP_FLAGS:
+		g_value_set_uint (value, priv->flags);
+		break;
+	case PROP_NAME:
+		g_value_set_string (value, priv->name);
+		break;
+	case PROP_MANUFACTURER:
+		g_value_set_string (value, priv->manufacturer);
+		break;
+	case PROP_MODEL:
+		g_value_set_string (value, priv->model);
+		break;
+	case PROP_MODEL_NUMBER:
+		g_value_set_string (value, priv->model_number);
+		break;
+	case PROP_SERIAL:
+		g_value_set_string (value, priv->serial);
+		break;
+	case PROP_WFD_IES:
+		g_value_take_variant (value, nm_utils_gbytes_to_variant_ay (priv->wfd_ies));
+		break;
+	case PROP_GROUPS:
+		g_value_set_variant (value,
+		                      g_variant_new_strv (   (const char*const*) priv->groups
+		                                          ?: NM_PTRARRAY_EMPTY (const char *),
+		                                          -1));
+		break;
+	case PROP_HW_ADDRESS:
+		g_value_set_string (value, priv->address);
+		break;
+	case PROP_STRENGTH:
+		g_value_set_uchar (value, priv->strength);
+		break;
+	case PROP_LAST_SEEN:
+		g_value_set_int (value,
+		                 priv->last_seen > 0
+		                     ? (int) nm_utils_monotonic_timestamp_as_boottime (priv->last_seen, NM_UTILS_NS_PER_SECOND)
+		                     : -1);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_wifi_p2p_peer_init (NMWifiP2PPeer *self)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerPrivate);
+
+	self->_priv = priv;
+
+	c_list_init (&self->peers_lst);
+
+	priv->flags = NM_802_11_AP_FLAGS_NONE;
+	priv->last_seen = -1;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peer_new_from_properties (const char *supplicant_path, GVariant *properties)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (supplicant_path != NULL, NULL);
+	g_return_val_if_fail (properties != NULL, NULL);
+
+	peer = (NMWifiP2PPeer *) g_object_new (NM_TYPE_WIFI_P2P_PEER, NULL);
+	nm_wifi_p2p_peer_update_from_properties (peer, supplicant_path, properties);
+
+	/* ignore peers with invalid or missing address */
+	if (!nm_wifi_p2p_peer_get_address (peer)) {
+		g_object_unref (peer);
+		return NULL;
+	}
+
+	return peer;
+}
+
+static void
+finalize (GObject *object)
+{
+	NMWifiP2PPeer *self = NM_WIFI_P2P_PEER (object);
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	nm_assert (!self->wifi_device);
+	nm_assert (c_list_is_empty (&self->peers_lst));
+
+	g_free (priv->supplicant_path);
+	g_free (priv->name);
+	g_free (priv->manufacturer);
+	g_free (priv->model);
+	g_free (priv->model_number);
+	g_free (priv->serial);
+	g_free (priv->address);
+	g_bytes_unref (priv->wfd_ies);
+	g_strfreev (priv->groups);
+
+	G_OBJECT_CLASS (nm_wifi_p2p_peer_parent_class)->finalize (object);
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_p2p_peer = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_WIFI_P2P_PEER,
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Flags",        "u",  NM_WIFI_P2P_PEER_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Name",         "s",  NM_WIFI_P2P_PEER_NAME),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Manufacturer", "s",  NM_WIFI_P2P_PEER_MANUFACTURER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Model",        "s",  NM_WIFI_P2P_PEER_MODEL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("ModelNumber",  "s",  NM_WIFI_P2P_PEER_MODEL_NUMBER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Serial",       "s",  NM_WIFI_P2P_PEER_SERIAL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("WfdIEs",       "ay", NM_WIFI_P2P_PEER_WFD_IES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Groups",       "as", NM_WIFI_P2P_PEER_GROUPS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("HwAddress",    "s",  NM_WIFI_P2P_PEER_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Strength",     "y",  NM_WIFI_P2P_PEER_STRENGTH),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("LastSeen",     "i",  NM_WIFI_P2P_PEER_LAST_SEEN),
+		),
+	),
+	.legacy_property_changed = FALSE,
+};
+
+static void
+nm_wifi_p2p_peer_class_init (NMWifiP2PPeerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (object_class, sizeof (NMWifiP2PPeerPrivate));
+
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH_WIFI_P2P_PEER);
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_p2p_peer);
+
+	object_class->get_property = get_property;
+	object_class->finalize     = finalize;
+
+	obj_properties[PROP_FLAGS] =
+	    g_param_spec_uint (NM_WIFI_P2P_PEER_FLAGS, "", "",
+	                       NM_802_11_AP_FLAGS_NONE,
+	                       NM_802_11_AP_FLAGS_PRIVACY,
+	                       NM_802_11_AP_FLAGS_NONE,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_NAME] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_NAME, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MANUFACTURER] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MANUFACTURER, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MODEL] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MODEL, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MODEL_NUMBER] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MODEL_NUMBER, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_SERIAL] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_SERIAL, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_WFD_IES] =
+	    g_param_spec_variant (NM_WIFI_P2P_PEER_WFD_IES, "", "",
+	                          G_VARIANT_TYPE ("ay"),
+	                          NULL,
+	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_GROUPS] =
+	    g_param_spec_variant (NM_WIFI_P2P_PEER_GROUPS, "", "",
+	                          G_VARIANT_TYPE ("as"),
+	                          NULL,
+	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_HW_ADDRESS] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_HW_ADDRESS, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_STRENGTH] =
+	    g_param_spec_uchar (NM_WIFI_P2P_PEER_STRENGTH, "", "",
+	                        0, G_MAXINT8, 0,
+	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_LAST_SEEN] =
+	    g_param_spec_int (NM_WIFI_P2P_PEER_LAST_SEEN, "", "",
+	                      -1, G_MAXINT, -1,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.h b/src/devices/wifi/nm-wifi-p2p-peer.h
new file mode 100644
index 00000000..d6ff7abc
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-p2p-peer.h
@@ -0,0 +1,114 @@
+/* NetworkManager -- Wi-Fi P2P Peer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_WIFI_P2P_PEER_H__
+#define __NM_WIFI_P2P_PEER_H__
+
+#include "nm-dbus-object.h"
+#include "nm-dbus-interface.h"
+#include "nm-connection.h"
+
+#define NM_TYPE_WIFI_P2P_PEER            (nm_wifi_p2p_peer_get_type ())
+#define NM_WIFI_P2P_PEER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeer))
+#define NM_WIFI_P2P_PEER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerClass))
+#define NM_IS_WIFI_P2P_PEER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIFI_P2P_PEER))
+#define NM_IS_WIFI_P2P_PEER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIFI_P2P_PEER))
+#define NM_WIFI_P2P_PEER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerClass))
+
+#define NM_WIFI_P2P_PEER_FLAGS                "flags"
+#define NM_WIFI_P2P_PEER_NAME                 "name"
+#define NM_WIFI_P2P_PEER_MANUFACTURER         "manufacturer"
+#define NM_WIFI_P2P_PEER_MODEL                "model"
+#define NM_WIFI_P2P_PEER_MODEL_NUMBER         "model-number"
+#define NM_WIFI_P2P_PEER_SERIAL               "serial"
+#define NM_WIFI_P2P_PEER_WFD_IES              "wfd-ies"
+#define NM_WIFI_P2P_PEER_GROUPS               "groups"
+#define NM_WIFI_P2P_PEER_HW_ADDRESS           "hw-address"
+#define NM_WIFI_P2P_PEER_STRENGTH             "strength"
+#define NM_WIFI_P2P_PEER_LAST_SEEN            "last-seen"
+
+typedef struct {
+	NMDBusObject parent;
+	NMDevice *wifi_device;
+	CList peers_lst;
+	struct _NMWifiP2PPeerPrivate *_priv;
+} NMWifiP2PPeer;
+
+typedef struct _NMWifiP2PPeerClass NMWifiP2PPeerClass;
+
+GType nm_wifi_p2p_peer_get_type (void);
+
+NMWifiP2PPeer *   nm_wifi_p2p_peer_new_from_properties      (const char *supplicant_path,
+                                                             GVariant *properties);
+
+gboolean          nm_wifi_p2p_peer_update_from_properties   (NMWifiP2PPeer *peer,
+                                                             const char *supplicant_path,
+                                                             GVariant *properties);
+
+gboolean          nm_wifi_p2p_peer_check_compatible         (NMWifiP2PPeer *self,
+                                                             NMConnection *connection);
+
+const char *      nm_wifi_p2p_peer_get_supplicant_path      (NMWifiP2PPeer *peer);
+
+const char *      nm_wifi_p2p_peer_get_name                 (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_name                 (NMWifiP2PPeer *peer,
+                                                             const char *name);
+const char *      nm_wifi_p2p_peer_get_manufacturer         (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_manufacturer         (NMWifiP2PPeer *peer,
+                                                             const char *manufacturer);
+const char *      nm_wifi_p2p_peer_get_model                (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_model                (NMWifiP2PPeer *peer,
+                                                             const char *model);
+const char *      nm_wifi_p2p_peer_get_model_number         (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_model_number         (NMWifiP2PPeer *peer,
+                                                             const char *number);
+const char *      nm_wifi_p2p_peer_get_serial               (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_serial               (NMWifiP2PPeer *peer,
+                                                             const char *serial);
+
+GBytes *          nm_wifi_p2p_peer_get_wfd_ies             (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_wfd_ies             (NMWifiP2PPeer *peer,
+                                                            GBytes *bytes);
+
+const char *const*nm_wifi_p2p_peer_get_groups              (const NMWifiP2PPeer *peer);
+
+const char *      nm_wifi_p2p_peer_get_address              (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_address              (NMWifiP2PPeer *peer,
+                                                             const char *addr);
+gint8             nm_wifi_p2p_peer_get_strength             (NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_strength             (NMWifiP2PPeer *peer,
+                                                             gint8 strength);
+NM80211ApFlags    nm_wifi_p2p_peer_get_flags                (const NMWifiP2PPeer *self);
+
+const char       *nm_wifi_p2p_peer_to_string                (const NMWifiP2PPeer *self,
+                                                             char *str_buf,
+                                                             gsize buf_len,
+                                                             gint32 now_s);
+
+const char      **nm_wifi_p2p_peers_get_paths               (const CList *peers_lst_head);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peers_find_first_compatible (const CList *peers_lst_head,
+                                                           NMConnection *connection);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peers_find_by_supplicant_path (const CList *peers_lst_head, const char *path);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peer_lookup_for_device (NMDevice *device, const char *exported_path);
+
+#endif /* __NM_WIFI_P2P_PEER_H__ */
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 0f7836be..c6e8b3e0 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -22,7 +22,6 @@
 
 #include "nm-wifi-utils.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-utils.h"
@@ -739,6 +738,12 @@ nm_wifi_utils_complete_connection (GBytes *ap_ssid,
 		 * setting.  Since there's so much configuration required for it, there's
 		 * no way it can be automatically completed.
 		 */
+	} else if (   (key_mgmt && !strcmp (key_mgmt, "sae"))
+	           || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE)) {
+		g_object_set (s_wsec,
+		              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae",
+		              NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open",
+		              NULL);
 	} else if (   (key_mgmt && !strcmp (key_mgmt, "wpa-psk"))
 	           || (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
 	           || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) {
diff --git a/src/devices/wifi/tests/meson.build b/src/devices/wifi/tests/meson.build
index bb8f7c27..895853c4 100644
--- a/src/devices/wifi/tests/meson.build
+++ b/src/devices/wifi/tests/meson.build
@@ -3,11 +3,12 @@ test_unit = 'test-general'
 exe = executable(
   'wifi-' + test_unit,
   [test_unit + '.c'] + common_sources,
-  dependencies: test_nm_dep
+  dependencies: test_nm_dep,
 )
 
 test(
   'devices/wifi/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/devices/wifi/tests/test-general.c b/src/devices/wifi/tests/test-general.c
index f752bbfc..e0899837 100644
--- a/src/devices/wifi/tests/test-general.c
+++ b/src/devices/wifi/tests/test-general.c
@@ -20,10 +20,7 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "devices/wifi/nm-wifi-utils.h"
-
 #include "nm-core-internal.h"
 
 #include "nm-test-utils-core.h"
diff --git a/src/devices/wwan/libnm-wwan.ver b/src/devices/wwan/libnm-wwan.ver
index 70b954c5..ea966afe 100644
--- a/src/devices/wwan/libnm-wwan.ver
+++ b/src/devices/wwan/libnm-wwan.ver
@@ -6,7 +6,6 @@ global:
 	nm_modem_complete_connection;
 	nm_modem_deactivate;
 	nm_modem_deactivate_async;
-	nm_modem_deactivate_async_finish;
 	nm_modem_device_state_changed;
 	nm_modem_get_capabilities;
 	nm_modem_get_configured_mtu;
diff --git a/src/devices/wwan/meson.build b/src/devices/wwan/meson.build
index 5fe6e433..482dc205 100644
--- a/src/devices/wwan/meson.build
+++ b/src/devices/wwan/meson.build
@@ -1,13 +1,13 @@
 sources = files(
   'nm-modem-broadband.c',
   'nm-modem.c',
-  'nm-modem-manager.c'
+  'nm-modem-manager.c',
 )
 
 deps = [
   libsystemd_dep,
   mm_glib_dep,
-  nm_dep
+  nm_dep,
 ]
 
 if enable_ofono
@@ -25,12 +25,12 @@ libnm_wwan = shared_module(
   ],
   link_depends: linker_script,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 libnm_wwan_dep = declare_dependency(
   include_directories: include_directories('.'),
-  link_with: libnm_wwan
+  link_with: libnm_wwan,
 )
 
 core_plugins += libnm_wwan
@@ -43,7 +43,7 @@ test(
 
 sources = files(
   'nm-device-modem.c',
-  'nm-wwan-factory.c'
+  'nm-wwan-factory.c',
 )
 
 libnm_device_plugin_wwan = shared_module(
@@ -55,7 +55,7 @@ libnm_device_plugin_wwan = shared_module(
   link_depends: linker_script_devices,
   install: true,
   install_dir: nm_plugindir,
-  install_rpath: nm_plugindir
+  install_rpath: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_wwan
@@ -63,7 +63,7 @@ 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
+  depends: libnm_device_plugin_wwan,
 )
 
 # FIXME: check_so_symbols replacement
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index 8ff931aa..1e316280 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -22,9 +22,8 @@
 
 #include "nm-device-modem.h"
 
-#include <string.h>
-
 #include "nm-modem.h"
+#include "nm-ip4-config.h"
 #include "devices/nm-device-private.h"
 #include "nm-rfkill-manager.h"
 #include "settings/nm-settings-connection.h"
@@ -61,7 +60,7 @@ struct _NMDeviceModemClass {
 
 G_DEFINE_TYPE (NMDeviceModem, nm_device_modem, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_MODEM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceModem, NM_IS_DEVICE_MODEM)
+#define NM_DEVICE_MODEM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceModem, NM_IS_DEVICE_MODEM, NMDevice)
 
 /*****************************************************************************/
 
@@ -85,9 +84,9 @@ ppp_failed (NMModem *modem,
 	case NM_DEVICE_STATE_SECONDARIES:
 	case NM_DEVICE_STATE_ACTIVATED:
 		if (nm_device_activate_ip4_state_in_conf (device))
-			nm_device_activate_schedule_ip4_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET);
 		else if (nm_device_activate_ip6_state_in_conf (device))
-			nm_device_activate_schedule_ip6_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET6);
 		else if (nm_device_activate_ip4_state_done (device)) {
 			nm_device_ip_method_failed (device,
 			                            AF_INET,
@@ -213,8 +212,8 @@ modem_ip4_config_result (NMModem *modem,
 		                            AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 	} else {
-		nm_device_set_wwan_ip4_config (device, config);
-		nm_device_activate_schedule_ip4_config_result (device, NULL);
+		nm_device_set_dev2_ip_config (device, AF_INET, NM_IP_CONFIG_CAST (config));
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NULL);
 	}
 }
 
@@ -229,7 +228,7 @@ modem_ip6_config_result (NMModem *modem,
 	NMDevice *device = NM_DEVICE (self);
 	NMActStageReturn ret;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-	NMIP6Config *ignored = NULL;
+	gs_unref_object NMIP6Config *ignored = NULL;
 	gboolean got_config = !!config;
 
 	g_return_if_fail (nm_device_activate_ip6_state_in_conf (device) == TRUE);
@@ -244,14 +243,14 @@ modem_ip6_config_result (NMModem *modem,
 	}
 
 	/* Re-enable IPv6 on the interface */
-	nm_device_ipv6_sysctl_set (device, "disable_ipv6", "0");
+	nm_device_sysctl_ip_conf_set (device, AF_INET6, "disable_ipv6", "0");
 
 	if (config)
-		nm_device_set_wwan_ip6_config (device, config);
+		nm_device_set_dev2_ip_config (device, AF_INET6, NM_IP_CONFIG_CAST (config));
 
 	if (do_slaac == FALSE) {
 		if (got_config)
-			nm_device_activate_schedule_ip6_config_result (device);
+			nm_device_activate_schedule_ip_config_result (device, AF_INET6, NULL);
 		else {
 			_LOGW (LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: SLAAC not requested and no addresses");
 			nm_device_ip_method_failed (device,
@@ -262,15 +261,17 @@ modem_ip6_config_result (NMModem *modem,
 	}
 
 	/* Start SLAAC now that we have a link-local address from the modem */
-	ret = NM_DEVICE_CLASS (nm_device_modem_parent_class)->act_stage3_ip6_config_start (device, &ignored, &failure_reason);
-	g_assert (ignored == NULL);
+	ret = NM_DEVICE_CLASS (nm_device_modem_parent_class)->act_stage3_ip_config_start (device, AF_INET6, (gpointer *) &ignored, &failure_reason);
+
+	nm_assert (ignored == NULL);
+
 	switch (ret) {
 	case NM_ACT_STAGE_RETURN_FAILURE:
 		nm_device_ip_method_failed (device, AF_INET6, failure_reason);
 		break;
 	case NM_ACT_STAGE_RETURN_IP_FAIL:
 		/* all done */
-		nm_device_activate_schedule_ip6_config_result (device);
+		nm_device_activate_schedule_ip_config_result (device, AF_INET6, NULL);
 		break;
 	case NM_ACT_STAGE_RETURN_POSTPONE:
 		/* let SLAAC run */
@@ -279,7 +280,7 @@ modem_ip6_config_result (NMModem *modem,
 		/* Should never get here since we've assured that the IPv6 method
 		 * will either be "auto" or "ignored" when starting IPv6 configuration.
 		 */
-		g_assert_not_reached ();
+		nm_assert_not_reached ();
 	}
 }
 
@@ -303,7 +304,7 @@ ip_ifindex_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
 	 * internally, and leaving it enabled could allow the kernel's IPv6
 	 * RA handling code to run before NM is ready.
 	 */
-	nm_device_ipv6_sysctl_set (device, "disable_ipv6", "1");
+	nm_device_sysctl_ip_conf_set (device, AF_INET6, "disable_ipv6", "1");
 }
 
 static void
@@ -474,7 +475,7 @@ check_connection_available (NMDevice *device,
 	state = nm_modem_get_state (priv->modem);
 	if (state <= NM_MODEM_STATE_INITIALIZING) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "modem not initalized");
+		                            "modem not initialized");
 		return FALSE;
 	}
 
@@ -509,44 +510,35 @@ deactivate (NMDevice *device)
 
 /*****************************************************************************/
 
-static gboolean
-deactivate_async_finish (NMDevice *self,
-                         GAsyncResult *res,
-                         GError **error)
-{
-	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
-}
-
 static void
-modem_deactivate_async_ready (NMModem *modem,
-                              GAsyncResult *res,
-                              GSimpleAsyncResult *simple)
+modem_deactivate_async_cb (NMModem *modem,
+                           GError *error,
+                           gpointer user_data)
 {
-	GError *error = NULL;
+	gs_unref_object NMDevice *self = NULL;
+	NMDeviceDeactivateCallback callback;
+	gpointer callback_user_data;
 
-	if (!nm_modem_deactivate_async_finish (modem, res, &error))
-		g_simple_async_result_take_error (simple, error);
-	g_simple_async_result_complete (simple);
-	g_object_unref (simple);
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
+	callback (self, error, callback_user_data);
 }
 
 static void
 deactivate_async (NMDevice *self,
                   GCancellable *cancellable,
-                  GAsyncReadyCallback callback,
+                  NMDeviceDeactivateCallback callback,
                   gpointer user_data)
 {
-	GSimpleAsyncResult *simple;
+	nm_assert (G_IS_CANCELLABLE (cancellable));
+	nm_assert (callback);
 
-	simple = g_simple_async_result_new (G_OBJECT (self),
-	                                    callback,
-	                                    user_data,
-	                                    deactivate_async);
-	nm_modem_deactivate_async (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) self)->modem,
+	nm_modem_deactivate_async (NM_DEVICE_MODEM_GET_PRIVATE (self)->modem,
 	                           self,
 	                           cancellable,
-	                           (GAsyncReadyCallback) modem_deactivate_async_ready,
-	                           simple);
+	                           modem_deactivate_async_cb,
+	                           nm_utils_user_data_pack (g_object_ref (self),
+	                                                    callback,
+	                                                    user_data));
 }
 
 /*****************************************************************************/
@@ -579,14 +571,25 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
-	return nm_modem_stage3_ip4_config_start (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem,
-	                                         device,
-	                                         NM_DEVICE_CLASS (nm_device_modem_parent_class),
-	                                         out_failure_reason);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
+
+	nm_assert_addr_family (addr_family);
+
+	if (addr_family == AF_INET) {
+		return nm_modem_stage3_ip4_config_start (priv->modem,
+		                                         device,
+		                                         NM_DEVICE_CLASS (nm_device_modem_parent_class),
+		                                         out_failure_reason);
+	} else {
+		return nm_modem_stage3_ip6_config_start (priv->modem,
+		                                         device,
+		                                         out_failure_reason);
+	}
 }
 
 static void
@@ -595,16 +598,6 @@ ip4_config_pre_commit (NMDevice *device, NMIP4Config *config)
 	nm_modem_ip4_pre_commit (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem, device, config);
 }
 
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	return nm_modem_stage3_ip6_config_start (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem,
-	                                         device,
-	                                         out_failure_reason);
-}
-
 static gboolean
 get_ip_iface_identifier (NMDevice *device, NMUtilsIPv6IfaceId *out_iid)
 {
@@ -831,12 +824,10 @@ nm_device_modem_class_init (NMDeviceModemClass *klass)
 	device_class->check_connection_available = check_connection_available;
 	device_class->complete_connection = complete_connection;
 	device_class->deactivate_async = deactivate_async;
-	device_class->deactivate_async_finish = deactivate_async_finish;
 	device_class->deactivate = deactivate;
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->ip4_config_pre_commit = ip4_config_pre_commit;
 	device_class->get_enabled = get_enabled;
 	device_class->set_enabled = set_enabled;
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 04cb8599..1cb549b0 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -22,7 +22,6 @@
 
 #include "nm-modem-broadband.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <libmm-glib.h>
 
@@ -254,16 +253,21 @@ get_bearer_ip_method (MMBearerIpConfig *config)
 static MMSimpleConnectProperties *
 create_cdma_connect_properties (NMConnection *connection)
 {
-	NMSettingCdma *setting;
 	MMSimpleConnectProperties *properties;
-	const char *str;
 
-	setting = nm_connection_get_setting_cdma (connection);
 	properties = mm_simple_connect_properties_new ();
 
-	str = nm_setting_cdma_get_number (setting);
-	if (str)
-		mm_simple_connect_properties_set_number (properties, str);
+#if !MM_CHECK_VERSION (1, 9, 1)
+	{
+		NMSettingCdma *setting;
+		const char *str;
+
+		setting = nm_connection_get_setting_cdma (connection);
+		str = nm_setting_cdma_get_number (setting);
+		if (str)
+			mm_simple_connect_properties_set_number (properties, str);
+	}
+#endif
 
 	return properties;
 }
@@ -279,11 +283,6 @@ create_gsm_connect_properties (NMConnection *connection)
 	setting = nm_connection_get_setting_gsm (connection);
 	properties = mm_simple_connect_properties_new ();
 
-	/* TODO: not needed */
-	str = nm_setting_gsm_get_number (setting);
-	if (str)
-		mm_simple_connect_properties_set_number (properties, str);
-
 	/* Blank APN ("") means the default subscription APN */
 	str = nm_setting_gsm_get_apn (setting);
 	mm_simple_connect_properties_set_apn (properties, str ?: "");
@@ -693,10 +692,6 @@ complete_connection (NMModem *_self,
 			return FALSE;
 		}
 
-		/* TODO: This is not needed */
-		if (!nm_setting_gsm_get_number (s_gsm))
-			g_object_set (G_OBJECT (s_gsm), NM_SETTING_GSM_NUMBER, "*99#", NULL);
-
 		nm_utils_complete_generic (NM_PLATFORM_GET,
 		                           connection,
 		                           NM_SETTING_GSM_SETTING_NAME,
@@ -884,7 +879,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	/* Fully fail if invalid IP address retrieved */
 	address_string = mm_bearer_ip_config_get_address (self->_priv.ipv4_config);
 	if (   !address_string
-	    || !nm_utils_parse_inaddr_bin (AF_INET, address_string, &address_network)) {
+	    || !nm_utils_parse_inaddr_bin (AF_INET, address_string, NULL, &address_network)) {
 		error = g_error_new (NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                     "(%s) retrieving IP4 configuration failed: invalid address given %s%s%s",
@@ -896,7 +891,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	/* Missing gateway not a hard failure */
 	gw_string = mm_bearer_ip_config_get_gateway (self->_priv.ipv4_config);
 	if (   gw_string
-	    && !nm_utils_parse_inaddr_bin (AF_INET, gw_string, &gw)) {
+	    && !nm_utils_parse_inaddr_bin (AF_INET, gw_string, NULL, &gw)) {
 		error = g_error_new (NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                     "(%s) retrieving IP4 configuration failed: invalid gateway address \"%s\"",
@@ -937,7 +932,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	/* DNS servers */
 	dns = mm_bearer_ip_config_get_dns (self->_priv.ipv4_config);
 	for (i = 0; dns && dns[i]; i++) {
-		if (   nm_utils_parse_inaddr_bin (AF_INET, dns[i], &address_network)
+		if (   nm_utils_parse_inaddr_bin (AF_INET, dns[i], NULL, &address_network)
 		    && address_network > 0) {
 			nm_ip4_config_add_nameserver (config, address_network);
 			_LOGI ("  DNS %s", dns[i]);
@@ -1098,100 +1093,101 @@ stage3_ip6_config_request (NMModem *modem, NMDeviceStateReason *out_failure_reas
 
 typedef struct {
 	NMModemBroadband *self;
-	GSimpleAsyncResult *result;
+	_NMModemDisconnectCallback callback;
+	gpointer callback_user_data;
 	GCancellable *cancellable;
 	gboolean warn;
 } DisconnectContext;
 
 static void
-disconnect_context_complete (DisconnectContext *ctx)
+disconnect_context_complete (DisconnectContext *ctx, GError *error)
 {
-	g_simple_async_result_complete_in_idle (ctx->result);
-	if (ctx->cancellable)
-		g_object_unref (ctx->cancellable);
-	g_object_unref (ctx->result);
+	if (ctx->callback)
+		ctx->callback (NM_MODEM (ctx->self), error, ctx->callback_user_data);
+	nm_g_object_unref (ctx->cancellable);
 	g_object_unref (ctx->self);
 	g_slice_free (DisconnectContext, ctx);
 }
 
-static gboolean
-disconnect_finish (NMModem *self,
-                   GAsyncResult *res,
-                   GError **error)
+static void
+disconnect_context_complete_on_idle (gpointer user_data,
+                                     GCancellable *cancellable)
 {
-	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+	DisconnectContext *ctx = user_data;
+	gs_free_error GError *cancelled_error = NULL;
+
+	g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error);
+	disconnect_context_complete (ctx, cancelled_error);
 }
 
 static void
-simple_disconnect_ready (MMModemSimple *modem_iface,
+simple_disconnect_ready (GObject *source_object,
                          GAsyncResult *res,
-                         DisconnectContext *ctx)
+                         gpointer user_data)
 {
+	MMModemSimple *modem_iface = MM_MODEM_SIMPLE (source_object);
+	DisconnectContext *ctx = user_data;
 	GError *error = NULL;
 
 	if (!mm_modem_simple_disconnect_finish (modem_iface, res, &error)) {
-		if (ctx->warn && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
+		if (   ctx->warn
+		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
 			NMModemBroadband *self = ctx->self;
 
 			_LOGW ("failed to disconnect modem: %s",
 			       error->message);
 		}
-		g_simple_async_result_take_error (ctx->result, error);
 	}
 
-	disconnect_context_complete (ctx);
+	disconnect_context_complete (ctx, error);
 }
 
 static void
 disconnect (NMModem *modem,
             gboolean warn,
             GCancellable *cancellable,
-            GAsyncReadyCallback callback,
+            _NMModemDisconnectCallback callback,
             gpointer user_data)
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
 	DisconnectContext *ctx;
-	GError *error = NULL;
 
 	connect_context_clear (self);
-	ctx = g_slice_new (DisconnectContext);
-	ctx->cancellable = NULL;
+
+	ctx = g_slice_new0 (DisconnectContext);
 	ctx->self = g_object_ref (self);
-	ctx->result = g_simple_async_result_new (G_OBJECT (self),
-	                                         callback,
-	                                         user_data,
-	                                         disconnect);
+	ctx->cancellable = nm_g_object_ref (cancellable);
+	ctx->callback = callback;
+	ctx->callback_user_data = user_data;
+
 	/* Don't bother warning on FAILED since the modem is already gone */
 	ctx->warn = warn;
 
-	/* Already cancelled? */
-	if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
-		g_simple_async_result_take_error (ctx->result, error);
-		disconnect_context_complete (ctx);
-		return;
-	}
-
-	/* If no simple iface, we're done */
-	if (!ctx->self->_priv.simple_iface) {
-		disconnect_context_complete (ctx);
+	/* Already cancelled or no simple-iface? We are done. */
+	if (   !ctx->self->_priv.simple_iface
+	    || g_cancellable_is_cancelled (cancellable)) {
+		nm_utils_invoke_on_idle (disconnect_context_complete_on_idle,
+		                         ctx,
+		                         cancellable);
 		return;
 	}
 
 	_LOGD ("notifying ModemManager about the modem disconnection");
-	ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-	mm_modem_simple_disconnect (ctx->self->_priv.simple_iface,
+	mm_modem_simple_disconnect (self->_priv.simple_iface,
 	                            NULL, /* bearer path; if NULL given ALL get disconnected */
 	                            cancellable,
-	                            (GAsyncReadyCallback) simple_disconnect_ready,
+	                            simple_disconnect_ready,
 	                            ctx);
 }
 
 /*****************************************************************************/
 
 static void
-deactivate_cleanup (NMModem *_self, NMDevice *device)
+deactivate_cleanup (NMModem *modem,
+                    NMDevice *device,
+                    gboolean stop_ppp_manager)
 {
-	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
+	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
 
 	/* TODO: cancel SimpleConnect() if any */
 
@@ -1202,8 +1198,9 @@ deactivate_cleanup (NMModem *_self, NMDevice *device)
 
 	self->_priv.pin_tries = 0;
 
-	/* Chain up parent's */
-	NM_MODEM_CLASS (nm_modem_broadband_parent_class)->deactivate_cleanup (_self, device);
+	NM_MODEM_CLASS (nm_modem_broadband_parent_class)->deactivate_cleanup (modem,
+	                                                                      device,
+	                                                                      stop_ppp_manager);
 }
 
 /*****************************************************************************/
@@ -1468,7 +1465,6 @@ nm_modem_broadband_class_init (NMModemBroadbandClass *klass)
 	modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start;
 	modem_class->stage3_ip6_config_request = stage3_ip6_config_request;
 	modem_class->disconnect = disconnect;
-	modem_class->disconnect_finish = disconnect_finish;
 	modem_class->deactivate_cleanup = deactivate_cleanup;
 	modem_class->set_mm_enabled = set_mm_enabled;
 	modem_class->get_user_pass = get_user_pass;
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index dfc102f3..fac14d69 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -24,7 +24,6 @@
 
 #include "nm-modem-manager.h"
 
-#include <string.h>
 #include <libmm-glib.h>
 
 #if HAVE_LIBSYSTEMD
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
index ea668590..78d9a9f0 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -22,8 +22,6 @@
 
 #include "nm-modem-ofono.h"
 
-#include <string.h>
-
 #include "nm-core-internal.h"
 #include "devices/nm-device-private.h"
 #include "nm-modem.h"
@@ -146,30 +144,36 @@ update_modem_state (NMModemOfono *self)
 /* Disconnect */
 typedef struct {
 	NMModemOfono *self;
-	GSimpleAsyncResult *result;
+	_NMModemDisconnectCallback callback;
+	gpointer callback_user_data;
 	GCancellable *cancellable;
 	gboolean warn;
 } DisconnectContext;
 
 static void
-disconnect_context_complete (DisconnectContext *ctx)
+disconnect_context_complete (DisconnectContext *ctx, GError *error)
 {
-	if (ctx->cancellable)
-		g_object_unref (ctx->cancellable);
-	if (ctx->result) {
-		g_simple_async_result_complete_in_idle (ctx->result);
-		g_object_unref (ctx->result);
-	}
+	if (ctx->callback)
+		ctx->callback (NM_MODEM (ctx->self), error, ctx->callback_user_data);
+	nm_g_object_unref (ctx->cancellable);
 	g_object_unref (ctx->self);
 	g_slice_free (DisconnectContext, ctx);
 }
 
-static gboolean
-disconnect_finish (NMModem *self,
-                   GAsyncResult *result,
-                   GError **error)
+static void
+disconnect_context_complete_on_idle (gpointer user_data,
+                                     GCancellable *cancellable)
 {
-	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+	DisconnectContext *ctx = user_data;
+	gs_free_error GError *error = NULL;
+
+	if (!g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+		g_set_error_literal (&error,
+		                     NM_UTILS_ERROR,
+		                     NM_UTILS_ERROR_UNKNOWN,
+		                     ("modem is currently not connected"));
+	}
+	disconnect_context_complete (ctx, error);
 }
 
 static void
@@ -177,16 +181,14 @@ disconnect_done (GObject *source,
                  GAsyncResult *result,
                  gpointer user_data)
 {
-	DisconnectContext *ctx = (DisconnectContext*) user_data;
+	DisconnectContext *ctx = user_data;
 	NMModemOfono *self = ctx->self;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *v = NULL;
 
 	v = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), result, &error);
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-		if (ctx->result)
-			g_simple_async_result_take_error (ctx->result, g_steal_pointer (&error));
-		disconnect_context_complete (ctx);
+		disconnect_context_complete (ctx, error);
 		return;
 	}
 
@@ -196,21 +198,20 @@ disconnect_done (GObject *source,
 	_LOGD ("modem disconnected");
 
 	update_modem_state (self);
-	disconnect_context_complete (ctx);
+	disconnect_context_complete (ctx, error);
 }
 
 static void
 disconnect (NMModem *modem,
             gboolean warn,
             GCancellable *cancellable,
-            GAsyncReadyCallback callback,
+            _NMModemDisconnectCallback callback,
             gpointer user_data)
 {
 	NMModemOfono *self = NM_MODEM_OFONO (modem);
 	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
 	DisconnectContext *ctx;
 	NMModemState state = nm_modem_get_state (NM_MODEM (self));
-	GError *error = NULL;
 
 	_LOGD ("warn: %s modem_state: %s",
 	       warn ? "TRUE" : "FALSE",
@@ -218,37 +219,19 @@ disconnect (NMModem *modem,
 
 	ctx = g_slice_new0 (DisconnectContext);
 	ctx->self = g_object_ref (self);
+	ctx->cancellable = nm_g_object_ref (cancellable);
 	ctx->warn = warn;
-	if (callback) {
-		ctx->result = g_simple_async_result_new (G_OBJECT (self),
-		                                         callback,
-		                                         user_data,
-		                                         disconnect);
-	}
-
-	if (state != NM_MODEM_STATE_CONNECTED) {
-		if (ctx->result) {
-			g_set_error_literal (&error,
-			                     NM_UTILS_ERROR,
-			                     NM_UTILS_ERROR_UNKNOWN,
-			                     ("modem is currently not connected"));
-			g_simple_async_result_take_error (ctx->result, error);
-		}
-		disconnect_context_complete (ctx);
+	ctx->callback = callback;
+	ctx->callback_user_data = user_data;
+
+	if (   state != NM_MODEM_STATE_CONNECTED
+	    || g_cancellable_is_cancelled (cancellable)) {
+		nm_utils_invoke_on_idle (disconnect_context_complete_on_idle,
+		                         ctx,
+		                         cancellable);
 		return;
 	}
 
-	if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
-		if (ctx->result)
-			g_simple_async_result_take_error (ctx->result, error);
-		else
-			g_clear_error (&error);
-		disconnect_context_complete (ctx);
-		return;
-	}
-
-	ctx->cancellable = nm_g_object_ref (cancellable);
-
 	nm_modem_set_state (NM_MODEM (self),
 	                    NM_MODEM_STATE_DISCONNECTING,
 	                    nm_modem_state_to_string (NM_MODEM_STATE_DISCONNECTING));
@@ -266,7 +249,9 @@ disconnect (NMModem *modem,
 }
 
 static void
-deactivate_cleanup (NMModem *modem, NMDevice *device)
+deactivate_cleanup (NMModem *modem,
+                    NMDevice *device,
+                    gboolean stop_ppp_manager)
 {
 	NMModemOfono *self = NM_MODEM_OFONO (modem);
 	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
@@ -275,7 +260,9 @@ deactivate_cleanup (NMModem *modem, NMDevice *device)
 
 	g_clear_object (&priv->ip4_config);
 
-	NM_MODEM_CLASS (nm_modem_ofono_parent_class)->deactivate_cleanup (modem, device);
+	NM_MODEM_CLASS (nm_modem_ofono_parent_class)->deactivate_cleanup (modem,
+	                                                                  device,
+	                                                                  stop_ppp_manager);
 }
 
 static gboolean
@@ -664,7 +651,7 @@ handle_connman_iface (NMModemOfono *self, gboolean found)
 		                          OFONO_DBUS_INTERFACE_CONNECTION_MANAGER,
 		                          priv->connman_proxy_cancellable,
 		                          _connman_proxy_new_cb,
-		                          NULL);
+		                          self);
 	}
 }
 
@@ -883,7 +870,7 @@ context_property_changed (GDBusProxy *proxy,
 		goto out;
 	}
 	if (   !s
-	    || !nm_utils_parse_inaddr_bin (AF_INET, s, &address_network)) {
+	    || !nm_utils_parse_inaddr_bin (AF_INET, s, NULL, &address_network)) {
 		_LOGW ("can't convert 'Address' %s to addr", s ?: "");
 		goto out;
 	}
@@ -897,7 +884,7 @@ context_property_changed (GDBusProxy *proxy,
 		goto out;
 	}
 	if (   !s
-	    || !nm_utils_parse_inaddr_bin (AF_INET, s, &address_network)) {
+	    || !nm_utils_parse_inaddr_bin (AF_INET, s, NULL, &address_network)) {
 		_LOGW ("invalid 'Netmask': %s", s ?: "");
 		goto out;
 	}
@@ -911,7 +898,7 @@ context_property_changed (GDBusProxy *proxy,
 		_LOGW ("Settings 'Gateway' missing");
 		goto out;
 	}
-	if (!nm_utils_parse_inaddr_bin (AF_INET, s, &gateway_network)) {
+	if (!nm_utils_parse_inaddr_bin (AF_INET, s, NULL, &gateway_network)) {
 		_LOGW ("invalid 'Gateway': %s", s);
 		goto out;
 	}
@@ -938,7 +925,7 @@ context_property_changed (GDBusProxy *proxy,
 	}
 	if (array) {
 		for (iter = array; *iter; iter++) {
-			if (   nm_utils_parse_inaddr_bin (AF_INET, *iter, &address_network)
+			if (   nm_utils_parse_inaddr_bin (AF_INET, *iter, NULL, &address_network)
 			    && address_network) {
 				_LOGI ("DNS: %s", *iter);
 				nm_ip4_config_add_nameserver (priv->ip4_config, address_network);
@@ -958,7 +945,7 @@ context_property_changed (GDBusProxy *proxy,
 	if (g_variant_lookup (v_dict, "MessageProxy", "&s", &s)) {
 		_LOGI ("MessageProxy: %s", s);
 		if (   s
-		    && nm_utils_parse_inaddr_bin (AF_INET, s, &address_network)) {
+		    && nm_utils_parse_inaddr_bin (AF_INET, s, NULL, &address_network)) {
 			nm_modem_get_route_parameters (NM_MODEM (self),
 			                               &ip4_route_table,
 			                               &ip4_route_metric,
@@ -1319,7 +1306,6 @@ nm_modem_ofono_class_init (NMModemOfonoClass *klass)
 
 	modem_class->get_capabilities = get_capabilities;
 	modem_class->disconnect = disconnect;
-	modem_class->disconnect_finish = disconnect_finish;
 	modem_class->deactivate_cleanup = deactivate_cleanup;
 	modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem;
 
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index 59b081e9..2217f2a2 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -24,7 +24,6 @@
 #include "nm-modem.h"
 
 #include <fcntl.h>
-#include <string.h>
 #include <termios.h>
 #include <linux/rtnetlink.h>
 
@@ -706,13 +705,13 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 
 	nm_modem_set_route_parameters_from_device (self, device);
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	method = nm_utils_get_ip_config_method (connection, AF_INET);
 
 	/* Only Disabled and Auto methods make sense for WWAN */
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0)
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
 		return NM_ACT_STAGE_RETURN_SUCCESS;
 
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) != 0) {
+	if (!nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
 		_LOGE ("unhandled WWAN IPv4 method '%s'; will fail", method);
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED);
 		return NM_ACT_STAGE_RETURN_FAILURE;
@@ -729,7 +728,7 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 		break;
 	case NM_MODEM_IP_METHOD_AUTO:
 		_LOGD ("MODEM_IP_METHOD_AUTO");
-		ret = device_class->act_stage3_ip4_config_start (device, NULL, out_failure_reason);
+		ret = device_class->act_stage3_ip_config_start (device, AF_INET, NULL, out_failure_reason);
 		break;
 	default:
 		_LOGI ("IPv4 configuration disabled");
@@ -823,13 +822,13 @@ nm_modem_stage3_ip6_config_start (NMModem *self,
 
 	nm_modem_set_route_parameters_from_device (self, device);
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	method = nm_utils_get_ip_config_method (connection, AF_INET6);
 
 	/* Only Ignore and Auto methods make sense for WWAN */
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0)
+	if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
 		return NM_ACT_STAGE_RETURN_IP_DONE;
 
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) != 0) {
+	if (!nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
 		_LOGW ("unhandled WWAN IPv6 method '%s'; will fail",
 		       method);
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
@@ -948,7 +947,7 @@ nm_modem_get_secrets (NMModem *self,
 	                                               FALSE,
 	                                               setting_name,
 	                                               flags,
-	                                               hint,
+	                                               NM_MAKE_STRV (hint),
 	                                               modem_secrets_cb,
 	                                               self);
 	g_return_if_fail (priv->secrets_id);
@@ -986,8 +985,7 @@ nm_modem_act_stage1_prepare (NMModem *self,
 
 	setting_name = nm_connection_need_secrets (connection, &hints);
 	if (!setting_name) {
-		/* Ready to connect */
-		g_assert (!hints);
+		nm_assert (!hints);
 		return NM_MODEM_GET_CLASS (self)->act_stage1_prepare (self, connection, out_failure_reason);
 	}
 
@@ -995,11 +993,14 @@ nm_modem_act_stage1_prepare (NMModem *self,
 	if (priv->secrets_tries++)
 		flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
 
+	if (hints)
+		g_ptr_array_add (hints, NULL);
+
 	priv->secrets_id = nm_act_request_get_secrets (req,
 	                                               FALSE,
 	                                               setting_name,
 	                                               flags,
-	                                               hints ? g_ptr_array_index (hints, 0) : NULL,
+	                                               hints ? (const char *const*) hints->pdata : NULL,
 	                                               modem_secrets_cb,
 	                                               self);
 	g_return_val_if_fail (priv->secrets_id, NM_ACT_STAGE_RETURN_FAILURE);
@@ -1105,7 +1106,9 @@ nm_modem_complete_connection (NMModem *self,
 /*****************************************************************************/
 
 static void
-deactivate_cleanup (NMModem *self, NMDevice *device)
+deactivate_cleanup (NMModem *self,
+                    NMDevice *device,
+                    gboolean stop_ppp_manager)
 {
 	NMModemPrivate *priv;
 	int ifindex;
@@ -1126,7 +1129,8 @@ deactivate_cleanup (NMModem *self, NMDevice *device)
 
 	if (priv->ppp_manager) {
 		g_signal_handlers_disconnect_by_data (priv->ppp_manager, self);
-		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL);
+		if (stop_ppp_manager)
+			nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL, NULL);
 		g_clear_object (&priv->ppp_manager);
 	}
 
@@ -1157,189 +1161,109 @@ deactivate_cleanup (NMModem *self, NMDevice *device)
 
 /*****************************************************************************/
 
-typedef enum {
-	DEACTIVATE_CONTEXT_STEP_FIRST,
-	DEACTIVATE_CONTEXT_STEP_CLEANUP,
-	DEACTIVATE_CONTEXT_STEP_PPP_MANAGER_STOP,
-	DEACTIVATE_CONTEXT_STEP_MM_DISCONNECT,
-	DEACTIVATE_CONTEXT_STEP_LAST
-} DeactivateContextStep;
-
 typedef struct {
 	NMModem *self;
 	NMDevice *device;
 	GCancellable *cancellable;
-	GSimpleAsyncResult *result;
-	DeactivateContextStep step;
-	NMPPPManager *ppp_manager;
-	NMPPPManagerStopHandle *ppp_stop_handle;
-	gulong ppp_stop_cancellable_id;
+	NMModemDeactivateCallback callback;
+	gpointer callback_user_data;
 } DeactivateContext;
 
 static void
-deactivate_context_complete (DeactivateContext *ctx)
+deactivate_context_complete (DeactivateContext *ctx, GError *error)
 {
-	if (ctx->ppp_stop_handle)
-		nm_ppp_manager_stop_cancel (ctx->ppp_stop_handle);
+	NMModem *self = ctx->self;
 
-	nm_assert (!ctx->ppp_stop_handle);
-	nm_assert (ctx->ppp_stop_cancellable_id == 0);
+	_LOGD ("modem deactivation finished %s%s%s",
+	       NM_PRINT_FMT_QUOTED (error, "with failure: ", error->message, "", "successfully"));
 
-	if (ctx->ppp_manager)
-		g_object_unref (ctx->ppp_manager);
-	if (ctx->cancellable)
-		g_object_unref (ctx->cancellable);
-	g_simple_async_result_complete_in_idle (ctx->result);
-	g_object_unref (ctx->result);
+	if (ctx->callback)
+		ctx->callback (ctx->self, error, ctx->callback_user_data);
+	nm_g_object_unref (ctx->cancellable);
 	g_object_unref (ctx->device);
 	g_object_unref (ctx->self);
 	g_slice_free (DeactivateContext, ctx);
 }
 
-gboolean
-nm_modem_deactivate_async_finish (NMModem *self,
-                                  GAsyncResult *res,
-                                  GError **error)
-{
-	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
-}
-
-static void deactivate_step (DeactivateContext *ctx);
-
 static void
-disconnect_ready (NMModem *self,
-                  GAsyncResult *res,
-                  DeactivateContext *ctx)
+_deactivate_call_disconnect_cb (NMModem *self,
+                                GError *error,
+                                gpointer user_data)
 {
-	GError *error = NULL;
-
-	if (!NM_MODEM_GET_CLASS (self)->disconnect_finish (self, res, &error)) {
-		g_simple_async_result_take_error (ctx->result, error);
-		deactivate_context_complete (ctx);
-		return;
-	}
-
-	/* Go on */
-	ctx->step++;
-	deactivate_step (ctx);
+	deactivate_context_complete (user_data, error);
 }
 
 static void
-ppp_manager_stop_ready (NMPPPManager *ppp_manager,
-                        NMPPPManagerStopHandle *handle,
-                        gboolean was_cancelled,
-                        gpointer user_data)
+_deactivate_call_disconnect (DeactivateContext *ctx)
 {
-	DeactivateContext *ctx = user_data;
-
-	nm_assert (ctx->ppp_stop_handle == handle);
-	ctx->ppp_stop_handle = NULL;
-
-	if (ctx->ppp_stop_cancellable_id) {
-		g_cancellable_disconnect (ctx->cancellable,
-		                          nm_steal_int (&ctx->ppp_stop_cancellable_id));
-	}
-
-	if (was_cancelled)
-		return;
-
-	ctx->step++;
-	deactivate_step (ctx);
+	NM_MODEM_GET_CLASS (ctx->self)->disconnect (ctx->self,
+	                                            FALSE,
+	                                            ctx->cancellable,
+	                                            _deactivate_call_disconnect_cb,
+	                                            ctx);
 }
 
 static void
-ppp_manager_stop_cancelled (GCancellable *cancellable,
-                            gpointer user_data)
+_deactivate_ppp_manager_stop_cb (NMPPPManager *ppp_manager,
+                                 NMPPPManagerStopHandle *handle,
+                                 gboolean was_cancelled,
+                                 gpointer user_data)
 {
 	DeactivateContext *ctx = user_data;
 
-	nm_ppp_manager_stop_cancel (ctx->ppp_stop_handle);
-}
+	g_object_unref (ppp_manager);
 
-static void
-deactivate_step (DeactivateContext *ctx)
-{
-	NMModem *self = ctx->self;
-	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
-	GError *error = NULL;
-
-	/* Check cancellable in each step */
-	if (g_cancellable_set_error_if_cancelled (ctx->cancellable, &error)) {
-		g_simple_async_result_take_error (ctx->result, error);
-		deactivate_context_complete (ctx);
-		return;
-	}
-
-	switch (ctx->step) {
-	case DEACTIVATE_CONTEXT_STEP_FIRST:
-		ctx->step++;
-		/* fall through */
-	case DEACTIVATE_CONTEXT_STEP_CLEANUP:
-		/* Make sure we keep a ref to the PPP manager if there is one */
-		if (priv->ppp_manager)
-			ctx->ppp_manager = g_object_ref (priv->ppp_manager);
-		/* Run cleanup */
-		NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, ctx->device);
-		ctx->step++;
-		/* fall through */
-	case DEACTIVATE_CONTEXT_STEP_PPP_MANAGER_STOP:
-		/* If we have a PPP manager, stop it */
-		if (ctx->ppp_manager) {
-			nm_assert (!ctx->ppp_stop_handle);
-			if (ctx->cancellable) {
-				ctx->ppp_stop_cancellable_id = g_cancellable_connect (ctx->cancellable,
-				                                                      G_CALLBACK (ppp_manager_stop_cancelled),
-				                                                      ctx,
-				                                                      NULL);
-			}
-			ctx->ppp_stop_handle = nm_ppp_manager_stop (ctx->ppp_manager,
-			                                            ppp_manager_stop_ready,
-			                                            ctx);
-			return;
-		}
-		ctx->step++;
-		/* fall through */
-	case DEACTIVATE_CONTEXT_STEP_MM_DISCONNECT:
-		/* Disconnect asynchronously */
-		NM_MODEM_GET_CLASS (self)->disconnect (self,
-		                                       FALSE,
-		                                       ctx->cancellable,
-		                                       (GAsyncReadyCallback) disconnect_ready,
-		                                       ctx);
-		return;
+	if (was_cancelled) {
+		gs_free_error GError *error = NULL;
 
-	case DEACTIVATE_CONTEXT_STEP_LAST:
-		_LOGD ("modem deactivation finished");
-		deactivate_context_complete (ctx);
+		if (!g_cancellable_set_error_if_cancelled (ctx->cancellable, &error))
+			nm_assert_not_reached ();
+		deactivate_context_complete (ctx, error);
 		return;
 	}
 
-	g_assert_not_reached ();
+	nm_assert (!g_cancellable_is_cancelled (ctx->cancellable));
+	_deactivate_call_disconnect (ctx);
 }
 
 void
 nm_modem_deactivate_async (NMModem *self,
                            NMDevice *device,
                            GCancellable *cancellable,
-                           GAsyncReadyCallback callback,
+                           NMModemDeactivateCallback callback,
                            gpointer user_data)
 {
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
 	DeactivateContext *ctx;
+	NMPPPManager *ppp_manager;
+
+	g_return_if_fail (NM_IS_MODEM (self));
+	g_return_if_fail (NM_IS_DEVICE (device));
+	g_return_if_fail (G_IS_CANCELLABLE (cancellable));
 
-	ctx = g_slice_new0 (DeactivateContext);
+	ctx = g_slice_new (DeactivateContext);
 	ctx->self = g_object_ref (self);
 	ctx->device = g_object_ref (device);
-	ctx->result = g_simple_async_result_new (G_OBJECT (self),
-	                                         callback,
-	                                         user_data,
-	                                         nm_modem_deactivate_async);
-	/* FIXME(shutdown): we always require a cancellable, otherwise we cannot
-	 * do a coordinated shutdown. */
-	ctx->cancellable = nm_g_object_ref (cancellable);
+	ctx->cancellable = g_object_ref (cancellable);
+	ctx->callback = callback;
+	ctx->callback_user_data = user_data;
+
+	ppp_manager = nm_g_object_ref (priv->ppp_manager);
+
+	NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, ctx->device, FALSE);
+
+	if (ppp_manager) {
+		/* If we have a PPP manager, stop it.
+		 *
+		 * Pass on the reference in @ppp_manager. */
+		nm_ppp_manager_stop (ppp_manager,
+		                     ctx->cancellable,
+		                     _deactivate_ppp_manager_stop_cb,
+		                     ctx);
+		return;
+	}
 
-	/* Start */
-	ctx->step = DEACTIVATE_CONTEXT_STEP_FIRST;
-	deactivate_step (ctx);
+	_deactivate_call_disconnect (ctx);
 }
 
 /*****************************************************************************/
@@ -1348,7 +1272,7 @@ void
 nm_modem_deactivate (NMModem *self, NMDevice *device)
 {
 	/* First cleanup */
-	NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, device);
+	NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, device, TRUE);
 	/* Then disconnect without waiting */
 	NM_MODEM_GET_CLASS (self)->disconnect (self, FALSE, NULL, NULL, NULL);
 }
@@ -1387,7 +1311,7 @@ nm_modem_device_state_changed (NMModem *self,
 			if (new_state == NM_DEVICE_STATE_FAILED || new_state == NM_DEVICE_STATE_DISCONNECTED)
 				warn = FALSE;
 			/* First cleanup */
-			NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, NULL);
+			NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, NULL, TRUE);
 			NM_MODEM_GET_CLASS (self)->disconnect (self, warn, NULL, NULL, NULL);
 		}
 		break;
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index c73745ce..f7b6bfe9 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -109,6 +109,10 @@ struct _NMModem {
 
 typedef struct _NMModem NMModem;
 
+typedef void (*_NMModemDisconnectCallback) (NMModem *modem,
+                                            GError *error,
+                                            gpointer user_data);
+
 typedef struct {
 	GObjectClass parent;
 
@@ -149,13 +153,12 @@ typedef struct {
 	void     (*disconnect)                     (NMModem *self,
 	                                            gboolean warn,
 	                                            GCancellable *cancellable,
-	                                            GAsyncReadyCallback callback,
+	                                            _NMModemDisconnectCallback callback,
 	                                            gpointer user_data);
-	gboolean (*disconnect_finish)              (NMModem *self,
-	                                            GAsyncResult *res,
-	                                            GError **error);
 
-	void     (*deactivate_cleanup)             (NMModem *self, NMDevice *device);
+	void     (*deactivate_cleanup)             (NMModem *self,
+	                                            NMDevice *device,
+	                                            gboolean stop_ppp_manager);
 
 	gboolean (*owns_port)                      (NMModem *self, const char *iface);
 } NMModemClass;
@@ -236,14 +239,15 @@ void nm_modem_get_secrets (NMModem *modem,
 
 void nm_modem_deactivate (NMModem *modem, NMDevice *device);
 
+typedef void (*NMModemDeactivateCallback) (NMModem *self,
+                                           GError *error,
+                                           gpointer user_data);
+
 void     nm_modem_deactivate_async        (NMModem *self,
                                            NMDevice *device,
                                            GCancellable *cancellable,
-                                           GAsyncReadyCallback callback,
+                                           NMModemDeactivateCallback callback,
                                            gpointer user_data);
-gboolean nm_modem_deactivate_async_finish (NMModem *self,
-                                           GAsyncResult *res,
-                                           GError **error);
 
 void nm_modem_device_state_changed (NMModem *modem,
                                     NMDeviceState new_state,
diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c
index a561b58a..c9ee27ff 100644
--- a/src/devices/wwan/nm-wwan-factory.c
+++ b/src/devices/wwan/nm-wwan-factory.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <gmodule.h>
 
 #include "devices/nm-device-factory.h"
diff --git a/src/dhcp/meson.build b/src/dhcp/meson.build
index 76707bca..a5dd3151 100644
--- a/src/dhcp/meson.build
+++ b/src/dhcp/meson.build
@@ -13,7 +13,7 @@ executable(
   link_args: ldflags_linker_script_binary,
   link_depends: linker_script_binary,
   install: true,
-  install_dir: nm_libexecdir
+  install_dir: nm_libexecdir,
 )
 
 if enable_tests
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 37fb18c4..a4fccce0 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -21,10 +21,8 @@
 
 #include "nm-dhcp-client.h"
 
-#include <string.h>
 #include <sys/types.h>
 #include <sys/wait.h>
-#include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -291,12 +289,13 @@ nm_dhcp_client_get_use_fqdn (NMDhcpClient *self)
 /*****************************************************************************/
 
 static const char *state_table[NM_DHCP_STATE_MAX + 1] = {
-	[NM_DHCP_STATE_UNKNOWN]  = "unknown",
-	[NM_DHCP_STATE_BOUND]    = "bound",
-	[NM_DHCP_STATE_TIMEOUT]  = "timeout",
-	[NM_DHCP_STATE_EXPIRE]   = "expire",
-	[NM_DHCP_STATE_DONE]     = "done",
-	[NM_DHCP_STATE_FAIL]     = "fail",
+	[NM_DHCP_STATE_UNKNOWN]    = "unknown",
+	[NM_DHCP_STATE_BOUND]      = "bound",
+	[NM_DHCP_STATE_TIMEOUT]    = "timeout",
+	[NM_DHCP_STATE_EXPIRE]     = "expire",
+	[NM_DHCP_STATE_DONE]       = "done",
+	[NM_DHCP_STATE_FAIL]       = "fail",
+	[NM_DHCP_STATE_TERMINATED] = "terminated",
 };
 
 static const char *
@@ -452,7 +451,6 @@ daemon_watch_cb (GPid pid, int status, gpointer user_data)
 {
 	NMDhcpClient *self = NM_DHCP_CLIENT (user_data);
 	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
-	NMDhcpState new_state;
 
 	g_return_if_fail (priv->watch_id);
 	priv->watch_id = 0;
@@ -468,14 +466,9 @@ daemon_watch_cb (GPid pid, int status, gpointer user_data)
 	else
 		_LOGW ("client died abnormally");
 
-	if (!WIFEXITED (status))
-		new_state = NM_DHCP_STATE_FAIL;
-	else
-		new_state = NM_DHCP_STATE_DONE;
-
 	priv->pid = -1;
 
-	nm_dhcp_client_set_state (self, new_state, NULL, NULL);
+	nm_dhcp_client_set_state (self, NM_DHCP_STATE_TERMINATED, NULL, NULL);
 }
 
 void
@@ -632,7 +625,7 @@ out:
 		int errsv = errno;
 
 		nm_log_dbg (LOGD_DHCP, "dhcp: could not remove pid file \"%s\": %s (%d)",
-		            pid_file, g_strerror (errsv), errsv);
+		            pid_file, nm_strerror_native (errsv), errsv);
 	}
 }
 
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 8be50717..1db7eac6 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -57,6 +57,7 @@ typedef enum {
 	NM_DHCP_STATE_DONE,         /* client quit or stopped */
 	NM_DHCP_STATE_EXPIRE,       /* lease expired or NAKed */
 	NM_DHCP_STATE_FAIL,         /* failed for some reason */
+	NM_DHCP_STATE_TERMINATED,   /* client is no longer running */
 	__NM_DHCP_STATE_MAX,
 	NM_DHCP_STATE_MAX = __NM_DHCP_STATE_MAX - 1,
 } NMDhcpState;
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index be8d06d9..cbd706fa 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -21,7 +21,6 @@
 
 #include "nm-dhcp-dhclient-utils.h"
 
-#include <string.h>
 #include <ctype.h>
 #include <arpa/inet.h>
 #include <net/if.h>
@@ -427,6 +426,8 @@ nm_dhcp_dhclient_create_config (const char *interface,
 		add_hostname6 (new_contents, hostname);
 		add_request (reqs, "dhcp6.name-servers");
 		add_request (reqs, "dhcp6.domain-search");
+
+		/* FIXME: internal client does not support requesting client-id option. Does this even work? */
 		add_request (reqs, "dhcp6.client-id");
 	}
 
@@ -493,7 +494,7 @@ nm_dhcp_dhclient_escape_duid (GBytes *duid)
 	return escaped;
 }
 
-static inline gboolean
+static gboolean
 isoctal (const guint8 *p)
 {
 	return (   p[0] >= '0' && p[0] <= '3'
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index 0146c8b4..af702cb4 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -29,9 +29,7 @@
 
 #if WITH_DHCLIENT
 
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <netinet/in.h>
@@ -593,17 +591,19 @@ stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
+	int errsv;
 
 	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->stop (client, release);
 
 	if (priv->conf_file)
-		if (remove (priv->conf_file) == -1)
-			_LOGD ("could not remove dhcp config file \"%s\": %d (%s)", priv->conf_file, errno, g_strerror (errno));
+		if (remove (priv->conf_file) == -1) {
+			errsv = errno;
+			_LOGD ("could not remove dhcp config file \"%s\": %d (%s)", priv->conf_file, errsv, nm_strerror_native (errsv));
+		}
 	if (priv->pid_file) {
 		if (remove (priv->pid_file) == -1) {
-			int errsv = errno;
-
-			_LOGD ("could not remove dhcp pid file \"%s\": %s (%d)", priv->pid_file, g_strerror (errsv), errsv);
+			errsv = errno;
+			_LOGD ("could not remove dhcp pid file \"%s\": %s (%d)", priv->pid_file, nm_strerror_native (errsv), errsv);
 		}
 		nm_clear_g_free (&priv->pid_file);
 	}
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
index 0f033e22..868cc9dd 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -22,9 +22,7 @@
 
 #if WITH_DHCPCANON
 
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 
 #include "nm-utils.h"
@@ -205,12 +203,15 @@ stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
 	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
+	int errsv;
 
 	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcanon_parent_class)->stop (client, release);
 
 	if (priv->pid_file) {
-		if (remove (priv->pid_file) == -1)
-			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errno, g_strerror (errno));
+		if (remove (priv->pid_file) == -1) {
+			errsv = errno;
+			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errsv, nm_strerror_native (errsv));
+		}
 		g_free (priv->pid_file);
 		priv->pid_file = NULL;
 	}
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index e2a1354f..2a7482b1 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -24,9 +24,7 @@
 
 #if WITH_DHCPCD
 
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <netinet/in.h>
@@ -199,12 +197,15 @@ stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
 	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (self);
+	int errsv;
 
 	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcd_parent_class)->stop (client, release);
 
 	if (priv->pid_file) {
-		if (remove (priv->pid_file) == -1)
-			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errno, g_strerror (errno));
+		if (remove (priv->pid_file) == -1) {
+			errsv = errno;
+			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errsv, nm_strerror_native (errsv));
+		}
 	}
 
 	/* FIXME: implement release... */
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c
index 7f1d2a7b..8f753a61 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -22,7 +22,6 @@
 
 #include <unistd.h>
 #include <stdlib.h>
-#include <string.h>
 #include <signal.h>
 
 #include "nm-utils/nm-vpn-plugin-macros.h"
@@ -190,7 +189,7 @@ do_notify:
 		_LOGW ("failure to call notify: %s (try signal via Event)", error->message);
 		g_clear_error (&error);
 
-		/* for backward compatibilty, try to emit the signal. There is no stable
+		/* for backward compatibility, try to emit the signal. There is no stable
 		 * API between the dhcp-helper and NetworkManager. However, while upgrading
 		 * the NetworkManager package, a newer helper might want to notify an
 		 * older server, which still uses the "Event". */
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index 1770ead3..049c4e55 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -24,9 +24,7 @@
 #include <sys/socket.h>
 #include <sys/wait.h>
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 
 #include "nm-dhcp-helper-api.h"
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index a51c6e38..7063c82c 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -27,9 +27,7 @@
 #include <sys/socket.h>
 #include <sys/wait.h>
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <stdio.h>
@@ -239,7 +237,7 @@ client_start (NMDhcpManager *self,
 	 *
 	 * - for IPv4, the calling code may determine a client-id (from NM's connection profile).
 	 *   If present, it is taken. If not present, the DHCP plugin uses a plugin specific default.
-	 *     - for "internal" plugin, the default is just "duid".
+	 *     - for "internal" plugin, the default is just "mac".
 	 *     - for "dhclient", we try to get the configuration from dhclient's /etc/dhcp or fallback
 	 *       to whatever dhclient uses by default.
 	 *   We do it this way, because for dhclient the user may configure a default
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 5c60af5f..70ed8715 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -18,9 +18,7 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <netinet/in.h>
@@ -98,63 +96,77 @@ G_DEFINE_TYPE (NMDhcpSystemd, nm_dhcp_systemd, NM_TYPE_DHCP_CLIENT)
 #define DHCP6_OPTION_IAID            1034
 
 typedef struct {
-	guint num;
 	const char *name;
-	gboolean include;
+	uint16_t option_num;
+	bool include;
 } ReqOption;
 
 #define REQPREFIX "requested_"
 
+#define REQ(_num, _name, _include) \
+	{ \
+		.name = REQPREFIX""_name, \
+		.option_num = _num, \
+		.include = _include, \
+	}
+
 static const ReqOption dhcp4_requests[] = {
-	{ SD_DHCP_OPTION_SUBNET_MASK,                    REQPREFIX "subnet_mask",                     TRUE },
-	{ SD_DHCP_OPTION_TIME_OFFSET,                    REQPREFIX "time_offset",                     TRUE },
-	{ SD_DHCP_OPTION_ROUTER,                         REQPREFIX "routers",                         TRUE },
-	{ SD_DHCP_OPTION_DOMAIN_NAME_SERVER,             REQPREFIX "domain_name_servers",             TRUE },
-	{ SD_DHCP_OPTION_HOST_NAME,                      REQPREFIX "host_name",                       TRUE },
-	{ SD_DHCP_OPTION_DOMAIN_NAME,                    REQPREFIX "domain_name",                     TRUE },
-	{ SD_DHCP_OPTION_INTERFACE_MTU,                  REQPREFIX "interface_mtu",                   TRUE },
-	{ SD_DHCP_OPTION_BROADCAST,                      REQPREFIX "broadcast_address",               TRUE },
-	{ SD_DHCP_OPTION_STATIC_ROUTE,                   REQPREFIX "static_routes",                   TRUE },
-	{ DHCP_OPTION_NIS_DOMAIN,                        REQPREFIX "nis_domain",                      TRUE },
-	{ DHCP_OPTION_NIS_SERVERS,                       REQPREFIX "nis_servers",                     TRUE },
-	{ SD_DHCP_OPTION_NTP_SERVER,                     REQPREFIX "ntp_servers",                     TRUE },
-	{ SD_DHCP_OPTION_SERVER_IDENTIFIER,              REQPREFIX "dhcp_server_identifier",          TRUE },
-	{ SD_DHCP_OPTION_DOMAIN_SEARCH_LIST,             REQPREFIX "domain_search",                   TRUE },
-	{ SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,         REQPREFIX "rfc3442_classless_static_routes", TRUE },
-	{ SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, REQPREFIX "ms_classless_static_routes",      TRUE },
-	{ SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,    REQPREFIX "wpad",                            TRUE },
-	{ SD_DHCP_OPTION_ROOT_PATH,                      REQPREFIX "root_path",                       TRUE },
+	REQ (SD_DHCP_OPTION_SUBNET_MASK,                    "subnet_mask",                     TRUE ),
+	REQ (SD_DHCP_OPTION_TIME_OFFSET,                    "time_offset",                     TRUE ),
+	REQ (SD_DHCP_OPTION_DOMAIN_NAME_SERVER,             "domain_name_servers",             TRUE ),
+	REQ (SD_DHCP_OPTION_HOST_NAME,                      "host_name",                       TRUE ),
+	REQ (SD_DHCP_OPTION_DOMAIN_NAME,                    "domain_name",                     TRUE ),
+	REQ (SD_DHCP_OPTION_INTERFACE_MTU,                  "interface_mtu",                   TRUE ),
+	REQ (SD_DHCP_OPTION_BROADCAST,                      "broadcast_address",               TRUE ),
+
+	/* RFC 3442: The Classless Static Routes option code MUST appear in the parameter
+	 *   request list prior to both the Router option code and the Static
+	 *   Routes option code, if present. */
+	REQ (SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,         "rfc3442_classless_static_routes", TRUE ),
+	REQ (SD_DHCP_OPTION_ROUTER,                         "routers",                         TRUE ),
+	REQ (SD_DHCP_OPTION_STATIC_ROUTE,                   "static_routes",                   TRUE ),
+
+	REQ (DHCP_OPTION_NIS_DOMAIN,                        "nis_domain",                      TRUE ),
+	REQ (DHCP_OPTION_NIS_SERVERS,                       "nis_servers",                     TRUE ),
+	REQ (SD_DHCP_OPTION_NTP_SERVER,                     "ntp_servers",                     TRUE ),
+	REQ (SD_DHCP_OPTION_SERVER_IDENTIFIER,              "dhcp_server_identifier",          TRUE ),
+	REQ (SD_DHCP_OPTION_DOMAIN_SEARCH_LIST,             "domain_search",                   TRUE ),
+	REQ (SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, "ms_classless_static_routes",      TRUE ),
+	REQ (SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,    "wpad",                            TRUE ),
+	REQ (SD_DHCP_OPTION_ROOT_PATH,                      "root_path",                       TRUE ),
 
 	/* Internal values */
-	{ SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,          REQPREFIX "expiry",                          FALSE },
-	{ SD_DHCP_OPTION_CLIENT_IDENTIFIER,              REQPREFIX "dhcp_client_identifier",          FALSE },
-	{ DHCP_OPTION_IP_ADDRESS,                        REQPREFIX "ip_address",                      FALSE },
-	{ 0, NULL, FALSE }
+	REQ (SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,          "expiry",                          FALSE ),
+	REQ (SD_DHCP_OPTION_CLIENT_IDENTIFIER,              "dhcp_client_identifier",          FALSE ),
+	REQ (DHCP_OPTION_IP_ADDRESS,                        "ip_address",                      FALSE ),
+
+	{ 0 }
 };
 
 static const ReqOption dhcp6_requests[] = {
-	{ SD_DHCP6_OPTION_CLIENTID,                      REQPREFIX "dhcp6_client_id",     TRUE },
+	REQ (SD_DHCP6_OPTION_CLIENTID,                      "dhcp6_client_id",     FALSE ),
 
 	/* Don't request server ID by default; some servers don't reply to
 	 * Information Requests that request the Server ID.
 	 */
-	{ SD_DHCP6_OPTION_SERVERID,                      REQPREFIX "dhcp6_server_id",     FALSE },
+	REQ (SD_DHCP6_OPTION_SERVERID,                      "dhcp6_server_id",     FALSE ),
 
-	{ SD_DHCP6_OPTION_DNS_SERVERS,                   REQPREFIX "dhcp6_name_servers",  TRUE },
-	{ SD_DHCP6_OPTION_DOMAIN_LIST,                   REQPREFIX "dhcp6_domain_search", TRUE },
-	{ SD_DHCP6_OPTION_SNTP_SERVERS,                  REQPREFIX "dhcp6_sntp_servers",  TRUE },
+	REQ (SD_DHCP6_OPTION_DNS_SERVERS,                   "dhcp6_name_servers",  TRUE ),
+	REQ (SD_DHCP6_OPTION_DOMAIN_LIST,                   "dhcp6_domain_search", TRUE ),
+	REQ (SD_DHCP6_OPTION_SNTP_SERVERS,                  "dhcp6_sntp_servers",  TRUE ),
 
 	/* Internal values */
-	{ DHCP6_OPTION_IP_ADDRESS,                       REQPREFIX "ip6_address",         FALSE },
-	{ DHCP6_OPTION_PREFIXLEN,                        REQPREFIX "ip6_prefixlen",       FALSE },
-	{ DHCP6_OPTION_PREFERRED_LIFE,                   REQPREFIX "preferred_life",      FALSE },
-	{ DHCP6_OPTION_MAX_LIFE,                         REQPREFIX "max_life",            FALSE },
-	{ DHCP6_OPTION_STARTS,                           REQPREFIX "starts",              FALSE },
-	{ DHCP6_OPTION_LIFE_STARTS,                      REQPREFIX "life_starts",         FALSE },
-	{ DHCP6_OPTION_RENEW,                            REQPREFIX "renew",               FALSE },
-	{ DHCP6_OPTION_REBIND,                           REQPREFIX "rebind",              FALSE },
-	{ DHCP6_OPTION_IAID,                             REQPREFIX "iaid",                FALSE },
-	{ 0, NULL, FALSE }
+	REQ (DHCP6_OPTION_IP_ADDRESS,                       "ip6_address",         FALSE ),
+	REQ (DHCP6_OPTION_PREFIXLEN,                        "ip6_prefixlen",       FALSE ),
+	REQ (DHCP6_OPTION_PREFERRED_LIFE,                   "preferred_life",      FALSE ),
+	REQ (DHCP6_OPTION_MAX_LIFE,                         "max_life",            FALSE ),
+	REQ (DHCP6_OPTION_STARTS,                           "starts",              FALSE ),
+	REQ (DHCP6_OPTION_LIFE_STARTS,                      "life_starts",         FALSE ),
+	REQ (DHCP6_OPTION_RENEW,                            "renew",               FALSE ),
+	REQ (DHCP6_OPTION_REBIND,                           "rebind",              FALSE ),
+	REQ (DHCP6_OPTION_IAID,                             "iaid",                FALSE ),
+
+	{ 0 }
 };
 
 static void
@@ -165,18 +177,22 @@ take_option (GHashTable *options,
 {
 	guint i;
 
-	g_return_if_fail (value != NULL);
+	nm_assert (options);
+	nm_assert (requests);
+	nm_assert (value);
 
 	for (i = 0; requests[i].name; i++) {
-		if (requests[i].num == option) {
+		nm_assert (g_str_has_prefix (requests[i].name, REQPREFIX));
+		if (requests[i].option_num == option) {
 			g_hash_table_insert (options,
 			                     (gpointer) (requests[i].name + NM_STRLEN (REQPREFIX)),
 			                     value);
-			break;
+			return;
 		}
 	}
+
 	/* Option should always be found */
-	g_assert (requests[i].name);
+	nm_assert_not_reached ();
 }
 
 static void
@@ -187,13 +203,6 @@ add_option (GHashTable *options, const ReqOption *requests, guint option, const
 }
 
 static void
-add_option_u32 (GHashTable *options, const ReqOption *requests, guint option, guint32 value)
-{
-	if (options)
-		take_option (options, requests, option, g_strdup_printf ("%u", value));
-}
-
-static void
 add_option_u64 (GHashTable *options, const ReqOption *requests, guint option, guint64 value)
 {
 	if (options)
@@ -205,12 +214,21 @@ add_requests_to_options (GHashTable *options, const ReqOption *requests)
 {
 	guint i;
 
-	for (i = 0; options && requests[i].name; i++) {
+	if (!options)
+		return;
+
+	for (i = 0; requests[i].name; i++) {
 		if (requests[i].include)
 			g_hash_table_insert (options, (gpointer) requests[i].name, g_strdup ("1"));
 	}
 }
 
+static GHashTable *
+create_options_dict (void)
+{
+	return g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
+}
+
 #define LOG_LEASE(domain, ...) \
 G_STMT_START { \
 	if (log_lease) { \
@@ -223,231 +241,322 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
                      const char *iface,
                      int ifindex,
                      sd_dhcp_lease *lease,
-                     GHashTable *options,
                      guint32 route_table,
                      guint32 route_metric,
                      gboolean log_lease,
+                     GHashTable **out_options,
                      GError **error)
 {
-	NMIP4Config *ip4_config = NULL;
-	struct in_addr tmp_addr;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	gs_unref_hashtable GHashTable *options = NULL;
 	const struct in_addr *addr_list;
-	char buf[INET_ADDRSTRLEN];
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
 	const char *s;
-	guint32 lifetime = 0, i;
-	NMPlatformIP4Address address;
 	nm_auto_free_gstring GString *str = NULL;
 	gs_free sd_dhcp_route **routes = NULL;
 	const char *const*search_domains = NULL;
 	guint16 mtu;
-	int r, num;
-	guint64 end_time;
+	int i, num;
 	const void *data;
 	gsize data_len;
 	gboolean metered = FALSE;
-	gboolean static_default_gateway = FALSE;
-	gboolean gateway_has = FALSE;
-	in_addr_t gateway = 0;
+	gboolean has_router_from_classless = FALSE;
+	gboolean has_classless_route = FALSE;
+	gboolean has_static_route = FALSE;
+	const gint32 ts = nm_utils_get_monotonic_timestamp_s ();
+	gint64 ts_time = time (NULL);
+	struct in_addr a_address;
+	struct in_addr a_netmask;
+	const struct in_addr *a_router;
+	guint32 a_plen;
+	guint32 a_lifetime;
 
 	g_return_val_if_fail (lease != NULL, NULL);
 
+	if (sd_dhcp_lease_get_address (lease, &a_address) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get address from lease");
+		return NULL;
+	}
+
+	if (sd_dhcp_lease_get_netmask (lease, &a_netmask) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get netmask from lease");
+		return NULL;
+	}
+
+	if (sd_dhcp_lease_get_lifetime (lease, &a_lifetime) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get lifetime from lease");
+		return NULL;
+	}
+
 	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
 
-	/* Address */
-	sd_dhcp_lease_get_address (lease, &tmp_addr);
-	memset (&address, 0, sizeof (address));
-	address.address = tmp_addr.s_addr;
-	address.peer_address = tmp_addr.s_addr;
-	s = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
-	LOG_LEASE (LOGD_DHCP4, "address %s", s);
-	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, s);
-
-	/* Prefix/netmask */
-	sd_dhcp_lease_get_netmask (lease, &tmp_addr);
-	address.plen = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr);
-	LOG_LEASE (LOGD_DHCP4, "plen %d", address.plen);
+	options = out_options ? create_options_dict () : NULL;
+
+	nm_utils_inet4_ntop (a_address.s_addr, addr_str);
+	LOG_LEASE (LOGD_DHCP4, "address %s", addr_str);
+	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, addr_str);
+
+	a_plen = nm_utils_ip4_netmask_to_prefix (a_netmask.s_addr);
+	LOG_LEASE (LOGD_DHCP4, "plen %u", (guint) a_plen);
 	add_option (options,
 	            dhcp4_requests,
 	            SD_DHCP_OPTION_SUBNET_MASK,
-	            nm_utils_inet4_ntop (tmp_addr.s_addr, NULL));
-
-	/* Lease time */
-	sd_dhcp_lease_get_lifetime (lease, &lifetime);
-	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
-	address.lifetime = address.preferred = lifetime;
-	end_time = (guint64) time (NULL) + lifetime;
-	LOG_LEASE (LOGD_DHCP4, "expires in %" G_GUINT32_FORMAT " seconds", lifetime);
+	            nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
+
+	LOG_LEASE (LOGD_DHCP4, "expires in %u seconds (at %lld)",
+	           (guint) a_lifetime,
+	           (long long) (ts_time + a_lifetime));
 	add_option_u64 (options,
 	                dhcp4_requests,
 	                SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
-	                end_time);
-
-	address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
-	nm_ip4_config_add_address (ip4_config, &address);
+	                (guint64) (ts_time + a_lifetime));
+
+	nm_ip4_config_add_address (ip4_config,
+	                           &((const NMPlatformIP4Address) {
+	                               .address      = a_address.s_addr,
+	                               .peer_address = a_address.s_addr,
+	                               .plen         = a_plen,
+	                               .addr_source  = NM_IP_CONFIG_SOURCE_DHCP,
+	                               .timestamp    = ts,
+	                               .lifetime     = a_lifetime,
+	                               .preferred    = a_lifetime,
+	                           }));
 
-	/* DNS Servers */
 	num = sd_dhcp_lease_get_dns (lease, &addr_list);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
-			if (addr_list[i].s_addr) {
-				nm_ip4_config_add_nameserver (ip4_config, addr_list[i].s_addr);
-				s = nm_utils_inet4_ntop (addr_list[i].s_addr, NULL);
-				LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", s);
-				g_string_append_printf (str, "%s%s", str->len ? " " : "", s);
+			nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
+			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
+
+			if (   addr_list[i].s_addr == 0
+			    || nm_ip4_addr_is_localhost (addr_list[i].s_addr)) {
+				/* Skip localhost addresses, like also networkd does.
+				 * See https://github.com/systemd/systemd/issues/4524. */
+				continue;
 			}
+			nm_ip4_config_add_nameserver (ip4_config, addr_list[i].s_addr);
 		}
-		if (str->len)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, str->str);
+		LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", str->str);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, str->str);
 	}
 
-	/* Search domains */
 	num = sd_dhcp_lease_get_search_domains (lease, (char ***) &search_domains);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
+			g_string_append (nm_gstring_add_space_delimiter (str), search_domains[i]);
 			nm_ip4_config_add_search (ip4_config, search_domains[i]);
-			g_string_append_printf (str, "%s%s", str->len ? " " : "", search_domains[i]);
-			LOG_LEASE (LOGD_DHCP4, "domain search '%s'", search_domains[i]);
 		}
+		LOG_LEASE (LOGD_DHCP4, "domain search '%s'", str->str);
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_SEARCH_LIST, str->str);
 	}
 
-	/* Domain Name */
-	r = sd_dhcp_lease_get_domainname (lease, &s);
-	if (r == 0) {
-		/* Multiple domains sometimes stuffed into option 15 "Domain Name".
-		 * As systemd escapes such characters, split them at \\032. */
-		char **domains = g_strsplit (s, "\\032", 0);
+	if (sd_dhcp_lease_get_domainname (lease, &s) >= 0) {
+		gs_strfreev char **domains = NULL;
 		char **d;
 
-		for (d = domains; *d; d++) {
-			LOG_LEASE (LOGD_DHCP4, "domain name '%s'", *d);
-			nm_ip4_config_add_domain (ip4_config, *d);
-		}
-		g_strfreev (domains);
+		LOG_LEASE (LOGD_DHCP4, "domain name '%s'", s);
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME, s);
+
+		/* Multiple domains sometimes stuffed into option 15 "Domain Name".
+		 * As systemd escapes such characters, split them at \\032. */
+		domains = g_strsplit (s, "\\032", 0);
+		for (d = domains; *d; d++)
+			nm_ip4_config_add_domain (ip4_config, *d);
 	}
 
-	/* Hostname */
-	r = sd_dhcp_lease_get_hostname (lease, &s);
-	if (r == 0) {
+	if (sd_dhcp_lease_get_hostname (lease, &s) >= 0) {
 		LOG_LEASE (LOGD_DHCP4, "hostname '%s'", s);
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_HOST_NAME, s);
 	}
 
-	/* Routes */
 	num = sd_dhcp_lease_get_routes (lease, &routes);
 	if (num > 0) {
-		nm_gstring_prepare (&str);
+		nm_auto_free_gstring GString *str_classless = NULL;
+		nm_auto_free_gstring GString *str_static = NULL;
+		guint32 default_route_metric = route_metric;
+
 		for (i = 0; i < num; i++) {
-			NMPlatformIP4Route route = { 0 };
-			const char *gw_str;
-			guint8 plen;
-			struct in_addr a;
+			switch (sd_dhcp_route_get_option (routes[i])) {
+			case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
+				has_classless_route = TRUE;
+				break;
+			case SD_DHCP_OPTION_STATIC_ROUTE:
+				has_static_route = TRUE;
+				break;
+			}
+		}
 
-			if (sd_dhcp_route_get_destination (routes[i], &a) < 0)
+		if (has_classless_route)
+			str_classless = g_string_sized_new (30);
+		if (has_static_route)
+			str_static = g_string_sized_new (30);
+
+		for (i = 0; i < num; i++) {
+			char network_net_str[NM_UTILS_INET_ADDRSTRLEN];
+			char gateway_str[NM_UTILS_INET_ADDRSTRLEN];
+			guint8 r_plen;
+			struct in_addr r_network;
+			struct in_addr r_gateway;
+			in_addr_t network_net;
+			int option;
+			guint32 m;
+
+			option = sd_dhcp_route_get_option (routes[i]);
+			if (!NM_IN_SET (option, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,
+			                        SD_DHCP_OPTION_STATIC_ROUTE))
 				continue;
 
-			if (   sd_dhcp_route_get_destination_prefix_length (routes[i], &plen) < 0
-			    || plen > 32)
+			if (sd_dhcp_route_get_destination (routes[i], &r_network) < 0)
+				continue;
+			if (   sd_dhcp_route_get_destination_prefix_length (routes[i], &r_plen) < 0
+			    || r_plen > 32)
+				continue;
+			if (sd_dhcp_route_get_gateway (routes[i], &r_gateway) < 0)
 				continue;
 
-			route.plen = plen;
-			route.network = nm_utils_ip4_address_clear_host_address (a.s_addr, plen);
+			network_net = nm_utils_ip4_address_clear_host_address (r_network.s_addr,
+			                                                       r_plen);
+			nm_utils_inet4_ntop (network_net, network_net_str);
+			nm_utils_inet4_ntop (r_gateway.s_addr, gateway_str);
+
+			LOG_LEASE (LOGD_DHCP4,
+			           "%sstatic route %s/%d gw %s",
+			             option == SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE
+			           ? "classless "
+			           : "",
+			           network_net_str,
+			           (int) r_plen,
+			           gateway_str);
+			g_string_append_printf (nm_gstring_add_space_delimiter (  option == SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE
+			                                                        ? str_classless
+			                                                        : str_static),
+			                        "%s/%d %s",
+			                        network_net_str,
+			                        (int) r_plen,
+			                        gateway_str);
+
+			if (   option == SD_DHCP_OPTION_STATIC_ROUTE
+			    && has_classless_route) {
+				/* RFC 3443: if the DHCP server returns both a Classless Static Routes
+				 * option and a Static Routes option, the DHCP client MUST ignore the
+				 * Static Routes option. */
+				continue;
+			}
 
-			if (sd_dhcp_route_get_gateway (routes[i], &a) < 0)
+			if (   r_plen == 0
+			    && option == SD_DHCP_OPTION_STATIC_ROUTE) {
+				/* for option 33 (static route), RFC 2132 says:
+				 *
+				 * The default route (0.0.0.0) is an illegal destination for a static
+				 * route. */
 				continue;
-			route.gateway = a.s_addr;
-
-			if (route.plen) {
-				route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
-				route.metric = route_metric;
-				route.table_coerced = nm_platform_route_table_coerce (route_table);
-				nm_ip4_config_add_route (ip4_config, &route, NULL);
-
-				s = nm_utils_inet4_ntop (route.network, buf);
-				gw_str = nm_utils_inet4_ntop (route.gateway, NULL);
-				LOG_LEASE (LOGD_DHCP4, "static route %s/%d gw %s", s, route.plen, gw_str);
-
-				g_string_append_printf (str, "%s%s/%d %s", str->len ? " " : "", s, route.plen, gw_str);
-			} else {
-				if (!static_default_gateway) {
-					static_default_gateway = TRUE;
-					gateway_has = TRUE;
-					gateway = route.gateway;
-
-					s = nm_utils_inet4_ntop (route.gateway, NULL);
-					LOG_LEASE (LOGD_DHCP4, "gateway %s", s);
-					add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, s);
-				}
 			}
-		}
-		if (str->len)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, str->str);
-	}
 
-	/* If the DHCP server returns both a Classless Static Routes option and a
-	 * Router option, the DHCP client MUST ignore the Router option [RFC 3442].
-	 * Be more lenient and ignore the Router option only if Classless Static
-	 * Routes contain a default gateway (as other DHCP backends do).
-	 */
-	/* Gateway */
-	if (!static_default_gateway) {
-		r = sd_dhcp_lease_get_router (lease, &tmp_addr);
-		if (r == 0) {
-			gateway_has = TRUE;
-			gateway = tmp_addr.s_addr;
-			s = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
-			LOG_LEASE (LOGD_DHCP4, "gateway %s", s);
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, s);
+			if (r_plen == 0) {
+				/* if there are multiple default routes, we add them with differing
+				 * metrics. */
+				m = default_route_metric;
+				if (default_route_metric < G_MAXUINT32)
+					default_route_metric++;
+
+				has_router_from_classless = TRUE;
+			} else
+				m = route_metric;
+
+			nm_ip4_config_add_route (ip4_config,
+			                         &((const NMPlatformIP4Route) {
+			                             .network       = network_net,
+			                             .plen          = r_plen,
+			                             .gateway       = r_gateway.s_addr,
+			                             .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
+			                             .metric        = m,
+			                             .table_coerced = nm_platform_route_table_coerce (route_table),
+			                         }),
+			                         NULL);
 		}
+
+		if (str_classless && str_classless->len > 0)
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, str_classless->str);
+		if (str_static && str_static->len > 0)
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_STATIC_ROUTE, str_static->str);
 	}
 
-	if (gateway_has) {
-		const NMPlatformIP4Route rt = {
-			.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
-			.gateway = gateway,
-			.table_coerced = nm_platform_route_table_coerce (route_table),
-			.metric = route_metric,
-		};
+	num = sd_dhcp_lease_get_router (lease, &a_router);
+	if (num > 0) {
+		guint32 default_route_metric = route_metric;
+
+		nm_gstring_prepare (&str);
+		for (i = 0; i < num; i++) {
+			guint32 m;
+
+			s = nm_utils_inet4_ntop (a_router[i].s_addr, addr_str);
+			g_string_append (nm_gstring_add_space_delimiter (str), s);
+
+			if (a_router[i].s_addr == 0) {
+				/* silently skip 0.0.0.0 */
+				continue;
+			}
 
-		nm_ip4_config_add_route (ip4_config, &rt, NULL);
+			if (has_router_from_classless) {
+				/* If the DHCP server returns both a Classless Static Routes option and a
+				 * Router option, the DHCP client MUST ignore the Router option [RFC 3442].
+				 *
+				 * Be more lenient and ignore the Router option only if Classless Static
+				 * Routes contain a default gateway (as other DHCP backends do).
+				 */
+				continue;
+			}
+
+			/* if there are multiple default routes, we add them with differing
+			 * metrics. */
+			m = default_route_metric;
+			if (default_route_metric < G_MAXUINT32)
+				default_route_metric++;
+
+			nm_ip4_config_add_route (ip4_config,
+			                         &((const NMPlatformIP4Route) {
+			                             .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
+			                             .gateway       = a_router[i].s_addr,
+			                             .table_coerced = nm_platform_route_table_coerce (route_table),
+			                             .metric        = m,
+			                         }),
+			                         NULL);
+		}
+		LOG_LEASE (LOGD_DHCP4, "router %s", str->str);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, str->str);
 	}
 
-	/* MTU */
-	r = sd_dhcp_lease_get_mtu (lease, &mtu);
-	if (r == 0 && mtu) {
-		nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
-		add_option_u32 (options, dhcp4_requests, SD_DHCP_OPTION_INTERFACE_MTU, mtu);
+	if (   sd_dhcp_lease_get_mtu (lease, &mtu) >= 0
+	    && mtu) {
 		LOG_LEASE (LOGD_DHCP4, "mtu %u", mtu);
+		add_option_u64 (options, dhcp4_requests, SD_DHCP_OPTION_INTERFACE_MTU, mtu);
+		nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
 	}
 
-	/* NTP servers */
 	num = sd_dhcp_lease_get_ntp (lease, &addr_list);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
-			s = nm_utils_inet4_ntop (addr_list[i].s_addr, buf);
-			LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", s);
-			g_string_append_printf (str, "%s%s", str->len ? " " : "", s);
+			nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
+			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 		}
+		LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", str->str);
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_NTP_SERVER, str->str);
 	}
 
-	/* Root path */
-	r = sd_dhcp_lease_get_root_path (lease, &s);
-	if (r >= 0) {
+	if (sd_dhcp_lease_get_root_path (lease, &s) >= 0) {
 		LOG_LEASE (LOGD_DHCP4, "root path '%s'", s);
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROOT_PATH, s);
 	}
 
-	r = sd_dhcp_lease_get_vendor_specific (lease, &data, &data_len);
-	if (r >= 0)
+	if (sd_dhcp_lease_get_vendor_specific (lease, &data, &data_len) >= 0)
 		metered = !!memmem (data, data_len, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
 	nm_ip4_config_set_metered (ip4_config, metered);
 
-	return ip4_config;
+	NM_SET_OUT (out_options, g_steal_pointer (&options));
+	return g_steal_pointer (&ip4_config);
 }
 
 /*****************************************************************************/
@@ -489,13 +598,12 @@ bound4_handle (NMDhcpSystemd *self)
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 	const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
 	sd_dhcp_lease *lease;
-	NMIP4Config *ip4_config;
-	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	gs_unref_hashtable GHashTable *options = NULL;
 	GError *error = NULL;
-	int r;
 
-	r = sd_dhcp_client_get_lease (priv->client4, &lease);
-	if (r < 0 || !lease) {
+	if (   sd_dhcp_client_get_lease (priv->client4, &lease) < 0
+	    || !lease) {
 		_LOGW ("no lease!");
 		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
 		return;
@@ -503,32 +611,29 @@ bound4_handle (NMDhcpSystemd *self)
 
 	_LOGD ("lease available");
 
-	options = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
 	ip4_config = lease_to_ip4_config (nm_dhcp_client_get_multi_idx (NM_DHCP_CLIENT (self)),
 	                                  iface,
 	                                  nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
 	                                  lease,
-	                                  options,
 	                                  nm_dhcp_client_get_route_table (NM_DHCP_CLIENT (self)),
 	                                  nm_dhcp_client_get_route_metric (NM_DHCP_CLIENT (self)),
 	                                  TRUE,
+	                                  &options,
 	                                  &error);
-	if (ip4_config) {
-		add_requests_to_options (options, dhcp4_requests);
-		dhcp_lease_save (lease, priv->lease_file);
-
-		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
-		                          NM_DHCP_STATE_BOUND,
-		                          NM_IP_CONFIG_CAST (ip4_config),
-		                          options);
-	} else {
+	if (!ip4_config) {
 		_LOGW ("%s", error->message);
-		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
 		g_clear_error (&error);
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+		return;
 	}
 
-	g_hash_table_destroy (options);
-	g_clear_object (&ip4_config);
+	add_requests_to_options (options, dhcp4_requests);
+	dhcp_lease_save (lease, priv->lease_file);
+
+	nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
+	                          NM_DHCP_STATE_BOUND,
+	                          NM_IP_CONFIG_CAST (ip4_config),
+	                          options);
 }
 
 static void
@@ -645,8 +750,7 @@ ip4_start (NMDhcpClient *client,
 
 	client_id = nm_dhcp_client_get_client_id (client);
 	if (!client_id) {
-		client_id_new = nm_utils_dhcp_client_id_systemd_node_specific (TRUE,
-		                                                               nm_dhcp_client_get_iface (client));
+		client_id_new = nm_utils_dhcp_client_id_mac (arp_type, hwaddr_arr, hwaddr_len);
 		client_id = client_id_new;
 	}
 
@@ -673,8 +777,11 @@ ip4_start (NMDhcpClient *client,
 
 	/* Add requested options */
 	for (i = 0; dhcp4_requests[i].name; i++) {
-		if (dhcp4_requests[i].include)
-			sd_dhcp_client_set_request_option (sd_client, dhcp4_requests[i].num);
+		if (dhcp4_requests[i].include) {
+			nm_assert (dhcp4_requests[i].option_num <= 255);
+			r = sd_dhcp_client_set_request_option (sd_client, dhcp4_requests[i].option_num);
+			nm_assert (r >= 0 || r == -EEXIST);
+		}
 	}
 
 	hostname = nm_dhcp_client_get_hostname (client);
@@ -720,52 +827,54 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
                      const char *iface,
                      int ifindex,
                      sd_dhcp6_lease *lease,
-                     GHashTable *options,
                      gboolean log_lease,
                      gboolean info_only,
+                     GHashTable **out_options,
                      GError **error)
 {
+	gs_unref_object NMIP6Config *ip6_config = NULL;
+	gs_unref_hashtable GHashTable *options = NULL;
 	struct in6_addr tmp_addr, *dns;
 	uint32_t lft_pref, lft_valid;
-	NMIP6Config *ip6_config;
-	const char *addr_str;
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
 	char **domains;
 	nm_auto_free_gstring GString *str = NULL;
 	int num, i;
-	gint32 ts;
+	const gint32 ts = nm_utils_get_monotonic_timestamp_s ();
 
 	g_return_val_if_fail (lease, NULL);
+
 	ip6_config = nm_ip6_config_new (multi_idx, ifindex);
-	ts = nm_utils_get_monotonic_timestamp_s ();
 
-	/* Addresses */
+	options = out_options ? create_options_dict () : NULL;
+
 	sd_dhcp6_lease_reset_address_iter (lease);
 	nm_gstring_prepare (&str);
 	while (sd_dhcp6_lease_get_address (lease, &tmp_addr, &lft_pref, &lft_valid) >= 0) {
-		NMPlatformIP6Address address = {
-			.plen = 128,
-			.address = tmp_addr,
-			.timestamp = ts,
-			.lifetime = lft_valid,
-			.preferred = lft_pref,
+		char sbuf[400];
+		const NMPlatformIP6Address address = {
+			.plen        = 128,
+			.address     = tmp_addr,
+			.timestamp   = ts,
+			.lifetime    = lft_valid,
+			.preferred   = lft_pref,
 			.addr_source = NM_IP_CONFIG_SOURCE_DHCP,
 		};
 
 		nm_ip6_config_add_address (ip6_config, &address);
 
-		addr_str = nm_utils_inet6_ntop (&tmp_addr, NULL);
-		g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);
+		nm_utils_inet6_ntop (&tmp_addr, addr_str);
+		g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 
 		LOG_LEASE (LOGD_DHCP6,
 		           "address %s",
-		           nm_platform_ip6_address_to_string (&address, NULL, 0));
+		           nm_platform_ip6_address_to_string (&address, sbuf, sizeof (sbuf)));
 	};
-
 	if (str->len)
 		add_option (options, dhcp6_requests, DHCP6_OPTION_IP_ADDRESS, str->str);
 
-	if (!info_only && nm_ip6_config_get_num_addresses (ip6_config) == 0) {
-		g_object_unref (ip6_config);
+	if (   !info_only
+	    && nm_ip6_config_get_num_addresses (ip6_config) == 0) {
 		g_set_error_literal (error,
 		                     NM_MANAGER_ERROR,
 		                     NM_MANAGER_ERROR_FAILED,
@@ -773,32 +882,31 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 		return NULL;
 	}
 
-	/* DNS servers */
 	num = sd_dhcp6_lease_get_dns (lease, &dns);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
+			nm_utils_inet6_ntop (&dns[i], addr_str);
+			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 			nm_ip6_config_add_nameserver (ip6_config, &dns[i]);
-			addr_str = nm_utils_inet6_ntop (&dns[i], NULL);
-			g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);
-			LOG_LEASE (LOGD_DHCP6, "nameserver %s", addr_str);
 		}
+		LOG_LEASE (LOGD_DHCP6, "nameserver %s", str->str);
 		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DNS_SERVERS, str->str);
 	}
 
-	/* Search domains */
 	num = sd_dhcp6_lease_get_domains (lease, &domains);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
+			g_string_append (nm_gstring_add_space_delimiter (str), domains[i]);
 			nm_ip6_config_add_search (ip6_config, domains[i]);
-			g_string_append_printf (str, "%s%s", str->len ? " " : "", domains[i]);
-			LOG_LEASE (LOGD_DHCP6, "domain name '%s'", domains[i]);
 		}
+		LOG_LEASE (LOGD_DHCP6, "domain name '%s'", str->str);
 		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DOMAIN_LIST, str->str);
 	}
 
-	return ip6_config;
+	NM_SET_OUT (out_options, g_steal_pointer (&options));
+	return g_steal_pointer (&ip6_config);
 }
 
 static void
@@ -810,10 +918,9 @@ bound6_handle (NMDhcpSystemd *self)
 	gs_unref_hashtable GHashTable *options = NULL;
 	gs_free_error GError *error = NULL;
 	sd_dhcp6_lease *lease;
-	int r;
 
-	r = sd_dhcp6_client_get_lease (priv->client6, &lease);
-	if (r < 0 || !lease) {
+	if (   sd_dhcp6_client_get_lease (priv->client6, &lease) < 0
+	    || !lease) {
 		_LOGW (" no lease!");
 		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
 		return;
@@ -821,25 +928,25 @@ bound6_handle (NMDhcpSystemd *self)
 
 	_LOGD ("lease available");
 
-	options = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
 	ip6_config = lease_to_ip6_config (nm_dhcp_client_get_multi_idx (NM_DHCP_CLIENT (self)),
 	                                  iface,
 	                                  nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
 	                                  lease,
-	                                  options,
 	                                  TRUE,
 	                                  nm_dhcp_client_get_info_only (NM_DHCP_CLIENT (self)),
+	                                  &options,
 	                                  &error);
 
-	if (ip6_config) {
-		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
-		                          NM_DHCP_STATE_BOUND,
-		                          NM_IP_CONFIG_CAST (ip6_config),
-		                          options);
-	} else {
+	if (!ip6_config) {
 		_LOGW ("%s", error->message);
 		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+		return;
 	}
+
+	nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
+	                          NM_DHCP_STATE_BOUND,
+	                          NM_IP_CONFIG_CAST (ip6_config),
+	                          options);
 }
 
 static void
@@ -883,6 +990,7 @@ ip6_start (NMDhcpClient *client,
 	nm_auto (sd_dhcp6_client_unrefp) sd_dhcp6_client *sd_client = NULL;
 	GBytes *hwaddr;
 	const char *hostname;
+	const char *iface;
 	int r, i;
 	const guint8 *duid_arr;
 	gsize duid_len;
@@ -917,6 +1025,17 @@ ip6_start (NMDhcpClient *client,
 	if (nm_dhcp_client_get_info_only (client))
 		sd_dhcp6_client_set_information_request (sd_client, 1);
 
+	iface = nm_dhcp_client_get_iface (client);
+
+	r = sd_dhcp6_client_set_iaid (sd_client,
+	                              nm_utils_create_dhcp_iaid (TRUE,
+	                                                         (const guint8 *) iface,
+	                                                         strlen (iface)));
+	if (r < 0) {
+		nm_utils_error_set_errno (error, r, "failed to set IAID: %s");
+		return FALSE;
+	}
+
 	r = sd_dhcp6_client_set_duid (sd_client,
 	                              unaligned_read_be16 (&duid_arr[0]),
 	                              &duid_arr[2],
@@ -957,8 +1076,10 @@ ip6_start (NMDhcpClient *client,
 
 	/* Add requested options */
 	for (i = 0; dhcp6_requests[i].name; i++) {
-		if (dhcp6_requests[i].include)
-			sd_dhcp6_client_set_request_option (sd_client, dhcp6_requests[i].num);
+		if (dhcp6_requests[i].include) {
+			r = sd_dhcp6_client_set_request_option (sd_client, dhcp6_requests[i].option_num);
+			nm_assert (r >= 0 || r == -EEXIST);
+		}
 	}
 
 	r = sd_dhcp6_client_set_local_address (sd_client, ll_addr);
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index 9b1653b8..5227eea7 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -19,8 +19,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-#include <errno.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 
@@ -197,7 +195,8 @@ ip4_process_dhclient_rfc3442_routes (const char *iface,
 			/* gateway passed as classless static route */
 			*gwaddr = route.gateway;
 		} else {
-			char addr[INET_ADDRSTRLEN];
+			char b1[INET_ADDRSTRLEN];
+			char b2[INET_ADDRSTRLEN];
 
 			/* normal route */
 			route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
@@ -206,8 +205,9 @@ ip4_process_dhclient_rfc3442_routes (const char *iface,
 			nm_ip4_config_add_route (ip4_config, &route, NULL);
 
 			_LOG2I (LOGD_DHCP4, iface, "  classless static route %s/%d gw %s",
-			        nm_utils_inet4_ntop (route.network, addr), route.plen,
-			        nm_utils_inet4_ntop (route.gateway, NULL));
+			        nm_utils_inet4_ntop (route.network, b1),
+			        route.plen,
+			        nm_utils_inet4_ntop (route.gateway, b2));
 		}
 	}
 
@@ -408,6 +408,7 @@ nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
 	gboolean gateway_has = FALSE;
 	guint32 gateway = 0;
 	guint8 plen = 0;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	g_return_val_if_fail (options != NULL, NULL);
 
@@ -439,7 +440,7 @@ nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
 		process_classful_routes (iface, options, route_table, route_metric, ip4_config);
 
 	if (gateway) {
-		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", nm_utils_inet4_ntop (gateway, NULL));
+		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", nm_utils_inet4_ntop (gateway, sbuf));
 		gateway_has = TRUE;
 	} else {
 		/* If the gateway wasn't provided as a classless static route with a
@@ -543,7 +544,7 @@ nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
 
 		errno = 0;
 		int_mtu = strtol (str, NULL, 10);
-		if ((errno == EINVAL) || (errno == ERANGE))
+		if (NM_IN_SET (errno, EINVAL, ERANGE))
 			goto error;
 
 		if (int_mtu > 576)
@@ -729,7 +730,7 @@ nm_dhcp_utils_duid_to_string (GBytes *duid)
 	g_return_val_if_fail (duid, NULL);
 
 	data = g_bytes_get_data (duid, &len);
-	return _nm_utils_bin2hexstr_full (data, len, ':', FALSE, NULL);
+	return nm_utils_bin2hexstr_full (data, len, ':', FALSE, NULL);
 }
 
 /**
diff --git a/src/dhcp/tests/meson.build b/src/dhcp/tests/meson.build
index 0fee26b2..43b33951 100644
--- a/src/dhcp/tests/meson.build
+++ b/src/dhcp/tests/meson.build
@@ -1,6 +1,6 @@
 test_units = [
   'test-dhcp-dhclient',
-  'test-dhcp-utils'
+  'test-dhcp-utils',
 ]
 
 foreach test_unit: test_units
@@ -13,6 +13,7 @@ foreach test_unit: test_units
   test(
     'dhcp/' + test_unit,
     test_script,
-    args: test_args + [exe.full_path()]
+    args: test_args + [exe.full_path()],
+    timeout: default_test_timeout,
   )
 endforeach
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index ab1f5551..55d712b0 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 #include <linux/rtnetlink.h>
@@ -803,7 +802,7 @@ test_write_duid (void)
 static void
 test_write_existing_duid (void)
 {
-	const guint8 duid[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '\"', 0372, 0214, 0326, 0302 };
+	const guint8 duid[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302 };
 	const char *original_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
 	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n";
 	GError *error = NULL;
@@ -825,7 +824,7 @@ test_write_existing_duid (void)
 	g_assert_cmpstr (expected_contents, ==, contents);
 }
 
-static const guint8 DUID_BIN[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '\"', 0372, 0214, 0326, 0302 };
+static const guint8 DUID_BIN[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302 };
 #define DUID "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302"
 
 static void
diff --git a/src/dhcp/tests/test-dhcp-utils.c b/src/dhcp/tests/test-dhcp-utils.c
index 617a3c6c..240d868c 100644
--- a/src/dhcp/tests/test-dhcp-utils.c
+++ b/src/dhcp/tests/test-dhcp-utils.c
@@ -21,7 +21,6 @@
 
 #include <netinet/in.h>
 #include <arpa/inet.h>
-#include <string.h>
 #include <linux/rtnetlink.h>
 
 #include "nm-utils/nm-dedup-multi.h"
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index aebe3e12..c7c561c4 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -23,7 +23,6 @@
 
 #include "nm-default.h"
 
-#include <errno.h>
 #include <fcntl.h>
 #include <resolv.h>
 #include <stdlib.h>
@@ -122,6 +121,7 @@ typedef struct {
 
 	NMDnsManagerResolvConfManager rc_manager;
 	char *mode;
+	NMDnsPlugin *sd_resolve_plugin;
 	NMDnsPlugin *plugin;
 
 	NMConfig *config;
@@ -311,37 +311,23 @@ _config_data_free (NMDnsConfigData *data)
 }
 
 static int
-_ip_config_data_cmp (const NMDnsIPConfigData *a, const NMDnsIPConfigData *b)
+_ip_config_lst_cmp (const CList *a_lst,
+                    const CList *b_lst,
+                    const void *user_data)
 {
-	int a_prio, b_prio;
-
-	a_prio = nm_ip_config_get_dns_priority (a->ip_config);
-	b_prio = nm_ip_config_get_dns_priority (b->ip_config);
+	const NMDnsIPConfigData *a = c_list_entry (a_lst, NMDnsIPConfigData, ip_config_lst);
+	const NMDnsIPConfigData *b = c_list_entry (b_lst, NMDnsIPConfigData, ip_config_lst);
 
 	/* Configurations with lower priority value first */
-	if (a_prio < b_prio)
-		return -1;
-	else if (a_prio > b_prio)
-		return 1;
+	NM_CMP_DIRECT (nm_ip_config_get_dns_priority (a->ip_config),
+	               nm_ip_config_get_dns_priority (b->ip_config));
 
-	/* Sort also according to type */
-	if (a->ip_config_type > b->ip_config_type)
-		return -1;
-	else if (a->ip_config_type < b->ip_config_type)
-		return 1;
+	/* Sort according to type (descendingly) */
+	NM_CMP_FIELD (b, a, ip_config_type);
 
 	return 0;
 }
 
-static int
-_ip_config_lst_cmp (const CList *a,
-                    const CList *b,
-                    const void *user_data)
-{
-	return _ip_config_data_cmp (c_list_entry (a, NMDnsIPConfigData, ip_config_lst),
-	                            c_list_entry (b, NMDnsIPConfigData, ip_config_lst));
-}
-
 static CList *
 _ip_config_lst_head (NMDnsManager *self)
 {
@@ -357,6 +343,21 @@ _ip_config_lst_head (NMDnsManager *self)
 
 /*****************************************************************************/
 
+gboolean
+nm_dns_manager_has_systemd_resolved (NMDnsManager *self)
+{
+	NMDnsManagerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DNS_MANAGER (self), FALSE);
+
+	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+
+	return    priv->sd_resolve_plugin
+	       || NM_IS_DNS_SYSTEMD_RESOLVED (priv->plugin);
+}
+
+/*****************************************************************************/
+
 static void
 add_string_item (GPtrArray *array, const char *str, gboolean dup)
 {
@@ -534,6 +535,7 @@ dispatch_netconfig (NMDnsManager *self,
 {
 	GPid pid;
 	int fd;
+	int errsv;
 	int status;
 	gssize l;
 	nm_auto_free_gstring GString *str = NULL;
@@ -564,11 +566,10 @@ again:
 
 	/* Wait until the process exits */
 	if (!nm_utils_kill_child_sync (pid, 0, LOGD_DNS, "netconfig", &status, 1000, 0)) {
-		int errsv = errno;
-
+		errsv = errno;
 		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
 		             "Error waiting for netconfig to exit: %s",
-		             strerror (errsv));
+		             nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 	if (!WIFEXITED (status) || WEXITSTATUS (status) != EXIT_SUCCESS) {
@@ -676,7 +677,7 @@ write_resolv_conf_contents (FILE *f,
 		             NM_MANAGER_ERROR,
 		             NM_MANAGER_ERROR_FAILED,
 		             "Could not write " _PATH_RESCONF ": %s",
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		errno = errsv;
 		return FALSE;
 	}
@@ -707,7 +708,8 @@ dispatch_resolvconf (NMDnsManager *self,
 	gs_free char *cmd = NULL;
 	FILE *f;
 	gboolean success = FALSE;
-	int errnosv, err;
+	int errsv;
+	int err;
 	char *argv[] = { RESOLVCONF_PATH, "-d", "NetworkManager", NULL };
 	int status;
 
@@ -741,12 +743,13 @@ dispatch_resolvconf (NMDnsManager *self,
 
 	cmd = g_strconcat (RESOLVCONF_PATH, " -a ", "NetworkManager", NULL);
 	if ((f = popen (cmd, "w")) == NULL) {
+		errsv = errno;
 		g_set_error (error,
 		             NM_MANAGER_ERROR,
 		             NM_MANAGER_ERROR_FAILED,
 		             "Could not write to %s: %s",
 		             RESOLVCONF_PATH,
-		             g_strerror (errno));
+		             nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
@@ -757,10 +760,10 @@ dispatch_resolvconf (NMDnsManager *self,
 	                             error);
 	err = pclose (f);
 	if (err < 0) {
-		errnosv = errno;
+		errsv = errno;
 		g_clear_error (error);
-		g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errnosv),
-		             "Failed to close pipe to resolvconf: %d", errnosv);
+		g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+		             "Failed to close pipe to resolvconf: %d", errsv);
 		return SR_ERROR;
 	} else if (err > 0) {
 		_LOGW ("resolvconf failed with status %d", err);
@@ -787,9 +790,36 @@ _read_link_cached (const char *path, gboolean *is_cached, char **cached)
 	return (*cached = g_file_read_link (path, NULL));
 }
 
-#define MY_RESOLV_CONF NMRUNDIR "/resolv.conf"
-#define MY_RESOLV_CONF_TMP MY_RESOLV_CONF ".tmp"
-#define RESOLV_CONF_TMP "/etc/.resolv.conf.NetworkManager"
+#define MY_RESOLV_CONF             NMRUNDIR"/resolv.conf"
+#define MY_RESOLV_CONF_TMP         MY_RESOLV_CONF".tmp"
+#define RESOLV_CONF_TMP            "/etc/.resolv.conf.NetworkManager"
+
+#define NO_STUB_RESOLV_CONF        NMRUNDIR "/no-stub-resolv.conf"
+
+static void
+update_resolv_conf_no_stub (NMDnsManager *self,
+                            const char *const*searches,
+                            const char *const*nameservers,
+                            const char *const*options)
+{
+	gs_free char *content = NULL;
+	GError *local = NULL;
+
+	content = create_resolv_conf (searches, nameservers, options);
+
+	if (!g_file_set_contents (NO_STUB_RESOLV_CONF,
+	                          content,
+	                          -1,
+	                          &local)) {
+		_LOGD ("update-resolv-no-stub: failure to write file: %s",
+		       local->message);
+		g_error_free (local);
+		return;
+	}
+
+	_LOGT ("update-resolv-no-stub: '%s' successfully written",
+	       NO_STUB_RESOLV_CONF);
+}
 
 static SpawnResult
 update_resolv_conf (NMDnsManager *self,
@@ -807,22 +837,6 @@ update_resolv_conf (NMDnsManager *self,
 	gboolean resconf_link_cached = FALSE;
 	gs_free char *resconf_link = NULL;
 
-	/* If we are not managing /etc/resolv.conf and it points to
-	 * MY_RESOLV_CONF, don't write the private DNS configuration to
-	 * MY_RESOLV_CONF otherwise we would overwrite the changes done by
-	 * some external application.
-	 *
-	 * This is the only situation, where we don't try to update our
-	 * internal resolv.conf file. */
-	if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED) {
-		if (nm_streq0 (_read_link_cached (_PATH_RESCONF, &resconf_link_cached, &resconf_link),
-		               MY_RESOLV_CONF)) {
-			_LOGD ("update-resolv-conf: not updating " _PATH_RESCONF
-			       " since it points to " MY_RESOLV_CONF);
-			return SR_SUCCESS;
-		}
-	}
-
 	content = create_resolv_conf (searches, nameservers, options);
 
 	if (   rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE
@@ -873,9 +887,9 @@ update_resolv_conf (NMDnsManager *self,
 		             NM_MANAGER_ERROR_FAILED,
 		             "Could not open %s: %s",
 		             MY_RESOLV_CONF_TMP,
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		_LOGT ("update-resolv-conf: open temporary file %s failed (%s)",
-		       MY_RESOLV_CONF_TMP, g_strerror (errsv));
+		       MY_RESOLV_CONF_TMP, nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
@@ -883,7 +897,7 @@ update_resolv_conf (NMDnsManager *self,
 	if (!success) {
 		errsv = errno;
 		_LOGT ("update-resolv-conf: write temporary file %s failed (%s)",
-		       MY_RESOLV_CONF_TMP, g_strerror (errsv));
+		       MY_RESOLV_CONF_TMP, nm_strerror_native (errsv));
 	}
 
 	if (fclose (f) < 0) {
@@ -897,9 +911,9 @@ update_resolv_conf (NMDnsManager *self,
 			             NM_MANAGER_ERROR_FAILED,
 			             "Could not close %s: %s",
 			             MY_RESOLV_CONF_TMP,
-			             g_strerror (errsv));
+			             nm_strerror_native (errsv));
 			_LOGT ("update-resolv-conf: close temporary file %s failed (%s)",
-			       MY_RESOLV_CONF_TMP, g_strerror (errsv));
+			       MY_RESOLV_CONF_TMP, nm_strerror_native (errsv));
 		}
 		return SR_ERROR;
 	} else if (!success)
@@ -912,9 +926,9 @@ update_resolv_conf (NMDnsManager *self,
 		             NM_MANAGER_ERROR_FAILED,
 		             "Could not replace %s: %s",
 		             MY_RESOLV_CONF,
-		             g_strerror (errno));
+		             nm_strerror_native (errsv));
 		_LOGT ("update-resolv-conf: failed to rename temporary file %s to %s (%s)",
-		       MY_RESOLV_CONF_TMP, MY_RESOLV_CONF, g_strerror (errsv));
+		       MY_RESOLV_CONF_TMP, MY_RESOLV_CONF, nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
@@ -949,10 +963,10 @@ update_resolv_conf (NMDnsManager *self,
 		             NM_MANAGER_ERROR_FAILED,
 		             "Could not unlink %s: %s",
 		             RESOLV_CONF_TMP,
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		_LOGT ("update-resolv-conf: write internal file %s succeeded "
 		       "but canot delete temporary file %s: %s",
-		       MY_RESOLV_CONF, RESOLV_CONF_TMP, g_strerror (errsv));
+		       MY_RESOLV_CONF, RESOLV_CONF_TMP, nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
@@ -964,10 +978,10 @@ update_resolv_conf (NMDnsManager *self,
 		             "Could not create symlink %s pointing to %s: %s",
 		             RESOLV_CONF_TMP,
 		             MY_RESOLV_CONF,
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		_LOGT ("update-resolv-conf: write internal file %s succeeded "
 		       "but failed to symlink %s: %s",
-		       MY_RESOLV_CONF, RESOLV_CONF_TMP, g_strerror (errsv));
+		       MY_RESOLV_CONF, RESOLV_CONF_TMP, nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
@@ -979,10 +993,10 @@ update_resolv_conf (NMDnsManager *self,
 		             "Could not rename %s to %s: %s",
 		             RESOLV_CONF_TMP,
 		             _PATH_RESCONF,
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		_LOGT ("update-resolv-conf: write internal file %s succeeded "
 		       "but failed to rename temporary symlink %s to %s: %s",
-		       MY_RESOLV_CONF, RESOLV_CONF_TMP, _PATH_RESCONF, g_strerror (errsv));
+		       MY_RESOLV_CONF, RESOLV_CONF_TMP, _PATH_RESCONF, nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
@@ -1414,6 +1428,16 @@ update_dns (NMDnsManager *self,
 	                           &searches, &options, &nameservers,
 	                           &nis_servers, &nis_domain);
 
+	if (priv->plugin || priv->sd_resolve_plugin)
+		rebuild_domain_lists (self);
+
+	if (priv->sd_resolve_plugin) {
+		nm_dns_plugin_update (priv->sd_resolve_plugin,
+		                      global_config,
+		                      _ip_config_lst_head (self),
+		                      priv->hostname);
+	}
+
 	/* Let any plugins do their thing first */
 	if (priv->plugin) {
 		NMDnsPlugin *plugin = priv->plugin;
@@ -1429,7 +1453,6 @@ update_dns (NMDnsManager *self,
 		}
 
 		_LOGD ("update-dns: updating plugin %s", plugin_name);
-		rebuild_domain_lists (self);
 		if (!nm_dns_plugin_update (plugin,
 		                           global_config,
 		                           _ip_config_lst_head (self),
@@ -1441,15 +1464,21 @@ update_dns (NMDnsManager *self,
 			 */
 			caching = FALSE;
 		}
-		/* Clear the generated search list as it points to
-		 * strings owned by IP configurations and we can't
-		 * guarantee they stay alive. */
-		clear_domain_lists (self);
 
 	skip:
 		;
 	}
 
+	/* Clear the generated search list as it points to
+	 * strings owned by IP configurations and we can't
+	 * guarantee they stay alive. */
+	clear_domain_lists (self);
+
+	update_resolv_conf_no_stub (self,
+	                            NM_CAST_STRV_CC (searches),
+	                            NM_CAST_STRV_CC (nameservers),
+	                            NM_CAST_STRV_CC (options));
+
 	/* If caching was successful, we only send 127.0.0.1 to /etc/resolv.conf
 	 * to ensure that the glibc resolver doesn't try to round-robin nameservers,
 	 * but only uses the local caching nameserver.
@@ -1963,9 +1992,13 @@ init_resolv_conf_mode (NMDnsManager *self, gboolean force_reload_plugin)
 	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 	NMDnsManagerResolvConfManager rc_manager;
 	const char *mode;
-	gboolean param_changed = FALSE, plugin_changed = FALSE;
+	gboolean systemd_resolved;
+	gboolean param_changed = FALSE;
+	gboolean plugin_changed = FALSE;
+	gboolean systemd_resolved_changed = FALSE;
 
 	mode = nm_config_data_get_dns_mode (nm_config_get_data (priv->config));
+	systemd_resolved = nm_config_data_get_systemd_resolved (nm_config_get_data (priv->config));
 
 	if (nm_streq0 (mode, "none"))
 		rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED;
@@ -2011,6 +2044,7 @@ again:
 			plugin_changed = TRUE;
 		}
 		mode = "systemd-resolved";
+		systemd_resolved = FALSE;
 	} else if (nm_streq0 (mode, "dnsmasq")) {
 		if (force_reload_plugin || !NM_IS_DNS_DNSMASQ (priv->plugin)) {
 			_clear_plugin (self);
@@ -2033,7 +2067,18 @@ again:
 			plugin_changed = TRUE;
 	}
 
-	if (plugin_changed && priv->plugin) {
+	/* The systemd-resolved plugin is special. We typically always want to keep
+	 * systemd-resolved up to date even if the configured plugin is different. */
+	if (systemd_resolved) {
+		if (!priv->sd_resolve_plugin) {
+			priv->sd_resolve_plugin = nm_dns_systemd_resolved_new ();
+			systemd_resolved_changed = TRUE;
+		}
+	} else if (nm_clear_g_object (&priv->sd_resolve_plugin))
+		systemd_resolved_changed = TRUE;
+
+	if (   plugin_changed
+	    && priv->plugin) {
 		g_signal_connect (priv->plugin, NM_DNS_PLUGIN_FAILED, G_CALLBACK (plugin_failed), self);
 		g_signal_connect (priv->plugin, NM_DNS_PLUGIN_CHILD_QUIT, G_CALLBACK (plugin_child_quit), self);
 	}
@@ -2053,9 +2098,11 @@ again:
 		_notify (self, PROP_RC_MANAGER);
 	}
 
-	if (param_changed || plugin_changed) {
-		_LOGI ("init: dns=%s, rc-manager=%s%s%s%s",
-		       mode, _rc_manager_to_string (rc_manager),
+	if (param_changed || plugin_changed || systemd_resolved_changed) {
+		_LOGI ("init: dns=%s%s rc-manager=%s%s%s%s",
+		       mode,
+		       (systemd_resolved ? ",systemd-resolved" : ""),
+		       _rc_manager_to_string (rc_manager),
 		       NM_PRINT_FMT_QUOTED (priv->plugin, ", plugin=",
 		                            nm_dns_plugin_get_name (priv->plugin), "", ""));
 	}
@@ -2316,6 +2363,7 @@ dispose (GObject *object)
 	if (priv->config)
 		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
 
+	g_clear_object (&priv->sd_resolve_plugin);
 	_clear_plugin (self);
 
 	priv->best_ip_config_4 = NULL;
diff --git a/src/dns/nm-dns-manager.h b/src/dns/nm-dns-manager.h
index a3e9472e..7f6ed3ed 100644
--- a/src/dns/nm-dns-manager.h
+++ b/src/dns/nm-dns-manager.h
@@ -129,6 +129,8 @@ typedef enum {
 
 void nm_dns_manager_stop (NMDnsManager *self);
 
+gboolean nm_dns_manager_has_systemd_resolved (NMDnsManager *self);
+
 /*****************************************************************************/
 
 char *nmtst_dns_create_resolv_conf (const char *const*searches,
diff --git a/src/dns/nm-dns-plugin.c b/src/dns/nm-dns-plugin.c
index 48f04b00..ce814186 100644
--- a/src/dns/nm-dns-plugin.c
+++ b/src/dns/nm-dns-plugin.c
@@ -21,7 +21,6 @@
 
 #include "nm-dns-plugin.h"
 
-#include <string.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/types.h>
diff --git a/src/dnsmasq/nm-dnsmasq-manager.c b/src/dnsmasq/nm-dnsmasq-manager.c
index 3fe2f489..43bc66fc 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.c
+++ b/src/dnsmasq/nm-dnsmasq-manager.c
@@ -25,7 +25,6 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <signal.h>
-#include <string.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 #include <stdlib.h>
@@ -73,51 +72,6 @@ G_DEFINE_TYPE (NMDnsMasqManager, nm_dnsmasq_manager, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
-typedef struct {
-	GPtrArray *array;
-	GStringChunk *chunk;
-} NMCmdLine;
-
-static NMCmdLine *
-nm_cmd_line_new (void)
-{
-	NMCmdLine *cmd;
-
-	cmd = g_slice_new (NMCmdLine);
-	cmd->array = g_ptr_array_new ();
-	cmd->chunk = g_string_chunk_new (1024);
-
-	return cmd;
-}
-
-static void
-nm_cmd_line_destroy (NMCmdLine *cmd)
-{
-	g_ptr_array_free (cmd->array, TRUE);
-	g_string_chunk_free (cmd->chunk);
-	g_slice_free (NMCmdLine, cmd);
-}
-
-static char *
-nm_cmd_line_to_str (NMCmdLine *cmd)
-{
-	char *str;
-
-	g_ptr_array_add (cmd->array, NULL);
-	str = g_strjoinv (" ", (char **) cmd->array->pdata);
-	g_ptr_array_remove_index (cmd->array, cmd->array->len - 1);
-
-	return str;
-}
-
-static void
-nm_cmd_line_add_string (NMCmdLine *cmd, const char *str)
-{
-	g_ptr_array_add (cmd->array, g_string_chunk_insert (cmd->chunk, str));
-}
-
-/*****************************************************************************/
-
 static void
 dm_watch_cb (GPid pid, int status, gpointer user_data)
 {
@@ -145,40 +99,40 @@ dm_watch_cb (GPid pid, int status, gpointer user_data)
 	g_signal_emit (manager, signals[STATE_CHANGED], 0, NM_DNSMASQ_STATUS_DEAD);
 }
 
-static NMCmdLine *
+static GPtrArray *
 create_dm_cmd_line (const char *iface,
                     const NMIP4Config *ip4_config,
                     const char *pidfile,
+                    gboolean announce_android_metered,
                     GError **error)
 {
-	NMCmdLine *cmd;
+	gs_unref_ptrarray GPtrArray *cmd = NULL;
 	nm_auto_free_gstring GString *s = NULL;
 	char first[INET_ADDRSTRLEN];
 	char last[INET_ADDRSTRLEN];
-	char localaddr[INET_ADDRSTRLEN];
+	char listen_address_s[INET_ADDRSTRLEN];
 	char tmpaddr[INET_ADDRSTRLEN];
-	char *error_desc = NULL;
+	gs_free char *error_desc = NULL;
 	const char *dm_binary;
 	const NMPlatformIP4Address *listen_address;
 	guint i, n;
 
 	listen_address = nm_ip4_config_get_first_address (ip4_config);
+
 	g_return_val_if_fail (listen_address, NULL);
 
 	dm_binary = nm_utils_find_helper ("dnsmasq", DNSMASQ_PATH, error);
 	if (!dm_binary)
 		return NULL;
 
-	s = g_string_sized_new (100);
+	cmd = g_ptr_array_new_with_free_func (g_free);
 
-	/* Create dnsmasq command line */
-	cmd = nm_cmd_line_new ();
-	nm_cmd_line_add_string (cmd, dm_binary);
+	nm_strv_ptrarray_add_string_dup (cmd, dm_binary);
 
 	if (   nm_logging_enabled (LOGL_TRACE, LOGD_SHARING)
 	    || getenv ("NM_DNSMASQ_DEBUG")) {
-		nm_cmd_line_add_string (cmd, "--log-dhcp");
-		nm_cmd_line_add_string (cmd, "--log-queries");
+		nm_strv_ptrarray_add_string_dup (cmd, "--log-dhcp");
+		nm_strv_ptrarray_add_string_dup (cmd, "--log-queries");
 	}
 
 	/* dnsmasq may read from its default config file location, which if that
@@ -187,25 +141,23 @@ create_dm_cmd_line (const char *iface,
 	 * as the gateway or whatever.  So tell dnsmasq not to use any config file
 	 * at all.
 	 */
-	nm_cmd_line_add_string (cmd, "--conf-file=/dev/null");
+	nm_strv_ptrarray_add_string_dup (cmd, "--conf-file=/dev/null");
 
-	nm_cmd_line_add_string (cmd, "--no-hosts");
-	nm_cmd_line_add_string (cmd, "--keep-in-foreground");
-	nm_cmd_line_add_string (cmd, "--bind-interfaces");
-	nm_cmd_line_add_string (cmd, "--except-interface=lo");
-	nm_cmd_line_add_string (cmd, "--clear-on-reload");
+	nm_strv_ptrarray_add_string_dup (cmd, "--no-hosts");
+	nm_strv_ptrarray_add_string_dup (cmd, "--keep-in-foreground");
+	nm_strv_ptrarray_add_string_dup (cmd, "--bind-interfaces");
+	nm_strv_ptrarray_add_string_dup (cmd, "--except-interface=lo");
+	nm_strv_ptrarray_add_string_dup (cmd, "--clear-on-reload");
 
 	/* Use strict order since in the case of VPN connections, the VPN's
 	 * nameservers will be first in resolv.conf, and those need to be tried
 	 * first by dnsmasq to successfully resolve names from the VPN.
 	 */
-	nm_cmd_line_add_string (cmd, "--strict-order");
+	nm_strv_ptrarray_add_string_dup (cmd, "--strict-order");
+
+	nm_utils_inet4_ntop (listen_address->address, listen_address_s);
 
-	nm_utils_inet4_ntop (listen_address->address, localaddr);
-	g_string_append (s, "--listen-address=");
-	g_string_append (s, localaddr);
-	nm_cmd_line_add_string (cmd, s->str);
-	g_string_truncate (s, 0);
+	nm_strv_ptrarray_add_string_concat (cmd, "--listen-address=", listen_address_s);
 
 	if (!nm_dnsmasq_utils_get_range (listen_address, first, last, &error_desc)) {
 		g_set_error_literal (error,
@@ -213,59 +165,61 @@ create_dm_cmd_line (const char *iface,
 		                     NM_MANAGER_ERROR_FAILED,
 		                     error_desc);
 		_LOGW ("failed to find DHCP address ranges: %s", error_desc);
-		g_free (error_desc);
-		nm_cmd_line_destroy (cmd);
 		return NULL;
 	}
 
-	g_string_append_printf (s, "--dhcp-range=%s,%s,60m", first, last);
-	nm_cmd_line_add_string (cmd, s->str);
-	g_string_truncate (s, 0);
+	nm_strv_ptrarray_add_string_printf (cmd,
+	                                    "--dhcp-range=%s,%s,60m",
+	                                    first,
+	                                    last);
 
 	if (nm_ip4_config_best_default_route_get (ip4_config)) {
-		g_string_append (s, "--dhcp-option=option:router,");
-		g_string_append (s, localaddr);
-		nm_cmd_line_add_string (cmd, s->str);
-		g_string_truncate (s, 0);
+		nm_strv_ptrarray_add_string_concat (cmd,
+		                                    "--dhcp-option=option:router,",
+		                                    listen_address_s);
 	}
 
 	if ((n = nm_ip4_config_get_num_nameservers (ip4_config))) {
+		nm_gstring_prepare (&s);
 		g_string_append (s, "--dhcp-option=option:dns-server");
 		for (i = 0; i < n; i++) {
 			g_string_append_c (s, ',');
 			g_string_append (s, nm_utils_inet4_ntop (nm_ip4_config_get_nameserver (ip4_config, i), tmpaddr));
 		}
-		nm_cmd_line_add_string (cmd, s->str);
-		g_string_truncate (s, 0);
+		nm_strv_ptrarray_take_gstring (cmd, &s);
 	}
 
 	if ((n = nm_ip4_config_get_num_searches (ip4_config))) {
+		nm_gstring_prepare (&s);
 		g_string_append (s, "--dhcp-option=option:domain-search");
 		for (i = 0; i < n; i++) {
 			g_string_append_c (s, ',');
 			g_string_append (s, nm_ip4_config_get_search (ip4_config, i));
 		}
-		nm_cmd_line_add_string (cmd, s->str);
-		g_string_truncate (s, 0);
+		nm_strv_ptrarray_take_gstring (cmd, &s);
 	}
 
-	nm_cmd_line_add_string (cmd, "--dhcp-lease-max=50");
+	if (announce_android_metered) {
+		/* force option 43 to announce ANDROID_METERED. Do this, even if the client
+		 * did not ask for this option. See https://www.lorier.net/docs/android-metered.html */
+		nm_strv_ptrarray_add_string_dup (cmd, "--dhcp-option-force=43,ANDROID_METERED");
+	}
+
+	nm_strv_ptrarray_add_string_dup (cmd, "--dhcp-lease-max=50");
 
-	g_string_append (s, "--dhcp-leasefile=" NMSTATEDIR);
-	g_string_append_printf (s, "/dnsmasq-%s.leases", iface);
-	nm_cmd_line_add_string (cmd, s->str);
-	g_string_truncate (s, 0);
+	nm_strv_ptrarray_add_string_printf (cmd,
+	                                    "--dhcp-leasefile=%s/dnsmasq-%s.leases",
+	                                    NMSTATEDIR,
+	                                    iface);
 
-	g_string_append (s, "--pid-file=");
-	g_string_append (s, pidfile);
-	nm_cmd_line_add_string (cmd, s->str);
-	g_string_truncate (s, 0);
+	nm_strv_ptrarray_add_string_concat (cmd, "--pid-file=", pidfile);
 
 	/* dnsmasq exits if the conf dir is not present */
 	if (g_file_test (CONFDIR, G_FILE_TEST_IS_DIR))
-		nm_cmd_line_add_string (cmd, "--conf-dir=" CONFDIR);
+		nm_strv_ptrarray_add_string_dup (cmd, "--conf-dir=" CONFDIR);
 
-	return cmd;
+	g_ptr_array_add (cmd, NULL);
+	return g_steal_pointer (&cmd);
 }
 
 static void
@@ -310,10 +264,11 @@ out:
 gboolean
 nm_dnsmasq_manager_start (NMDnsMasqManager *manager,
                           NMIP4Config *ip4_config,
+                          gboolean announce_android_metered,
                           GError **error)
 {
 	NMDnsMasqManagerPrivate *priv;
-	NMCmdLine *dm_cmd;
+	gs_unref_ptrarray GPtrArray *dm_cmd = NULL;
 	gs_free char *cmd_str = NULL;
 
 	g_return_val_if_fail (NM_IS_DNSMASQ_MANAGER (manager), FALSE);
@@ -324,32 +279,35 @@ nm_dnsmasq_manager_start (NMDnsMasqManager *manager,
 
 	kill_existing_by_pidfile (priv->pidfile);
 
-	dm_cmd = create_dm_cmd_line (priv->iface, ip4_config, priv->pidfile, error);
+	dm_cmd = create_dm_cmd_line (priv->iface,
+	                             ip4_config,
+	                             priv->pidfile,
+	                             announce_android_metered,
+	                             error);
 	if (!dm_cmd)
 		return FALSE;
 
-	g_ptr_array_add (dm_cmd->array, NULL);
-
 	_LOGI ("starting dnsmasq...");
-	_LOGD ("command line: %s", (cmd_str = nm_cmd_line_to_str (dm_cmd)));
+	_LOGD ("command line: %s", (cmd_str = g_strjoinv (" ", (char **) dm_cmd->pdata)));
 
 	priv->pid = 0;
-	if (!g_spawn_async (NULL, (char **) dm_cmd->array->pdata, NULL,
+	if (!g_spawn_async (NULL,
+	                    (char **) dm_cmd->pdata,
+	                    NULL,
 	                    G_SPAWN_DO_NOT_REAP_CHILD,
-	                    nm_utils_setpgid, NULL,
-	                    &priv->pid, error)) {
-		goto out;
-	}
+	                    nm_utils_setpgid,
+	                    NULL,
+	                    &priv->pid,
+	                    error))
+		return FALSE;
+
+	nm_assert (priv->pid > 0);
 
 	_LOGD ("dnsmasq started with pid %d", priv->pid);
 
 	priv->dm_watch_id = g_child_watch_add (priv->pid, (GChildWatchFunc) dm_watch_cb, manager);
 
- out:
-	if (dm_cmd)
-		nm_cmd_line_destroy (dm_cmd);
-
-	return priv->pid > 0;
+	return TRUE;
 }
 
 void
diff --git a/src/dnsmasq/nm-dnsmasq-manager.h b/src/dnsmasq/nm-dnsmasq-manager.h
index a0ad295c..dd4a9069 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.h
+++ b/src/dnsmasq/nm-dnsmasq-manager.h
@@ -48,6 +48,7 @@ NMDnsMasqManager *nm_dnsmasq_manager_new (const char *iface);
 
 gboolean nm_dnsmasq_manager_start (NMDnsMasqManager *manager,
                                    NMIP4Config *ip4_config,
+                                   gboolean announce_android_metered,
                                    GError **error);
 
 void     nm_dnsmasq_manager_stop  (NMDnsMasqManager *manager);
diff --git a/src/dnsmasq/nm-dnsmasq-utils.c b/src/dnsmasq/nm-dnsmasq-utils.c
index 382b3aeb..ec5545d3 100644
--- a/src/dnsmasq/nm-dnsmasq-utils.c
+++ b/src/dnsmasq/nm-dnsmasq-utils.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 
 #include "nm-dnsmasq-utils.h"
diff --git a/src/dnsmasq/tests/meson.build b/src/dnsmasq/tests/meson.build
index 12106c52..09ef52e5 100644
--- a/src/dnsmasq/tests/meson.build
+++ b/src/dnsmasq/tests/meson.build
@@ -9,5 +9,5 @@ exe = executable(
 test(
   'dnsmasq/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
 )
diff --git a/src/initrd/meson.build b/src/initrd/meson.build
index 66e825d5..a12b718a 100644
--- a/src/initrd/meson.build
+++ b/src/initrd/meson.build
@@ -1,6 +1,6 @@
 sources = files(
   'nmi-cmdline-reader.c',
-  'nmi-ibft-reader.c'
+  'nmi-ibft-reader.c',
 )
 
 nm_cflags = ['-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_DAEMON']
diff --git a/src/initrd/nm-initrd-generator.c b/src/initrd/nm-initrd-generator.c
index eb9a38df..b84543c4 100644
--- a/src/initrd/nm-initrd-generator.c
+++ b/src/initrd/nm-initrd-generator.c
@@ -40,45 +40,39 @@ output_conn (gpointer key, gpointer value, gpointer user_data)
 	const char *basename = key;
 	NMConnection *connection = value;
 	char *connections_dir = user_data;
-	GKeyFile *file;
+	gs_unref_keyfile GKeyFile *file = NULL;
 	gs_free char *data = NULL;
-	GError *error = NULL;
+	gs_free_error GError *error = NULL;
 	gsize len;
 
-	if (!nm_connection_normalize (connection, NULL, NULL, &error)) {
-		g_print ("%s\n", error->message);
-		g_error_free (error);
-		return;
-	}
+	if (!nm_connection_normalize (connection, NULL, NULL, &error))
+		goto err_out;
 
 	file = nm_keyfile_write (connection, NULL, NULL, &error);
-	if (file == NULL) {
-		g_print ("%s\n", error->message);
-		g_error_free (error);
-		return;
-	}
+	if (file == NULL)
+		goto err_out;
 
 	data = g_key_file_to_data (file, &len, &error);
-	if (!data) {
-		g_print ("%s\n", error->message);
-		g_error_free (error);
-	} else if (connections_dir) {
-		gs_free char *basename_w_ext = g_strconcat (basename, ".nmconnection", NULL);
-		char *filename = g_build_filename (connections_dir, basename_w_ext, NULL);
-
-		if (!nm_utils_file_set_contents (filename, data, len, 0600, &error)) {
-			g_print ("%s\n", error->message);
-			g_error_free (error);
-		}
-		g_free (filename);
-	} else {
+	if (!data)
+		goto err_out;
+
+	if (connections_dir) {
+		gs_free char *filename = NULL;
+		gs_free char *full_filename = NULL;
+
+		filename = nm_keyfile_utils_create_filename (basename, TRUE);
+		full_filename = g_build_filename (connections_dir, filename, NULL);
+
+		if (!nm_utils_file_set_contents (full_filename, data, len, 0600, &error))
+			goto err_out;
+	} else
 		g_print ("\n*** Connection '%s' ***\n\n%s\n", basename, data);
-	}
 
-	g_key_file_free (file);
+	return;
+err_out:
+	g_print ("%s\n", error->message);
 }
 
-#define DEFAULT_CONNECTIONS_DIR  NMRUNDIR "/system-connections"
 #define DEFAULT_SYSFS_DIR        "/sys"
 
 int
@@ -90,7 +84,7 @@ main (int argc, char *argv[])
 	gboolean dump_to_stdout = FALSE;
 	gs_strfreev char **remaining = NULL;
 	GOptionEntry option_entries[] = {
-		{ "connections-dir", 'c', 0, G_OPTION_ARG_FILENAME, &connections_dir, "Output connection directory", DEFAULT_CONNECTIONS_DIR },
+		{ "connections-dir", 'c', 0, G_OPTION_ARG_FILENAME, &connections_dir, "Output connection directory", NM_KEYFILE_PATH_NAME_RUN },
 		{ "sysfs-dir", 'd', 0, G_OPTION_ARG_FILENAME, &sysfs_dir, "The sysfs mount point", DEFAULT_SYSFS_DIR },
 		{ "stdout", 's', 0, G_OPTION_ARG_NONE, &dump_to_stdout, "Dump connections to standard output", NULL },
 		{ G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &remaining, NULL, NULL },
@@ -98,6 +92,7 @@ main (int argc, char *argv[])
 	};
 	GOptionContext *option_context;
 	GError *error = NULL;
+	int errsv;
 
 	option_context = g_option_context_new ("-- [ip=...] [rd.route=...] [bridge=...] [bond=...] [team=...] [vlan=...] "
 	                                       "[bootdev=...] [nameserver=...] [rd.peerdns=...] [rd.bootif=...] [BOOTIF=...] ... ");
@@ -110,7 +105,7 @@ main (int argc, char *argv[])
 	g_option_context_add_main_entries (option_context, option_entries, GETTEXT_PACKAGE);
 
 	if (!g_option_context_parse (option_context, &argc, &argv, &error)) {
-		_LOGW (LOGD_CORE, "%s\n", error->message);
+		_LOGW (LOGD_CORE, "%s", error->message);
 		return 1;
 	}
 
@@ -120,14 +115,15 @@ main (int argc, char *argv[])
 	}
 
 	if (!connections_dir)
-		connections_dir = g_strdup (DEFAULT_CONNECTIONS_DIR);
+		connections_dir = g_strdup (NM_KEYFILE_PATH_NAME_RUN);
 	if (!sysfs_dir)
 		sysfs_dir = g_strdup (DEFAULT_SYSFS_DIR);
 	if (dump_to_stdout)
 		g_clear_pointer (&connections_dir, g_free);
 
 	if (connections_dir && g_mkdir_with_parents (connections_dir, 0755) != 0) {
-		_LOGW (LOGD_CORE, "%s: %s\n", connections_dir, strerror (errno));
+		errsv = errno;
+		_LOGW (LOGD_CORE, "%s: %s", connections_dir, nm_strerror_native (errsv));
 		return 1;
 	}
 
diff --git a/src/initrd/nm-initrd-generator.h b/src/initrd/nm-initrd-generator.h
index 1fa858fc..dab6fb64 100644
--- a/src/initrd/nm-initrd-generator.h
+++ b/src/initrd/nm-initrd-generator.h
@@ -1,18 +1,19 @@
 /* NetworkManager initrd configuration generator
  *
- * 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 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 program is distributed in the hope that it will be useful,
+ * 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 General Public License for more details.
+ * 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 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.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2014, 2018 Red Hat, Inc.
  */
diff --git a/src/initrd/nmi-cmdline-reader.c b/src/initrd/nmi-cmdline-reader.c
index e3b1bb63..b9c75c1b 100644
--- a/src/initrd/nmi-cmdline-reader.c
+++ b/src/initrd/nmi-cmdline-reader.c
@@ -19,12 +19,10 @@
  */
 
 #include "nm-default.h"
-#include "nm-core-internal.h"
 
+#include "nm-core-internal.h"
 #include "nm-initrd-generator.h"
 
-#include <string.h>
-
 /*****************************************************************************/
 
 #define _NMLOG(level, domain, ...) \
@@ -39,7 +37,17 @@ get_conn (GHashTable *connections, const char *ifname, const char *type_name)
 {
 	NMConnection *connection;
 	NMSetting *setting;
-	const char *basename = ifname ?: "default_connection";
+	const char *basename;
+	NMConnectionMultiConnect multi_connect;
+
+	if (ifname) {
+		basename = ifname;
+		multi_connect = NM_CONNECTION_MULTI_CONNECT_SINGLE;
+	} else {
+		/* This is essentially for the "ip=dhcp" scenario. */
+		basename = "default_connection";
+		multi_connect = NM_CONNECTION_MULTI_CONNECT_MULTIPLE;
+	}
 
 	connection = g_hash_table_lookup (connections, (gpointer)basename);
 
@@ -71,6 +79,7 @@ get_conn (GHashTable *connections, const char *ifname, const char *type_name)
 		              NM_SETTING_CONNECTION_ID, ifname ?: "Wired Connection",
 		              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
 		              NM_SETTING_CONNECTION_INTERFACE_NAME, ifname,
+		              NM_SETTING_CONNECTION_MULTI_CONNECT, multi_connect,
 		              NULL);
 
 		if (!type_name)
@@ -132,7 +141,7 @@ _base_setting_set (NMConnection *connection, const char *property, const char *v
 	GParamSpec *spec = g_object_class_find_property (object_class, property);
 
 	if (!spec) {
-		_LOGW (LOGD_CORE, "'%s' does not support setting %s\n", type_name, property);
+		_LOGW (LOGD_CORE, "'%s' does not support setting %s", type_name, property);
 		return;
 	}
 
@@ -151,7 +160,7 @@ _base_setting_set (NMConnection *connection, const char *property, const char *v
 	} else if (G_IS_PARAM_SPEC_STRING (spec))
 		g_object_set (setting, property, value, NULL);
 	else
-		_LOGW (LOGD_CORE, "Don't know how to set '%s' of %s\n", property, type_name);
+		_LOGW (LOGD_CORE, "Don't know how to set '%s' of %s", property, type_name);
 
 	g_type_class_unref (object_class);
 }
@@ -211,7 +220,7 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 			dns[1] = get_word (&argument, ':');
 			dns_addr_family[1] = guess_ip_address_family (dns[1]);
 			if (argument && *argument)
-				_LOGW (LOGD_CORE, "Ignoring extra: '%s'.\n", argument);
+				_LOGW (LOGD_CORE, "Ignoring extra: '%s'.", argument);
 		} else {
 			mtu = tmp;
 			macaddr = argument;
@@ -236,12 +245,12 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 
 			index = g_hash_table_lookup (nic, "index");
 			if (!index) {
-				_LOGW (LOGD_CORE, "Ignoring an iBFT entry without an index\n");
+				_LOGW (LOGD_CORE, "Ignoring an iBFT entry without an index");
 				continue;
 			}
 
 			if (!nmi_ibft_update_connection_from_nic (connection, nic, &error)) {
-				_LOGW (LOGD_CORE, "Unable to merge iBFT configuration: %s\n", error->message);
+				_LOGW (LOGD_CORE, "Unable to merge iBFT configuration: %s", error->message);
 				g_error_free (error);
 			}
 
@@ -261,10 +270,10 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 	if (netmask && *netmask) {
 		NMIPAddr addr;
 
-		if (nm_utils_parse_inaddr_bin (AF_INET, netmask, &addr)) {
+		if (nm_utils_parse_inaddr_bin (AF_INET, netmask, NULL, &addr)) {
 			client_ip_prefix = nm_utils_ip4_netmask_to_prefix (addr.addr4);
 		} else {
-			_LOGW (LOGD_CORE, "Unrecognized address: %s\n", client_ip);
+			_LOGW (LOGD_CORE, "Unrecognized address: %s", client_ip);
 		}
 	}
 
@@ -273,7 +282,7 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 		NMIPAddress *address = NULL;
 		NMIPAddr addr;
 
-		if (nm_utils_parse_inaddr_prefix_bin (client_ip_family, client_ip, &addr,
+		if (nm_utils_parse_inaddr_prefix_bin (client_ip_family, client_ip, NULL, &addr,
 		                                      client_ip_prefix == -1 ? &client_ip_prefix : NULL)) {
 			if (client_ip_prefix == -1) {
 				switch (client_ip_family) {
@@ -288,11 +297,11 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 
 			address = nm_ip_address_new_binary (client_ip_family, &addr.addr_ptr, client_ip_prefix, &error);
 			if (!address) {
-				_LOGW (LOGD_CORE, "Invalid address '%s': %s\n", client_ip, error->message);
+				_LOGW (LOGD_CORE, "Invalid address '%s': %s", client_ip, error->message);
 				g_clear_error (&error);
 			}
 		} else {
-			_LOGW (LOGD_CORE, "Unrecognized address: %s\n", client_ip);
+			_LOGW (LOGD_CORE, "Unrecognized address: %s", client_ip);
 		}
 
 		if (address) {
@@ -312,7 +321,7 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 				nm_setting_ip_config_add_address (s_ip6, address);
 				break;
 			default:
-				_LOGW (LOGD_CORE, "Unknown address family: %s\n", client_ip);
+				_LOGW (LOGD_CORE, "Unknown address family: %s", client_ip);
 				break;
 			}
 			nm_ip_address_unref (address);
@@ -377,12 +386,12 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 				ibft = nmi_ibft_read (sysfs_dir);
 			nic = g_hash_table_lookup (ibft, mac_up);
 			if (!nic)
-				_LOGW (LOGD_CORE, "No iBFT NIC for %s (%s)\n", ifname, mac_up);
+				_LOGW (LOGD_CORE, "No iBFT NIC for %s (%s)", ifname, mac_up);
 		}
 
 		if (nic) {
 			if (!nmi_ibft_update_connection_from_nic (connection, nic, &error)) {
-				_LOGW (LOGD_CORE, "Unable to merge iBFT configuration: %s\n", error->message);
+				_LOGW (LOGD_CORE, "Unable to merge iBFT configuration: %s", error->message);
 				g_clear_error (&error);
 			}
 		}
@@ -403,11 +412,11 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 				g_object_set (s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, gateway_ip, NULL);
 				break;
 			default:
-				_LOGW (LOGD_CORE, "Unknown address family: %s\n", gateway_ip);
+				_LOGW (LOGD_CORE, "Unknown address family: %s", gateway_ip);
 				break;
 			}
 		} else {
-			_LOGW (LOGD_CORE, "Invalid gateway: %s\n", gateway_ip);
+			_LOGW (LOGD_CORE, "Invalid gateway: %s", gateway_ip);
 		}
 	}
 
@@ -428,11 +437,11 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 				nm_setting_ip_config_add_dns (s_ip6, dns[i]);
 				break;
 			default:
-				_LOGW (LOGD_CORE, "Unknown address family: %s\n", dns[i]);
+				_LOGW (LOGD_CORE, "Unknown address family: %s", dns[i]);
 				break;
 			}
 		} else {
-			_LOGW (LOGD_CORE, "Invalid name server: %s\n", dns[i]);
+			_LOGW (LOGD_CORE, "Invalid name server: %s", dns[i]);
 		}
 	}
 
@@ -496,7 +505,7 @@ parse_master (GHashTable *connections, char *argument, const char *type_name)
 	} while (slaves && *slaves != '\0');
 
 	if (argument && *argument)
-		_LOGW (LOGD_CORE, "Ignoring extra: '%s'.\n", argument);
+		_LOGW (LOGD_CORE, "Ignoring extra: '%s'.", argument);
 }
 
 static void
@@ -507,8 +516,8 @@ parse_rd_route (GHashTable *connections, char *argument)
 	const char *gateway;
 	const char *interface;
 	int family = AF_UNSPEC;
-	NMIPAddr net_addr = { 0, };
-	NMIPAddr gateway_addr = { 0, };
+	NMIPAddr net_addr = { };
+	NMIPAddr gateway_addr = { };
 	int net_prefix = -1;
 	NMIPRoute *route;
 	NMSettingIPConfig *s_ip;
@@ -518,19 +527,18 @@ parse_rd_route (GHashTable *connections, char *argument)
 	gateway = get_word (&argument, ':');
 	interface = get_word (&argument, ':');
 
-	family = guess_ip_address_family (net);
 	connection = get_conn (connections, interface, NULL);
 
 	if (net && *net) {
-		if (!nm_utils_parse_inaddr_prefix_bin (family, net, &net_addr, &net_prefix)) {
-			_LOGW (LOGD_CORE, "Unrecognized address: %s\n", net);
+		if (!nm_utils_parse_inaddr_prefix_bin (family, net, &family, &net_addr, &net_prefix)) {
+			_LOGW (LOGD_CORE, "Unrecognized address: %s", net);
 			return;
 		}
 	}
 
-	if (gateway && *net) {
-		if (!nm_utils_parse_inaddr_bin (family, gateway, &gateway_addr)) {
-			_LOGW (LOGD_CORE, "Unrecognized address: %s\n", gateway);
+	if (gateway && *gateway) {
+		if (!nm_utils_parse_inaddr_bin (family, gateway, &family, &gateway_addr)) {
+			_LOGW (LOGD_CORE, "Unrecognized address: %s", gateway);
 			return;
 		}
 	}
@@ -547,7 +555,7 @@ parse_rd_route (GHashTable *connections, char *argument)
 			net_prefix = 128;
 		break;
 	default:
-		_LOGW (LOGD_CORE, "Unknown address family: %s\n", net);
+		_LOGW (LOGD_CORE, "Unknown address family: %s", net);
 		return;
 	}
 
@@ -588,7 +596,7 @@ parse_vlan (GHashTable *connections, char *argument)
 	              NULL);
 
 	if (argument && *argument)
-		_LOGW (LOGD_CORE, "Ignoring extra: '%s'.\n", argument);
+		_LOGW (LOGD_CORE, "Ignoring extra: '%s'.", argument);
 }
 
 static void
@@ -624,14 +632,14 @@ parse_nameserver (GHashTable *connections, char *argument)
 		s_ip = nm_connection_get_setting_ip6_config (connection);
 		break;
 	default:
-		_LOGW (LOGD_CORE, "Unknown address family: %s\n", dns);
+		_LOGW (LOGD_CORE, "Unknown address family: %s", dns);
 		break;
 	}
 
 	nm_setting_ip_config_add_dns (s_ip, dns);
 
 	if (argument && *argument)
-		_LOGW (LOGD_CORE, "xIgnoring extra: '%s'.\n", argument);
+		_LOGW (LOGD_CORE, "Ignoring extra: '%s'.", argument);
 }
 
 static void
diff --git a/src/initrd/nmi-ibft-reader.c b/src/initrd/nmi-ibft-reader.c
index c9275467..efac5307 100644
--- a/src/initrd/nmi-ibft-reader.c
+++ b/src/initrd/nmi-ibft-reader.c
@@ -1,18 +1,19 @@
 /* NetworkManager initrd configuration generator
  *
- * 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 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 program is distributed in the hope that it will be useful,
+ * 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 General Public License for more details.
+ * 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 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.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
  *
  * Copyright 2014 - 2018 Red Hat, Inc.
  */
@@ -22,13 +23,11 @@
 #include "nm-initrd-generator.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <sys/wait.h>
 #include <sys/inotify.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 
diff --git a/src/initrd/tests/meson.build b/src/initrd/tests/meson.build
index 6b316d4e..0ef72fff 100644
--- a/src/initrd/tests/meson.build
+++ b/src/initrd/tests/meson.build
@@ -13,11 +13,11 @@ foreach test_unit : test_units
     test_unit + '.c',
     dependencies: test_nm_dep,
     c_args: cflags,
-    link_with: libnmi_core
+    link_with: libnmi_core,
   )
   test(
     'initrd/' + test_unit,
     test_script,
-    args: test_args + [exe.full_path()]
+    args: test_args + [exe.full_path()],
   )
 endforeach
diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/initrd/tests/test-cmdline-reader.c
index 4db71147..1a87505a 100644
--- a/src/initrd/tests/test-cmdline-reader.c
+++ b/src/initrd/tests/test-cmdline-reader.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/ether.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
@@ -62,6 +61,8 @@ test_auto (void)
 	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
 	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "Wired Connection");
 	g_assert_cmpint (nm_setting_connection_get_timestamp (s_con), ==, 0);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_MULTIPLE);
+
 	g_assert (nm_setting_connection_get_autoconnect (s_con));
 
 	s_wired = nm_connection_get_setting_wired (connection);
@@ -328,6 +329,7 @@ test_some_more (void)
 	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
 	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "Wired Connection");
 	g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "eth1");
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_MULTIPLE);
 
 	s_wired = nm_connection_get_setting_wired (connection);
 	g_assert (s_wired);
@@ -361,6 +363,7 @@ test_some_more (void)
 	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
 	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "ens10");
 	g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "ens10");
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
 
 	s_wired = nm_connection_get_setting_wired (connection);
 	g_assert (s_wired);
@@ -454,6 +457,7 @@ test_bond (void)
 	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth0");
 	g_assert_cmpstr (nm_setting_connection_get_slave_type (s_con), ==, NM_SETTING_BOND_SETTING_NAME);
 	g_assert_cmpstr (nm_setting_connection_get_master (s_con), ==, master_uuid);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
 
 	connection = g_hash_table_lookup (connections, "eth1");
 	g_assert (connection);
@@ -466,6 +470,7 @@ test_bond (void)
 	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth1");
 	g_assert_cmpstr (nm_setting_connection_get_slave_type (s_con), ==, NM_SETTING_BOND_SETTING_NAME);
 	g_assert_cmpstr (nm_setting_connection_get_master (s_con), ==, master_uuid);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
 }
 
 static void
@@ -525,6 +530,7 @@ test_bond_default (void)
 	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth0");
 	g_assert_cmpstr (nm_setting_connection_get_slave_type (s_con), ==, NM_SETTING_BOND_SETTING_NAME);
 	g_assert_cmpstr (nm_setting_connection_get_master (s_con), ==, master_uuid);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
 }
 
 static void
@@ -588,6 +594,7 @@ test_bridge (void)
 	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth0");
 	g_assert_cmpstr (nm_setting_connection_get_slave_type (s_con), ==, NM_SETTING_BRIDGE_SETTING_NAME);
 	g_assert_cmpstr (nm_setting_connection_get_master (s_con), ==, master_uuid);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
 
 	connection = g_hash_table_lookup (connections, "eth1");
 	g_assert (connection);
@@ -600,6 +607,7 @@ test_bridge (void)
 	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth1");
 	g_assert_cmpstr (nm_setting_connection_get_slave_type (s_con), ==, NM_SETTING_BRIDGE_SETTING_NAME);
 	g_assert_cmpstr (nm_setting_connection_get_master (s_con), ==, master_uuid);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
 }
 
 static void
@@ -657,6 +665,7 @@ test_bridge_default (void)
 	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth0");
 	g_assert_cmpstr (nm_setting_connection_get_slave_type (s_con), ==, NM_SETTING_BRIDGE_SETTING_NAME);
 	g_assert_cmpstr (nm_setting_connection_get_master (s_con), ==, master_uuid);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
 }
 
 static void
@@ -713,6 +722,7 @@ test_team (void)
 	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth0");
 	g_assert_cmpstr (nm_setting_connection_get_slave_type (s_con), ==, NM_SETTING_TEAM_SETTING_NAME);
 	g_assert_cmpstr (nm_setting_connection_get_master (s_con), ==, master_uuid);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
 
 	connection = g_hash_table_lookup (connections, "eth1");
 	g_assert (connection);
@@ -725,6 +735,7 @@ test_team (void)
 	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth1");
 	g_assert_cmpstr (nm_setting_connection_get_slave_type (s_con), ==, NM_SETTING_TEAM_SETTING_NAME);
 	g_assert_cmpstr (nm_setting_connection_get_master (s_con), ==, master_uuid);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
 }
 
 static void
diff --git a/src/initrd/tests/test-ibft-reader.c b/src/initrd/tests/test-ibft-reader.c
index 340b3896..64362d18 100644
--- a/src/initrd/tests/test-ibft-reader.c
+++ b/src/initrd/tests/test-ibft-reader.c
@@ -1,18 +1,19 @@
 /* NetworkManager initrd configuration generator
  *
- * 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 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 program is distributed in the hope that it will be useful,
+ * 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 General Public License for more details.
+ * 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 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.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
  *
  * Copyright 2014 - 2018 Red Hat, Inc.
  */
@@ -22,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/ether.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
diff --git a/src/main-utils.c b/src/main-utils.c
index a597ede7..f3a2edf5 100644
--- a/src/main-utils.c
+++ b/src/main-utils.c
@@ -22,7 +22,6 @@
 #include "nm-default.h"
 
 #include <stdio.h>
-#include <errno.h>
 #include <fcntl.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -93,21 +92,26 @@ nm_main_utils_write_pidfile (const char *pidfile)
 {
 	char pid[16];
 	int fd;
+	int errsv;
 	gboolean success = FALSE;
 
 	if ((fd = open (pidfile, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 00644)) < 0) {
-		fprintf (stderr, _("Opening %s failed: %s\n"), pidfile, strerror (errno));
+		errsv = errno;
+		fprintf (stderr, _("Opening %s failed: %s\n"), pidfile, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
 	g_snprintf (pid, sizeof (pid), "%d", getpid ());
-	if (write (fd, pid, strlen (pid)) < 0)
-		fprintf (stderr, _("Writing to %s failed: %s\n"), pidfile, strerror (errno));
-	else
+	if (write (fd, pid, strlen (pid)) < 0) {
+		errsv = errno;
+		fprintf (stderr, _("Writing to %s failed: %s\n"), pidfile, nm_strerror_native (errsv));
+	} else
 		success = TRUE;
 
-	if (nm_close (fd))
-		fprintf (stderr, _("Closing %s failed: %s\n"), pidfile, strerror (errno));
+	if (nm_close (fd)) {
+		errsv = errno;
+		fprintf (stderr, _("Closing %s failed: %s\n"), pidfile, nm_strerror_native (errsv));
+	}
 
 	return success;
 }
@@ -126,13 +130,13 @@ nm_main_utils_ensure_statedir ()
 	    && parent[1] != '\0'
 	    && g_mkdir_with_parents (parent, 0755) != 0) {
 		errsv = errno;
-		fprintf (stderr, "Cannot create parents for '%s': %s", NMSTATEDIR, g_strerror (errsv));
+		fprintf (stderr, "Cannot create parents for '%s': %s", NMSTATEDIR, nm_strerror_native (errsv));
 		exit (1);
 	}
 	/* Ensure state directory exists */
 	if (g_mkdir_with_parents (NMSTATEDIR, 0700) != 0) {
 		errsv = errno;
-		fprintf (stderr, "Cannot create '%s': %s", NMSTATEDIR, g_strerror (errsv));
+		fprintf (stderr, "Cannot create '%s': %s", NMSTATEDIR, nm_strerror_native (errsv));
 		exit (1);
 	}
 }
@@ -145,7 +149,7 @@ nm_main_utils_ensure_rundir ()
 	/* Setup runtime directory */
 	if (g_mkdir_with_parents (NMRUNDIR, 0755) != 0) {
 		errsv = errno;
-		fprintf (stderr, _("Cannot create '%s': %s"), NMRUNDIR, g_strerror (errsv));
+		fprintf (stderr, _("Cannot create '%s': %s"), NMRUNDIR, nm_strerror_native (errsv));
 		exit (1);
 	}
 
@@ -156,7 +160,7 @@ nm_main_utils_ensure_rundir ()
 	if (g_mkdir (NM_CONFIG_DEVICE_STATE_DIR, 0755) != 0) {
 		errsv = errno;
 		if (errsv != EEXIST) {
-			fprintf (stderr, _("Cannot create '%s': %s"), NM_CONFIG_DEVICE_STATE_DIR, g_strerror (errsv));
+			fprintf (stderr, _("Cannot create '%s': %s"), NM_CONFIG_DEVICE_STATE_DIR, nm_strerror_native (errsv));
 			exit (1);
 		}
 	}
diff --git a/src/main.c b/src/main.c
index f834fa94..9f979cf4 100644
--- a/src/main.c
+++ b/src/main.c
@@ -23,15 +23,12 @@
 
 #include <getopt.h>
 #include <locale.h>
-#include <errno.h>
 #include <stdlib.h>
 #include <signal.h>
-#include <pthread.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <string.h>
 #include <sys/resource.h>
 
 #include "main-utils.h"
@@ -155,7 +152,7 @@ nm_main_config_reload (int signal)
 	 *
 	 * Hence, a NMConfig singleton instance must always be
 	 * available. */
-	nm_config_reload (nm_config_get (), reload_flags);
+	nm_config_reload (nm_config_get (), reload_flags, TRUE);
 }
 
 static void
@@ -232,6 +229,8 @@ main (int argc, char *argv[])
 	NMConfigCmdLineOptions *config_cli;
 	guint sd_id = 0;
 	GError *error_invalid_logging_config = NULL;
+	const char *const *warnings;
+	int errsv;
 
 	/* Known to cause a possible deadlock upon GDBus initialization:
 	 * https://bugzilla.gnome.org/show_bug.cgi?id=674885 */
@@ -333,12 +332,10 @@ main (int argc, char *argv[])
 
 	if (global_opt.become_daemon && !nm_config_get_is_debug (config)) {
 		if (daemon (0, 0) < 0) {
-			int saved_errno;
-
-			saved_errno = errno;
+			errsv = errno;
 			fprintf (stderr, _("Could not daemonize: %s [error %u]\n"),
-			         g_strerror (saved_errno),
-			         saved_errno);
+			         nm_strerror_native (errsv),
+			         errsv);
 			exit (1);
 		}
 		wrote_pidfile = nm_main_utils_write_pidfile (global_opt.pidfile);
@@ -354,7 +351,7 @@ main (int argc, char *argv[])
 		                              NM_CONFIG_KEYFILE_GROUP_LOGGING,
 		                              NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND,
 		                              NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
-		nm_logging_syslog_openlog (v, nm_config_get_is_debug (config));
+		nm_logging_init (v, nm_config_get_is_debug (config));
 	}
 
 	nm_log_info (LOGD_CORE, "NetworkManager (version " NM_DIST_VERSION ") is starting... (%s)",
@@ -376,6 +373,11 @@ main (int argc, char *argv[])
 		nm_clear_g_free (&bad_domains);
 	}
 
+	warnings = nm_config_get_warnings (config);
+	for ( ; warnings && *warnings; warnings++)
+		nm_log_warn (LOGD_CORE, "config: %s", *warnings);
+	nm_config_clear_warnings (config);
+
 	/* the first access to State causes the file to be read (and possibly print a warning) */
 	nm_config_state_get (config);
 
diff --git a/src/meson.build b/src/meson.build
index 28fcfa8f..06a0dc57 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -2,7 +2,7 @@ src_inc = include_directories('.')
 
 install_data(
   'org.freedesktop.NetworkManager.conf',
-  install_dir: dbus_conf_dir
+  install_dir: dbus_conf_dir,
 )
 
 subdir('systemd')
@@ -14,7 +14,7 @@ nm_cflags = ['-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_DAEMON'
 nm_dep = declare_dependency(
   include_directories: src_inc,
   dependencies: nm_core_dep,
-  compile_args: nm_cflags
+  compile_args: nm_cflags,
 )
 
 cflags = nm_cflags
@@ -42,13 +42,13 @@ sources = files(
   'nm-dbus-utils.c',
   'nm-ip4-config.c',
   'nm-ip6-config.c',
-  'nm-logging.c'
+  'nm-logging.c',
 )
 
 deps = [
   libsystemd_dep,
   libudev_dep,
-  nm_core_dep
+  nm_core_dep,
 ]
 
 if enable_wext
@@ -60,7 +60,7 @@ libnetwork_manager_base = static_library(
   sources: sources,
   dependencies: deps,
   c_args: cflags,
-  link_with: libnm_core
+  link_with: libnm_core,
 )
 
 sources = files(
@@ -133,6 +133,7 @@ sources = files(
   'nm-dispatcher.c',
   'nm-firewall-manager.c',
   'nm-hostname-manager.c',
+  'nm-keep-alive.c',
   'nm-manager.c',
   'nm-netns.c',
   'nm-pacrunner-manager.c',
@@ -140,7 +141,7 @@ sources = files(
   'nm-proxy-config.c',
   'nm-rfkill-manager.c',
   'nm-session-monitor.c',
-  'nm-sleep-monitor.c'
+  'nm-sleep-monitor.c',
 )
 
 nm_deps = [
@@ -173,14 +174,18 @@ libnetwork_manager = static_library(
   sources: sources,
   dependencies: nm_deps,
   c_args: cflags,
-  link_with: [libnetwork_manager_base, libsystemd_nm]
+  link_with: [
+    libnetwork_manager_base,
+    libnm_systemd_core,
+    libnm_systemd_shared,
+  ],
 )
 
 deps = [
   dl_dep,
   libndp_dep,
   libudev_dep,
-  nm_core_dep
+  nm_core_dep,
 ]
 
 name = 'nm-iface-helper'
@@ -190,23 +195,27 @@ executable(
   name + '.c',
   dependencies: deps,
   c_args: cflags,
-  link_with: [libnetwork_manager_base, libsystemd_nm],
+  link_with: [
+    libnetwork_manager_base,
+    libnm_systemd_core,
+    libnm_systemd_shared,
+  ],
   link_args: ldflags_linker_script_binary,
   link_depends: linker_script_binary,
   install: true,
-  install_dir: nm_libexecdir
+  install_dir: nm_libexecdir,
 )
 
 if enable_tests
   sources = files(
     'ndisc/nm-fake-ndisc.c',
     'platform/tests/test-common.c',
-    'platform/nm-fake-platform.c'
+    'platform/nm-fake-platform.c',
   )
 
   deps = [
     libudev_dep,
-    nm_core_dep
+    nm_core_dep,
   ]
 
   test_cflags = ['-DNETWORKMANAGER_COMPILATION_TEST']
@@ -219,13 +228,13 @@ if enable_tests
     sources: sources,
     dependencies: deps,
     c_args: cflags + test_cflags,
-    link_with: libnetwork_manager
+    link_with: libnetwork_manager,
   )
 
   test_nm_dep = declare_dependency(
     dependencies: nm_dep,
     compile_args: test_cflags,
-    link_with: libnetwork_manager_test
+    link_with: libnetwork_manager_test,
   )
 
   test_nm_dep_fake = declare_dependency(
@@ -281,7 +290,7 @@ ver_script = custom_target(
   input: meson.source_root(),
   output: symbol_map_name,
   depends: [ network_manager_sym, core_plugins ],
-  command: [create_exports_networkmanager, '--called-from-build', '@INPUT@']
+  command: [create_exports_networkmanager, '--called-from-build', '@INPUT@'],
 )
 
 ldflags = ['-rdynamic', '-Wl,--version-script,@0@'.format(ver_script.full_path())]
@@ -295,7 +304,7 @@ network_manager = executable(
   link_args: ldflags,
   link_depends: ver_script,
   install: true,
-  install_dir: nm_sbindir
+  install_dir: nm_sbindir,
 )
 
 if enable_tests
@@ -306,3 +315,9 @@ if enable_tests
           env: ['LD_BIND_NOW=1', 'LD_PRELOAD=' + plugin.full_path()])
   endforeach
 endif
+
+test(
+  'check-config-options',
+  find_program(join_paths(meson.source_root(), 'tools', 'check-config-options.sh')),
+  args: [meson.source_root()]
+)
diff --git a/src/ndisc/nm-fake-ndisc.c b/src/ndisc/nm-fake-ndisc.c
index 6e9a72c6..f4719231 100644
--- a/src/ndisc/nm-fake-ndisc.c
+++ b/src/ndisc/nm-fake-ndisc.c
@@ -22,7 +22,6 @@
 
 #include "nm-fake-ndisc.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 
 #include "nm-ndisc-private.h"
diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/ndisc/nm-lndp-ndisc.c
index e1003ad1..2dd7e7d8 100644
--- a/src/ndisc/nm-lndp-ndisc.c
+++ b/src/ndisc/nm-lndp-ndisc.c
@@ -22,7 +22,6 @@
 
 #include "nm-lndp-ndisc.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <netinet/icmp6.h>
 /* stdarg.h included because of a bug in ndp.h */
@@ -73,7 +72,6 @@ send_rs (NMNDisc *ndisc, GError **error)
 
 	errsv = ndp_msg_new (&msg, NDP_MSG_RS);
 	if (errsv) {
-		errsv = errsv > 0 ? errsv : -errsv;
 		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		                     "cannot create router solicitation");
 		return FALSE;
@@ -83,10 +81,10 @@ send_rs (NMNDisc *ndisc, GError **error)
 	errsv = ndp_msg_send (priv->ndp, msg);
 	ndp_msg_destroy (msg);
 	if (errsv) {
-		errsv = errsv > 0 ? errsv : -errsv;
+		errsv = nm_errno_native (errsv);
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "%s (%d)",
-		             g_strerror (errsv), errsv);
+		             nm_strerror_native (errsv), errsv);
 		return FALSE;
 	}
 
@@ -259,7 +257,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 
 			/* Pad the lifetime somewhat to give a bit of slack in cases
 			 * where one RA gets lost or something (which can happen on unreliable
-			 * links like WiFi where certain types of frames are not retransmitted).
+			 * links like Wi-Fi where certain types of frames are not retransmitted).
 			 * Note that 0 has special meaning and is therefore not adjusted.
 			 */
 			if (dns_server.lifetime && dns_server.lifetime < 7200)
@@ -281,7 +279,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 
 			/* Pad the lifetime somewhat to give a bit of slack in cases
 			 * where one RA gets lost or something (which can happen on unreliable
-			 * links like WiFi where certain types of frames are not retransmitted).
+			 * links like Wi-Fi where certain types of frames are not retransmitted).
 			 * Note that 0 has special meaning and is therefore not adjusted.
 			 */
 			if (dns_domain.lifetime && dns_domain.lifetime < 7200)
@@ -361,7 +359,6 @@ send_ra (NMNDisc *ndisc, GError **error)
 
 	errsv = ndp_msg_new (&msg, NDP_MSG_RA);
 	if (errsv) {
-		errsv = errsv > 0 ? errsv : -errsv;
 		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		                     "cannot create a router advertisement");
 		return FALSE;
@@ -469,10 +466,10 @@ send_ra (NMNDisc *ndisc, GError **error)
 
 	ndp_msg_destroy (msg);
 	if (errsv) {
-		errsv = errsv > 0 ? errsv : -errsv;
+		errsv = nm_errno_native (errsv);
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "%s (%d)",
-		             g_strerror (errsv), errsv);
+		             nm_strerror_native (errsv), errsv);
 		return FALSE;
 	}
 
@@ -536,17 +533,17 @@ start (NMNDisc *ndisc)
 
 /*****************************************************************************/
 
-static inline int
+static int
 ipv6_sysctl_get (NMPlatform *platform, const char *ifname, const char *property, int min, int max, int defval)
 {
-	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-	return (int) nm_platform_sysctl_get_int_checked (platform,
-	                                                 NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, ifname, property)),
-	                                                 10,
-	                                                 min,
-	                                                 max,
-	                                                 defval);
+	return nm_platform_sysctl_ip_conf_get_int_checked (platform,
+	                                                   AF_INET6,
+	                                                   ifname,
+	                                                   property,
+	                                                   10,
+	                                                   min,
+	                                                   max,
+	                                                   defval);
 }
 
 static void
@@ -600,10 +597,10 @@ nm_lndp_ndisc_new (NMPlatform *platform,
 	errsv = ndp_open (&priv->ndp);
 
 	if (errsv != 0) {
-		errsv = errsv > 0 ? errsv : -errsv;
+		errsv = nm_errno_native (errsv);
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "failure creating libndp socket: %s (%d)",
-		             g_strerror (errsv), errsv);
+		             nm_strerror_native (errsv), errsv);
 		g_object_unref (ndisc);
 		return NULL;
 	}
diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c
index 1dd8398c..dd535630 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/ndisc/nm-ndisc.c
@@ -24,7 +24,6 @@
 
 #include <stdlib.h>
 #include <arpa/inet.h>
-#include <string.h>
 
 #include "nm-setting-ip6-config.h"
 
@@ -442,7 +441,7 @@ nm_ndisc_add_address (NMNDisc *ndisc,
 
 		if (from_ra) {
 			/* RFC4862 5.5.3.d, we find an existing address with the same prefix.
-			 * (note that all prefixes at this point have implicity length /64). */
+			 * (note that all prefixes at this point have implicitly length /64). */
 			if (memcmp (&item->address, &new->address, 8) == 0) {
 				existing = item;
 				break;
@@ -965,7 +964,9 @@ nm_ndisc_dad_failed (NMNDisc *ndisc, const struct in6_addr *address, gboolean em
 		NMNDiscAddress *item = &g_array_index (rdata->addresses, NMNDiscAddress, i);
 
 		if (IN6_ARE_ADDR_EQUAL (&item->address, address)) {
-			_LOGD ("DAD failed for discovered address %s", nm_utils_inet6_ntop (address, NULL));
+			char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
+			_LOGD ("DAD failed for discovered address %s", nm_utils_inet6_ntop (address, sbuf));
 			changed = TRUE;
 			if (!complete_address (ndisc, item)) {
 				g_array_remove_index (rdata->addresses, i);
@@ -1056,10 +1057,11 @@ _config_changed_log (NMNDisc *ndisc, NMNDiscConfigMap changed)
 	}
 	for (i = 0; i < rdata->routes->len; i++) {
 		NMNDiscRoute *route = &g_array_index (rdata->routes, NMNDiscRoute, i);
+		char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 		inet_ntop (AF_INET6, &route->network, addrstr, sizeof (addrstr));
 		_LOGD ("  route %s/%u via %s pref %s exp %s", addrstr, (guint) route->plen,
-		       nm_utils_inet6_ntop (&route->gateway, NULL),
+		       nm_utils_inet6_ntop (&route->gateway, sbuf),
 		       nm_icmpv6_router_pref_to_string (route->preference, str_pref, sizeof (str_pref)),
 		       get_exp (str_exp, now_ns, route));
 	}
diff --git a/src/ndisc/tests/meson.build b/src/ndisc/tests/meson.build
index e0dc9aa6..99cf664e 100644
--- a/src/ndisc/tests/meson.build
+++ b/src/ndisc/tests/meson.build
@@ -9,7 +9,8 @@ exe = executable(
 test(
   'ndisc/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
 
 test = 'test-ndisc-linux'
diff --git a/src/ndisc/tests/test-ndisc-fake.c b/src/ndisc/tests/test-ndisc-fake.c
index 268f3b49..d2291d44 100644
--- a/src/ndisc/tests/test-ndisc-fake.c
+++ b/src/ndisc/tests/test-ndisc-fake.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <syslog.h>
 
 #include "ndisc/nm-ndisc.h"
diff --git a/src/ndisc/tests/test-ndisc-linux.c b/src/ndisc/tests/test-ndisc-linux.c
index 2764b6c0..d47c0018 100644
--- a/src/ndisc/tests/test-ndisc-linux.c
+++ b/src/ndisc/tests/test-ndisc-linux.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <syslog.h>
 
 #include "ndisc/nm-ndisc.h"
diff --git a/src/nm-act-request.c b/src/nm-act-request.c
index 0ac20b85..a79167f2 100644
--- a/src/nm-act-request.c
+++ b/src/nm-act-request.c
@@ -23,7 +23,6 @@
 
 #include "nm-act-request.h"
 
-#include <string.h>
 #include <stdlib.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -166,7 +165,7 @@ nm_act_request_get_secrets (NMActRequest *self,
                             gboolean ref_self,
                             const char *setting_name,
                             NMSecretAgentGetSecretsFlags flags,
-                            const char *hint,
+                            const char *const*hints,
                             NMActRequestSecretsFunc callback,
                             gpointer callback_data)
 {
@@ -175,7 +174,6 @@ nm_act_request_get_secrets (NMActRequest *self,
 	NMSettingsConnectionCallId *call_id_s;
 	NMSettingsConnection *settings_connection;
 	NMConnection *applied_connection;
-	const char *hints[2] = { hint, NULL };
 
 	g_return_val_if_fail (NM_IS_ACT_REQUEST (self), NULL);
 
@@ -534,11 +532,12 @@ nm_act_request_init (NMActRequest *req)
  *
  * @settings_connection: (allow-none): the connection to activate @device with
  * @applied_connection: (allow-none): the applied connection
- * @specific_object: the object path of the specific object (ie, WiFi access point,
+ * @specific_object: the object path of the specific object (ie, Wi-Fi 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_reason: the reason for activation
+ * @initial_state_flags: the initial state flags.
  * @device: the device/interface to configure according to @connection
  *
  * Creates a new device-based activation request. If an applied connection is
@@ -553,6 +552,7 @@ nm_act_request_new (NMSettingsConnection *settings_connection,
                     NMAuthSubject *subject,
                     NMActivationType activation_type,
                     NMActivationReason activation_reason,
+                    NMActivationStateFlags initial_state_flags,
                     NMDevice *device)
 {
 	g_return_val_if_fail (!settings_connection || NM_IS_SETTINGS_CONNECTION (settings_connection), NULL);
@@ -567,6 +567,7 @@ nm_act_request_new (NMSettingsConnection *settings_connection,
 	                                      NM_ACTIVE_CONNECTION_INT_SUBJECT, subject,
 	                                      NM_ACTIVE_CONNECTION_INT_ACTIVATION_TYPE, (int) activation_type,
 	                                      NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON, (int) activation_reason,
+	                                      NM_ACTIVE_CONNECTION_STATE_FLAGS, (guint) initial_state_flags,
 	                                      NULL);
 }
 
diff --git a/src/nm-act-request.h b/src/nm-act-request.h
index a8f09271..e16e1ecf 100644
--- a/src/nm-act-request.h
+++ b/src/nm-act-request.h
@@ -42,6 +42,7 @@ NMActRequest *nm_act_request_new          (NMSettingsConnection *settings_connec
                                            NMAuthSubject *subject,
                                            NMActivationType activation_type,
                                            NMActivationReason activation_reason,
+                                           NMActivationStateFlags initial_state_flags,
                                            NMDevice *device);
 
 NMSettingsConnection *nm_act_request_get_settings_connection (NMActRequest *req);
@@ -68,7 +69,7 @@ NMActRequestGetSecretsCallId *nm_act_request_get_secrets (NMActRequest *req,
                                                           gboolean take_ref,
                                                           const char *setting_name,
                                                           NMSecretAgentGetSecretsFlags flags,
-                                                          const char *hint,
+                                                          const char *const*hints,
                                                           NMActRequestSecretsFunc callback,
                                                           gpointer callback_data);
 
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index a2f9ae4a..7b9f2cc7 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -30,6 +30,7 @@
 #include "nm-auth-utils.h"
 #include "nm-auth-manager.h"
 #include "nm-auth-subject.h"
+#include "nm-keep-alive.h"
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
 
@@ -75,6 +76,7 @@ typedef struct _NMActiveConnectionPrivate {
 		gpointer user_data;
 	} auth;
 
+	NMKeepAlive *keep_alive;
 } NMActiveConnectionPrivate;
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMActiveConnection,
@@ -159,16 +161,21 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_state_to_string, NMActiveConnectionState,
 	NM_UTILS_LOOKUP_STR_ITEM (NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, "deactivating"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,  "deactivated"),
 );
-#define state_to_string(state) NM_UTILS_LOOKUP_STR (_state_to_string, state)
+
+#define state_to_string_a(state) NM_UTILS_LOOKUP_STR_A (_state_to_string, state)
+
+/* the maximum required buffer size for _state_flags_to_string(). */
+#define _NM_ACTIVATION_STATE_FLAG_TO_STRING_BUFSIZE (255)
 
 NM_UTILS_FLAGS2STR_DEFINE_STATIC (_state_flags_to_string, NMActivationStateFlags,
-	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_NONE,                 "none"),
-	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IS_MASTER,            "is-master"),
-	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IS_SLAVE,             "is-slave"),
-	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_LAYER2_READY,         "layer2-ready"),
-	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IP4_READY,            "ip4-ready"),
-	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IP6_READY,            "ip6-ready"),
-	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES,    "master-has-slaves"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_NONE,                                 "none"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IS_MASTER,                            "is-master"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IS_SLAVE,                             "is-slave"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_LAYER2_READY,                         "layer2-ready"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IP4_READY,                            "ip4-ready"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IP6_READY,                            "ip6-ready"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES,                    "master-has-slaves"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, "lifetime-bound-to-profile-visibility"),
 );
 
 /*****************************************************************************/
@@ -250,8 +257,14 @@ nm_active_connection_set_state (NMActiveConnection *self,
 		return;
 
 	_LOGD ("set state %s (was %s)",
-	       state_to_string (new_state),
-	       state_to_string (priv->state));
+	       state_to_string_a (new_state),
+	       state_to_string_a (priv->state));
+
+	if (new_state > NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
+		/* once we are about to deactivate, we don't need the keep-alive instance
+		 * anymore. Freeze/disarm it. */
+		nm_keep_alive_disarm (priv->keep_alive);
+	}
 
 	if (   new_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED
 	    && priv->activation_type == NM_ACTIVATION_TYPE_ASSUME) {
@@ -353,14 +366,20 @@ nm_active_connection_set_state_flags_full (NMActiveConnection *self,
 
 	f = (priv->state_flags & ~mask) | (state_flags & mask);
 	if (f != priv->state_flags) {
-		char buf1[G_N_ELEMENTS (_nm_utils_to_string_buffer)];
-		char buf2[G_N_ELEMENTS (_nm_utils_to_string_buffer)];
+		char buf1[_NM_ACTIVATION_STATE_FLAG_TO_STRING_BUFSIZE];
+		char buf2[_NM_ACTIVATION_STATE_FLAG_TO_STRING_BUFSIZE];
 
 		_LOGD ("set state-flags %s (was %s)",
 		       _state_flags_to_string (f, buf1, sizeof (buf1)),
 		       _state_flags_to_string (priv->state_flags, buf2, sizeof (buf2)));
 		priv->state_flags = f;
 		_notify (self, PROP_STATE_FLAGS);
+
+		nm_keep_alive_set_settings_connection_watch_visible (priv->keep_alive,
+		                                                       NM_FLAGS_HAS (priv->state_flags,
+		                                                                     NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY)
+		                                                     ? priv->settings_connection.obj
+		                                                     : NULL);
 	}
 }
 
@@ -768,11 +787,11 @@ check_master_ready (NMActiveConnection *self)
 	       signalling
 	           ? "signal"
 	           : (priv->master_ready ? "already signalled" : "not signalling"),
-	       state_to_string (priv->state),
+	       state_to_string_a (priv->state),
 	       priv->master
 	           ? nm_sprintf_bufa (128, "master %p is in state %s",
 	                              priv->master,
-	                              state_to_string (nm_active_connection_get_state (priv->master)))
+	                              state_to_string_a (nm_active_connection_get_state (priv->master)))
 	           : "no master");
 
 	if (signalling) {
@@ -836,7 +855,7 @@ nm_active_connection_set_master (NMActiveConnection *self, NMActiveConnection *m
 	_LOGD ("set master %p, %s, state %s",
 	       master,
 	       nm_active_connection_get_settings_connection_id (master),
-	       state_to_string (nm_active_connection_get_state (master)));
+	       state_to_string_a (nm_active_connection_get_state (master)));
 
 	priv->master = g_object_ref (master);
 	g_signal_connect (priv->master,
@@ -906,6 +925,32 @@ nm_active_connection_get_activation_reason (NMActiveConnection *self)
 
 /*****************************************************************************/
 
+/**
+ * nm_active_connection_get_keep_alive:
+ * @self: the #NMActiveConnection instance
+ *
+ * Gives the #NMKeepAlive instance of the active connection. Note that
+ * @self is guaranteed not to swap the keep-alive instance, so it is
+ * in particular safe to assume that the keep-alive instance is alive
+ * as long as @self, and that nm_active_connection_get_keep_alive()
+ * will return always the same instance.
+ *
+ * In particular this means, that it is safe and encouraged, that you
+ * register to the notify:alive property changed signal of the returned
+ * instance.
+ *
+ * Returns: the #NMKeepAlive instance.
+ */
+NMKeepAlive *
+nm_active_connection_get_keep_alive (NMActiveConnection *self)
+{
+	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);
+
+	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->keep_alive;
+}
+
+/*****************************************************************************/
+
 static void
 _settings_connection_flags_changed (NMSettingsConnection *settings_connection,
                                     NMActiveConnection *self)
@@ -1350,6 +1395,12 @@ set_property (GObject *object, guint prop_id,
 			g_return_if_reached ();
 		_set_activation_type (self, (NMActivationType) i);
 		break;
+	case PROP_STATE_FLAGS:
+		/* construct-only */
+		priv->state_flags = g_value_get_uint (value);
+		nm_assert ((guint) priv->state_flags == g_value_get_uint (value));
+		nm_assert (!NM_FLAGS_ANY (priv->state_flags, ~NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY));
+		break;
 	case PROP_INT_ACTIVATION_REASON:
 		/* construct-only */
 		i = g_value_get_int (value);
@@ -1400,6 +1451,10 @@ nm_active_connection_init (NMActiveConnection *self)
 
 	priv->activation_type = NM_ACTIVATION_TYPE_MANAGED;
 	priv->version_id = _version_id_new ();
+
+	/* the keep-alive instance must never change. Callers rely on that. */
+	priv->keep_alive = nm_keep_alive_new ();
+	_nm_keep_alive_set_owner (priv->keep_alive, G_OBJECT (self));
 }
 
 static void
@@ -1429,6 +1484,10 @@ constructed (GObject *object)
 		                              g_steal_pointer (&priv->applied_connection));
 	}
 
+	if (NM_FLAGS_HAS (priv->state_flags,
+	                  NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY))
+		nm_keep_alive_set_settings_connection_watch_visible (priv->keep_alive, priv->settings_connection.obj);
+
 	g_return_if_fail (priv->subject);
 	g_return_if_fail (priv->activation_reason != NM_ACTIVATION_REASON_UNSET);
 }
@@ -1475,6 +1534,9 @@ finalize (GObject *object)
 
 	nm_dbus_track_obj_path_set (&priv->settings_connection, NULL, FALSE);
 
+	_nm_keep_alive_set_owner (priv->keep_alive, NULL);
+	g_clear_object (&priv->keep_alive);
+
 	G_OBJECT_CLASS (nm_active_connection_parent_class)->finalize (object);
 }
 
@@ -1579,7 +1641,7 @@ nm_active_connection_class_init (NMActiveConnectionClass *ac_class)
 	obj_properties[PROP_STATE_FLAGS] =
 	     g_param_spec_uint (NM_ACTIVE_CONNECTION_STATE_FLAGS, "", "",
 	                        0, G_MAXUINT32, NM_ACTIVATION_STATE_FLAG_NONE,
-	                        G_PARAM_READABLE |
+	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                        G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_DEFAULT] =
diff --git a/src/nm-active-connection.h b/src/nm-active-connection.h
index b28c2b02..1d4ce29e 100644
--- a/src/nm-active-connection.h
+++ b/src/nm-active-connection.h
@@ -163,6 +163,13 @@ nm_active_connection_set_state_flags (NMActiveConnection *self,
 	nm_active_connection_set_state_flags_full (self, state_flags, state_flags);
 }
 
+static inline void
+nm_active_connection_set_state_flags_clear (NMActiveConnection *self,
+                                            NMActivationStateFlags state_flags)
+{
+	nm_active_connection_set_state_flags_full (self, NM_ACTIVATION_STATE_FLAG_NONE, state_flags);
+}
+
 NMDevice *    nm_active_connection_get_device (NMActiveConnection *self);
 
 gboolean      nm_active_connection_set_device (NMActiveConnection *self, NMDevice *device);
@@ -185,6 +192,8 @@ NMActivationType nm_active_connection_get_activation_type (NMActiveConnection *s
 
 NMActivationReason nm_active_connection_get_activation_reason (NMActiveConnection *self);
 
+NMKeepAlive  *nm_active_connection_get_keep_alive (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 93bd9551..9f868508 100644
--- a/src/nm-audit-manager.c
+++ b/src/nm-audit-manager.c
@@ -22,8 +22,6 @@
 
 #include "nm-audit-manager.h"
 
-#include <errno.h>
-#include <string.h>
 #if HAVE_LIBAUDIT
 #include <libaudit.h>
 #endif
@@ -337,15 +335,17 @@ init_auditd (NMAuditManager *self)
 {
 	NMAuditManagerPrivate *priv = NM_AUDIT_MANAGER_GET_PRIVATE (self);
 	NMConfigData *data = nm_config_get_data (priv->config);
+	int errsv;
 
 	if (nm_config_data_get_value_boolean (data, NM_CONFIG_KEYFILE_GROUP_LOGGING,
-	                                      NM_CONFIG_KEYFILE_KEY_AUDIT,
+	                                      NM_CONFIG_KEYFILE_KEY_LOGGING_AUDIT,
 	                                      NM_CONFIG_DEFAULT_LOGGING_AUDIT_BOOL)) {
 		if (priv->auditd_fd < 0) {
 			priv->auditd_fd = audit_open ();
-			if (priv->auditd_fd < 0)
-				_LOGE (LOGD_CORE, "failed to open auditd socket: %s", strerror (errno));
-			else
+			if (priv->auditd_fd < 0) {
+				errsv = errno;
+				_LOGE (LOGD_CORE, "failed to open auditd socket: %s", nm_strerror_native (errsv));
+			} else
 				_LOGD (LOGD_CORE, "socket created");
 		}
 	} else {
diff --git a/src/nm-auth-subject.c b/src/nm-auth-subject.c
index 9ed65e2d..dff331a8 100644
--- a/src/nm-auth-subject.c
+++ b/src/nm-auth-subject.c
@@ -30,7 +30,6 @@
 
 #include "nm-auth-subject.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-dbus-manager.h"
diff --git a/src/nm-auth-utils.c b/src/nm-auth-utils.c
index b41f6efa..146f6883 100644
--- a/src/nm-auth-utils.c
+++ b/src/nm-auth-utils.c
@@ -22,10 +22,7 @@
 
 #include "nm-auth-utils.h"
 
-#include <string.h>
-
 #include "nm-utils/nm-c-list.h"
-
 #include "nm-setting-connection.h"
 #include "nm-auth-subject.h"
 #include "nm-auth-manager.h"
@@ -139,7 +136,7 @@ nm_auth_chain_get_data (NMAuthChain *self, const char *tag)
  * @self: A #NMAuthChain.
  * @tag: A "tag" uniquely identifying the data to steal.
  *
- * Removes the datum assocated with @tag from the chain's data associations,
+ * Removes the datum associated with @tag from the chain's data associations,
  * without invoking the association's destroy handler.  The caller assumes
  * ownership over the returned value.
  *
diff --git a/src/nm-checkpoint.c b/src/nm-checkpoint.c
index b0cf1f51..9b1622ec 100644
--- a/src/nm-checkpoint.c
+++ b/src/nm-checkpoint.c
@@ -22,8 +22,6 @@
 
 #include "nm-checkpoint.h"
 
-#include <string.h>
-
 #include "nm-active-connection.h"
 #include "nm-act-request.h"
 #include "nm-auth-subject.h"
@@ -46,6 +44,7 @@ typedef struct {
 	guint64 ac_version_id;
 	NMDeviceState state;
 	bool realized:1;
+	bool activation_lifetime_bound_to_profile_visiblity:1;
 	NMUnmanFlagOp unmanaged_explicit;
 	NMActivationReason activation_reason;
 } DeviceCheckpoint;
@@ -335,6 +334,9 @@ activate:
 				                                     subject,
 				                                     NM_ACTIVATION_TYPE_MANAGED,
 				                                     dev_checkpoint->activation_reason,
+				                                       dev_checkpoint->activation_lifetime_bound_to_profile_visiblity
+				                                     ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
+				                                     : NM_ACTIVATION_STATE_FLAG_NONE,
 				                                     &local_error)) {
 					_LOGW ("rollback: reactivation of connection %s/%s failed: %s",
 					       nm_settings_connection_get_id (connection),
@@ -439,6 +441,8 @@ device_checkpoint_create (NMDevice *device)
 		dev_checkpoint->settings_connection = nm_simple_connection_new_clone (nm_settings_connection_get_connection (settings_connection));
 		dev_checkpoint->ac_version_id = nm_active_connection_version_id_get (NM_ACTIVE_CONNECTION (act_request));
 		dev_checkpoint->activation_reason = nm_active_connection_get_activation_reason (NM_ACTIVE_CONNECTION (act_request));
+		dev_checkpoint->activation_lifetime_bound_to_profile_visiblity = NM_FLAGS_HAS (nm_active_connection_get_state_flags (NM_ACTIVE_CONNECTION (act_request)),
+		                                                                               NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
 	}
 
 	return dev_checkpoint;
diff --git a/src/nm-config-data.c b/src/nm-config-data.c
index 0259f001..608b7e58 100644
--- a/src/nm-config-data.c
+++ b/src/nm-config-data.c
@@ -23,8 +23,6 @@
 
 #include "nm-config-data.h"
 
-#include <string.h>
-
 #include "nm-config.h"
 #include "devices/nm-device.h"
 #include "nm-core-internal.h"
@@ -110,6 +108,8 @@ typedef struct {
 	char *rc_manager;
 
 	NMGlobalDnsConfig *global_dns;
+
+	bool systemd_resolved:1;
 } NMConfigDataPrivate;
 
 struct _NMConfigData {
@@ -216,7 +216,6 @@ nm_config_data_get_value_int64 (const NMConfigData *self, const char *group, con
 	str = nm_config_keyfile_get_value (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, key, NM_CONFIG_GET_VALUE_NONE);
 	val = _nm_utils_ascii_str_to_int64 (str, base, min, max, fallback);
 	if (str) {
-		/* preserve errno from the parsing. */
 		errsv = errno;
 		g_free (str);
 		errno = errsv;
@@ -323,6 +322,14 @@ nm_config_data_get_rc_manager (const NMConfigData *self)
 }
 
 gboolean
+nm_config_data_get_systemd_resolved (const NMConfigData *self)
+{
+	g_return_val_if_fail (self, FALSE);
+
+	return NM_CONFIG_DATA_GET_PRIVATE (self)->systemd_resolved;
+}
+
+gboolean
 nm_config_data_get_ignore_carrier (const NMConfigData *self, NMDevice *device)
 {
 	gs_free char *value = NULL;
@@ -913,7 +920,10 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
 	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);
+	strv = g_key_file_get_string_list (keyfile,
+	                                   group,
+	                                   NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_SEARCHES,
+	                                   NULL, NULL);
 	if (strv) {
 		_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
 		if (!strv[0])
@@ -922,7 +932,10 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
 			dns_config->searches = strv;
 	}
 
-	strv = g_key_file_get_string_list (keyfile, group, "options", NULL, NULL);
+	strv = g_key_file_get_string_list (keyfile,
+	                                   group,
+	                                   NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_OPTIONS,
+	                                   NULL, NULL);
 	if (strv) {
 		_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
 		for (i = 0, j = 0; strv[i]; i++) {
@@ -949,7 +962,10 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
 		    || !groups[g][domain_prefix_len])
 			continue;
 
-		strv = g_key_file_get_string_list (keyfile, groups[g], "servers", NULL, NULL);
+		strv = g_key_file_get_string_list (keyfile,
+		                                   groups[g],
+		                                   NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_SERVERS,
+		                                   NULL, NULL);
 		if (strv) {
 			_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
 			for (i = 0, j = 0; strv[i]; i++) {
@@ -970,7 +986,10 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
 		if (!servers)
 			continue;
 
-		strv = g_key_file_get_string_list (keyfile, groups[g], "options", NULL, NULL);
+		strv = g_key_file_get_string_list (keyfile,
+		                                   groups[g],
+		                                   NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_OPTIONS,
+		                                   NULL, NULL);
 		if (strv) {
 			options = _nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
 			if (!options[0])
@@ -1365,6 +1384,19 @@ nm_config_data_get_connection_default (const NMConfigData *self,
 
 	priv = NM_CONFIG_DATA_GET_PRIVATE (self);
 
+#if NM_MORE_ASSERTS > 10
+	{
+		const char **ptr;
+
+		for (ptr = __start_connection_defaults; ptr < __stop_connection_defaults; ptr++) {
+			if (nm_streq (property, *ptr))
+				break;
+		}
+
+		nm_assert (ptr < __stop_connection_defaults);
+	}
+#endif
+
 	_match_section_infos_lookup (&priv->connection_infos[0],
 	                             priv->keyfile,
 	                             property,
@@ -1397,9 +1429,12 @@ _get_connection_info_init (MatchSectionInfo *connection_info, GKeyFile *keyfile,
 
 	connection_info->match_device.spec = nm_config_get_match_spec (keyfile,
 	                                                               group,
-	                                                               "match-device",
+	                                                               NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE,
 	                                                               &connection_info->match_device.has);
-	connection_info->stop_match = nm_config_keyfile_get_boolean (keyfile, group, "stop-match", FALSE);
+	connection_info->stop_match = nm_config_keyfile_get_boolean (keyfile,
+	                                                             group,
+	                                                             NM_CONFIG_KEYFILE_KEY_STOP_MATCH,
+	                                                             FALSE);
 }
 
 static void
@@ -1642,27 +1677,58 @@ constructed (GObject *object)
 	priv->connection_infos = _match_section_infos_construct (priv->keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
 	priv->device_infos = _match_section_infos_construct (priv->keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
 
-	priv->connectivity.enabled = nm_config_keyfile_get_boolean (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "enabled", TRUE);
-	priv->connectivity.uri = nm_strstrip (g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "uri", NULL));
-	priv->connectivity.response = g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "response", NULL);
-
-	str = nm_config_keyfile_get_value (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_AUTOCONNECT_RETRIES_DEFAULT, NM_CONFIG_GET_VALUE_NONE);
+	priv->connectivity.enabled = nm_config_keyfile_get_boolean (priv->keyfile,
+	                                                            NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY,
+	                                                            NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_ENABLED,
+	                                                            TRUE);
+	priv->connectivity.uri = nm_strstrip (g_key_file_get_string (priv->keyfile,
+	                                                             NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY,
+	                                                             NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_URI,
+	                                                             NULL));
+	priv->connectivity.response = g_key_file_get_string (priv->keyfile,
+	                                                     NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY,
+	                                                     NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_RESPONSE,
+	                                                     NULL);
+	str = nm_config_keyfile_get_value (priv->keyfile,
+	                                   NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                                   NM_CONFIG_KEYFILE_KEY_MAIN_AUTOCONNECT_RETRIES_DEFAULT,
+	                                   NM_CONFIG_GET_VALUE_NONE);
 	priv->autoconnect_retries_default = _nm_utils_ascii_str_to_int64 (str, 10, 0, G_MAXINT32, 4);
 	g_free (str);
 
 	/* On missing config value, fallback to 300. On invalid value, disable connectivity checking by setting
 	 * the interval to zero. */
-	str = g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "interval", NULL);
+	str = g_key_file_get_string (priv->keyfile,
+	                             NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY,
+	                             NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_INTERVAL,
+	                             NULL);
 	priv->connectivity.interval = _nm_utils_ascii_str_to_int64 (str, 10, 0, G_MAXUINT, NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL);
 	g_free (str);
 
-	priv->dns_mode = nm_strstrip (g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "dns", NULL));
-	priv->rc_manager = nm_strstrip (g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "rc-manager", NULL));
-
-	priv->ignore_carrier = nm_config_get_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "ignore-carrier", NULL);
-	priv->assume_ipv6ll_only = nm_config_get_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "assume-ipv6ll-only", NULL);
-
-	priv->no_auto_default.specs_config = nm_config_get_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "no-auto-default", NULL);
+	priv->dns_mode = nm_strstrip (g_key_file_get_string (priv->keyfile,
+	                                                     NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                                                     NM_CONFIG_KEYFILE_KEY_MAIN_DNS,
+	                                                     NULL));
+	priv->rc_manager = nm_strstrip (g_key_file_get_string (priv->keyfile,
+	                                                       NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                                                       NM_CONFIG_KEYFILE_KEY_MAIN_RC_MANAGER,
+	                                                       NULL));
+	priv->systemd_resolved = nm_config_keyfile_get_boolean (priv->keyfile,
+	                                                        NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                                                        NM_CONFIG_KEYFILE_KEY_MAIN_SYSTEMD_RESOLVED,
+	                                                        TRUE);
+	priv->ignore_carrier = nm_config_get_match_spec (priv->keyfile,
+	                                                 NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                                                 NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER,
+	                                                 NULL);
+	priv->assume_ipv6ll_only = nm_config_get_match_spec (priv->keyfile,
+	                                                     NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                                                     NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY,
+	                                                     NULL);
+	priv->no_auto_default.specs_config = nm_config_get_match_spec (priv->keyfile,
+	                                                               NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                                                               NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT,
+	                                                               NULL);
 
 	priv->global_dns = load_global_dns (priv->keyfile_user, FALSE);
 	if (!priv->global_dns)
diff --git a/src/nm-config-data.h b/src/nm-config-data.h
index 545d9a87..c043aa35 100644
--- a/src/nm-config-data.h
+++ b/src/nm-config-data.h
@@ -170,6 +170,7 @@ gboolean          nm_config_data_get_no_auto_default_for_device (const NMConfigD
 
 const char *nm_config_data_get_dns_mode (const NMConfigData *self);
 const char *nm_config_data_get_rc_manager (const NMConfigData *self);
+gboolean nm_config_data_get_systemd_resolved (const NMConfigData *self);
 
 gboolean nm_config_data_get_ignore_carrier (const NMConfigData *self, NMDevice *device);
 gboolean nm_config_data_get_assume_ipv6ll_only (const NMConfigData *self, NMDevice *device);
@@ -177,6 +178,21 @@ int      nm_config_data_get_sriov_num_vfs (const NMConfigData *self, NMDevice *d
 
 NMGlobalDnsConfig *nm_config_data_get_global_dns_config (const NMConfigData *self);
 
+extern const char *__start_connection_defaults[];
+extern const char *__stop_connection_defaults[];
+
+#define NM_CON_DEFAULT_NOP(name) \
+	static const char *NM_UNIQ_T (connection_default, NM_UNIQ) \
+		_nm_used _nm_section ("connection_defaults") = "" name
+
+#define NM_CON_DEFAULT(name) \
+	({ \
+		static const char *__con_default_prop \
+			_nm_used _nm_section ("connection_defaults") = "" name; \
+		\
+		name; \
+	})
+
 char *nm_config_data_get_connection_default (const NMConfigData *self,
                                              const char *property,
                                              NMDevice *device);
diff --git a/src/nm-config.c b/src/nm-config.c
index 628eca4f..3e82bdec 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -23,7 +23,6 @@
 
 #include "nm-config.h"
 
-#include <string.h>
 #include <stdio.h>
 
 #include "nm-utils.h"
@@ -130,6 +129,8 @@ typedef struct {
 	 * that they are changed outside of NM (at least not while NM is running).
 	 * Hence, we read them once, that's it. */
 	GHashTable *device_states;
+
+	char **warnings;
 } NMConfigPrivate;
 
 struct _NMConfig {
@@ -281,6 +282,18 @@ nm_config_keyfile_set_string_list (GKeyFile *keyfile,
 
 /*****************************************************************************/
 
+const char *const*
+nm_config_get_warnings (NMConfig *config)
+{
+	return (const char *const *) NM_CONFIG_GET_PRIVATE (config)->warnings;
+}
+
+void
+nm_config_clear_warnings (NMConfig *config)
+{
+	g_clear_pointer (&NM_CONFIG_GET_PRIVATE (config)->warnings, g_strfreev);
+}
+
 NMConfigData *
 nm_config_get_data (NMConfig *config)
 {
@@ -704,26 +717,184 @@ static gboolean
 _setting_is_device_spec (const char *group, const char *key)
 {
 #define _IS(group_v, key_v) (strcmp (group, (""group_v)) == 0 && strcmp (key, (""key_v)) == 0)
-	return    _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "no-auto-default")
-	       || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "ignore-carrier")
-	       || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "assume-ipv6ll-only")
-	       || _IS (NM_CONFIG_KEYFILE_GROUP_KEYFILE, "unmanaged-devices")
-	       || (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION) && !strcmp (key, "match-device"))
-	       || (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE    ) && !strcmp (key, "match-device"));
+	return    _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT)
+	       || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER)
+	       || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY)
+	       || _IS (NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES)
+	       || (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION) && !strcmp (key, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE))
+	       || (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE    ) && !strcmp (key, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE));
 }
 
 static gboolean
 _setting_is_string_list (const char *group, const char *key)
 {
-	return    _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins")
+	return    _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS)
 	       || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG)
-	       || _IS (NM_CONFIG_KEYFILE_GROUP_LOGGING, "domains")
+	       || _IS (NM_CONFIG_KEYFILE_GROUP_LOGGING, NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS)
 	       || g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST);
 #undef _IS
 }
 
+typedef struct {
+	char *group;
+	const char *const *keys;
+	bool is_prefix:1;
+	bool is_connection:1;
+} ConfigGroup;
+
+/* The following comment is used by check-config-options.sh, don't remove it. */
+/* START OPTION LIST */
+
+static const ConfigGroup config_groups[] = {
+	{
+		.group = NM_CONFIG_KEYFILE_GROUP_MAIN,
+		.keys = NM_MAKE_STRV (
+			NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY,
+			NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT,
+			NM_CONFIG_KEYFILE_KEY_MAIN_AUTOCONNECT_RETRIES_DEFAULT,
+			NM_CONFIG_KEYFILE_KEY_MAIN_CONFIGURE_AND_QUIT,
+			NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG,
+			NM_CONFIG_KEYFILE_KEY_MAIN_DHCP,
+			NM_CONFIG_KEYFILE_KEY_MAIN_DNS,
+			NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE,
+			NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER,
+			NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES,
+			NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT,
+			NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS,
+			NM_CONFIG_KEYFILE_KEY_MAIN_RC_MANAGER,
+			NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER,
+			NM_CONFIG_KEYFILE_KEY_MAIN_SYSTEMD_RESOLVED,
+		),
+	},
+	{
+		.group = NM_CONFIG_KEYFILE_GROUP_LOGGING,
+		.keys = NM_MAKE_STRV (
+			NM_CONFIG_KEYFILE_KEY_LOGGING_AUDIT,
+			NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND,
+			NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS,
+			NM_CONFIG_KEYFILE_KEY_LOGGING_LEVEL,
+		),
+	},
+	{
+		.group = NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY,
+		.keys = NM_MAKE_STRV (
+			NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_ENABLED,
+			NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_INTERVAL,
+			NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_RESPONSE,
+			NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_URI,
+		),
+	},
+	{
+		.group = NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+		.keys = NM_MAKE_STRV (
+			NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME,
+			NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
+			NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES,
+		),
+	},
+	{
+		.group = NM_CONFIG_KEYFILE_GROUP_IFUPDOWN,
+		.keys = NM_MAKE_STRV (
+			NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED,
+		),
+	},
+	{
+		.group = NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE,
+		.is_prefix = TRUE,
+		.keys = NM_MAKE_STRV (
+			NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT,
+			NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER,
+			NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
+			NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
+			NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND,
+			NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS,
+			NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE,
+			NM_CONFIG_KEYFILE_KEY_STOP_MATCH,
+		),
+	},
+	{
+		.group = NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS,
+		.keys = NM_MAKE_STRV (
+			NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_OPTIONS,
+			NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_SEARCHES,
+		),
+	},
+	{
+		.group = NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN,
+		.is_prefix = TRUE,
+		.keys = NM_MAKE_STRV (
+			NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_SERVERS,
+			NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_OPTIONS,
+		),
+	},
+	{
+		.group = NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION,
+		.is_prefix = TRUE,
+		.is_connection = TRUE,
+		.keys = NM_MAKE_STRV (
+			NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE,
+			NM_CONFIG_KEYFILE_KEY_STOP_MATCH,
+		),
+	},
+	{ } /* sentinel */
+};
+
+/* The following comment is used by check-config-options.sh, don't remove it. */
+/* END OPTION LIST */
+
+static gboolean
+check_config_key (const char *group, const char *key)
+{
+	const ConfigGroup *g;
+	const char *const *k;
+	const char **ptr;
+
+#if NM_MORE_ASSERTS > 10
+	{
+		static gboolean checked = FALSE;
+		const char **ptr1, **ptr2;
+
+		/* check for duplicate elements in the static list */
+
+		if (!checked) {
+			for (ptr1 = __start_connection_defaults; ptr1 < __stop_connection_defaults; ptr1++) {
+				for (ptr2 = ptr1 + 1; ptr2 < __stop_connection_defaults; ptr2++)
+					nm_assert (!nm_streq (*ptr1, *ptr2));
+			}
+			checked = TRUE;
+		}
+	}
+#endif
+
+	for (g = config_groups; g->group; g++) {
+		if (   (!g->is_prefix && nm_streq (group, g->group))
+		    || (g->is_prefix && g_str_has_prefix (group, g->group)))
+			break;
+	}
+
+	if (!g->group)
+		return FALSE;
+
+	for (k = g->keys; *k; k++) {
+		if (nm_streq (key, *k))
+			return TRUE;
+	}
+
+	if (g->is_connection) {
+		for (ptr = __start_connection_defaults; ptr < __stop_connection_defaults; ptr++) {
+			if (nm_streq (key, *ptr))
+				return TRUE;
+		}
+		return FALSE;
+	}
+
+	return FALSE;
+}
+
 static gboolean
-read_config (GKeyFile *keyfile, gboolean is_base_config, const char *dirname, const char *path, GError **error)
+read_config (GKeyFile *keyfile, gboolean is_base_config,
+             const char *dirname, const char *path,
+             GPtrArray *warnings, GError **error)
 {
 	GKeyFile *kf;
 	char **groups, **keys;
@@ -847,7 +1018,7 @@ read_config (GKeyFile *keyfile, gboolean is_base_config, const char *dirname, co
 						new_val = _nm_utils_slist_to_strv (new_specs, FALSE);
 					}
 
-					/* merge the string lists, by omiting duplicates. */
+					/* merge the string lists, by omitting duplicates. */
 
 					for (iter_val = old_val; iter_val && *iter_val; iter_val++) {
 						if (   last_char != '-'
@@ -892,6 +1063,12 @@ read_config (GKeyFile *keyfile, gboolean is_base_config, const char *dirname, co
 
 			new_value = g_key_file_get_value (kf, group, key, NULL);
 			g_key_file_set_value (keyfile, group, key, new_value);
+
+			if (!check_config_key (group, key)) {
+				g_ptr_array_add (warnings,
+				                 g_strdup_printf ("unknown key '%s' in section [%s] of file '%s'",
+				                                  key, group, path));
+			}
 			g_free (new_value);
 		}
 		g_strfreev (keys);
@@ -906,6 +1083,7 @@ static gboolean
 read_base_config (GKeyFile *keyfile,
                   const char *cli_config_main_file,
                   char **out_config_main_file,
+                  GPtrArray *warnings,
                   GError **error)
 {
 	GError *my_error = NULL;
@@ -917,7 +1095,7 @@ read_base_config (GKeyFile *keyfile,
 	/* Try a user-specified config file first */
 	if (cli_config_main_file) {
 		/* Bad user-specific config file path is a hard error */
-		if (read_config (keyfile, TRUE, NULL, cli_config_main_file, error)) {
+		if (read_config (keyfile, TRUE, NULL, cli_config_main_file, warnings, error)) {
 			*out_config_main_file = g_strdup (cli_config_main_file);
 			return TRUE;
 		} else
@@ -932,7 +1110,7 @@ read_base_config (GKeyFile *keyfile,
 	 */
 
 	/* Try deprecated nm-system-settings.conf first */
-	if (read_config (keyfile, TRUE, NULL, DEFAULT_CONFIG_MAIN_FILE_OLD, &my_error)) {
+	if (read_config (keyfile, TRUE, NULL, DEFAULT_CONFIG_MAIN_FILE_OLD, warnings, &my_error)) {
 		*out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE_OLD);
 		return TRUE;
 	}
@@ -944,7 +1122,7 @@ read_base_config (GKeyFile *keyfile,
 	g_clear_error (&my_error);
 
 	/* Try the standard config file location next */
-	if (read_config (keyfile, TRUE, NULL, DEFAULT_CONFIG_MAIN_FILE, &my_error)) {
+	if (read_config (keyfile, TRUE, NULL, DEFAULT_CONFIG_MAIN_FILE, warnings, &my_error)) {
 		*out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE);
 		return TRUE;
 	}
@@ -1022,6 +1200,7 @@ read_entire_config (const NMConfigCmdLineOptions *cli,
                     const char *system_config_dir,
                     char **out_config_main_file,
                     char **out_config_description,
+                    char ***out_warnings,
                     GError **error)
 {
 	gs_unref_keyfile GKeyFile *keyfile = NULL;
@@ -1031,12 +1210,14 @@ read_entire_config (const NMConfigCmdLineOptions *cli,
 	guint i;
 	gs_free char *o_config_main_file = NULL;
 	const char *run_config_dir = "";
+	gs_unref_ptrarray GPtrArray *warnings = NULL;
 
 	g_return_val_if_fail (config_dir, NULL);
 	g_return_val_if_fail (system_config_dir, NULL);
 	g_return_val_if_fail (!out_config_main_file || !*out_config_main_file, FALSE);
 	g_return_val_if_fail (!out_config_description || !*out_config_description, NULL);
 	g_return_val_if_fail (!error || !*error, FALSE);
+	g_return_val_if_fail (out_warnings && !*out_warnings, FALSE);
 
 	if (   (""RUN_CONFIG_DIR)[0] == '/'
 	    && !nm_streq (RUN_CONFIG_DIR, system_config_dir)
@@ -1045,6 +1226,7 @@ read_entire_config (const NMConfigCmdLineOptions *cli,
 
 	/* create a default configuration file. */
 	keyfile = nm_config_create_keyfile ();
+	warnings = g_ptr_array_new_with_free_func (g_free);
 
 	system_confs = _get_config_dir_files (system_config_dir);
 	confs = _get_config_dir_files (config_dir);
@@ -1060,7 +1242,7 @@ read_entire_config (const NMConfigCmdLineOptions *cli,
 			continue;
 		}
 
-		if (!read_config (keyfile, FALSE, system_config_dir, filename, error))
+		if (!read_config (keyfile, FALSE, system_config_dir, filename, warnings, error))
 			return NULL;
 		i++;
 	}
@@ -1074,19 +1256,19 @@ read_entire_config (const NMConfigCmdLineOptions *cli,
 			continue;
 		}
 
-		if (!read_config (keyfile, FALSE, run_config_dir, filename, error))
+		if (!read_config (keyfile, FALSE, run_config_dir, filename, warnings, error))
 			return NULL;
 		i++;
 	}
 
 	/* First read the base config file */
-	if (!read_base_config (keyfile, cli ? cli->config_main_file : NULL, &o_config_main_file, error))
+	if (!read_base_config (keyfile, cli ? cli->config_main_file : NULL, &o_config_main_file, warnings, error))
 		return NULL;
 
 	g_assert (o_config_main_file);
 
 	for (i = 0; i < confs->len; i++) {
-		if (!read_config (keyfile, FALSE, config_dir, confs->pdata[i], error))
+		if (!read_config (keyfile, FALSE, config_dir, confs->pdata[i], warnings, error))
 			return NULL;
 	}
 
@@ -1133,6 +1315,11 @@ read_entire_config (const NMConfigCmdLineOptions *cli,
 		*out_config_description = g_string_free (str, FALSE);
 	}
 	NM_SET_OUT (out_config_main_file, g_steal_pointer (&o_config_main_file));
+
+	g_ptr_array_add (warnings, NULL);
+	*out_warnings = (char **) g_ptr_array_free (warnings, warnings->len == 1);
+	g_steal_pointer (&warnings);
+
 	return g_steal_pointer (&keyfile);
 }
 
@@ -1657,11 +1844,13 @@ nm_config_set_global_dns (NMConfig *self, NMGlobalDnsConfig *global_dns, GError
 
 	/* Set new values */
 	nm_config_keyfile_set_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS,
-	                                   "searches", nm_global_dns_config_get_searches (global_dns),
+	                                   NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_SEARCHES,
+	                                   nm_global_dns_config_get_searches (global_dns),
 	                                   -1);
 
 	nm_config_keyfile_set_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS,
-	                                   "options", nm_global_dns_config_get_options (global_dns),
+	                                   NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_OPTIONS,
+	                                   nm_global_dns_config_get_options (global_dns),
 	                                   -1);
 
 	for (i = 0; i < nm_global_dns_config_get_num_domains (global_dns); i++) {
@@ -1671,9 +1860,9 @@ nm_config_set_global_dns (NMConfig *self, NMGlobalDnsConfig *global_dns, GError
 		group_name = g_strdup_printf (NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN "%s",
 		                              nm_global_dns_domain_get_name (domain));
 
-		nm_config_keyfile_set_string_list (keyfile, group_name, "servers",
+		nm_config_keyfile_set_string_list (keyfile, group_name, NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_SERVERS,
 		                                   nm_global_dns_domain_get_servers (domain), -1);
-		nm_config_keyfile_set_string_list (keyfile, group_name, "options",
+		nm_config_keyfile_set_string_list (keyfile, group_name, NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_OPTIONS,
 		                                   nm_global_dns_domain_get_options (domain), -1);
 	}
 
@@ -1924,7 +2113,7 @@ state_write (NMConfig *self)
 	                          &error)) {
 		_LOGD ("state: error writing state file \"%s\": %s", filename, error->message);
 		g_clear_error (&error);
-		/* we leave the state dirty. That potentally means, that we try to
+		/* we leave the state dirty. That potentially means, that we try to
 		 * write the file over and over again, although it isn't possible. */
 		priv->state->p.dirty = TRUE;
 	} else
@@ -2354,7 +2543,7 @@ nm_config_device_state_get (NMConfig *self,
 /*****************************************************************************/
 
 void
-nm_config_reload (NMConfig *self, NMConfigChangeFlags reload_flags)
+nm_config_reload (NMConfig *self, NMConfigChangeFlags reload_flags, gboolean emit_warnings)
 {
 	NMConfigPrivate *priv;
 	GError *error = NULL;
@@ -2364,6 +2553,8 @@ nm_config_reload (NMConfig *self, NMConfigChangeFlags reload_flags)
 	char *config_description = NULL;
 	gs_strfreev char **no_auto_default = NULL;
 	gboolean intern_config_needs_rewrite;
+	gs_strfreev char **warnings = NULL;
+	guint i;
 
 	g_return_if_fail (NM_IS_CONFIG (self));
 	g_return_if_fail (   reload_flags
@@ -2388,6 +2579,7 @@ nm_config_reload (NMConfig *self, NMConfigChangeFlags reload_flags)
 	                              priv->system_config_dir,
 	                              &config_main_file,
 	                              &config_description,
+	                              &warnings,
 	                              &error);
 	if (!keyfile) {
 		_LOGE ("Failed to reload the configuration: %s", error->message);
@@ -2396,6 +2588,11 @@ nm_config_reload (NMConfig *self, NMConfigChangeFlags reload_flags)
 		return;
 	}
 
+	if (emit_warnings && warnings) {
+		for (i = 0; warnings[i]; i++)
+			_LOGW ("%s", warnings[i]);
+	}
+
 	no_auto_default = no_auto_default_from_file (priv->no_auto_default_file);
 
 	keyfile_intern = intern_config_read (priv->intern_config_file,
@@ -2557,10 +2754,12 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 {
 	NMConfig *self = NM_CONFIG (initable);
 	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
-	GKeyFile *keyfile, *keyfile_intern;
-	char *config_main_file = NULL;
-	char *config_description = NULL;
+	gs_unref_keyfile GKeyFile *keyfile = NULL;
+	gs_unref_keyfile GKeyFile *keyfile_intern = NULL;
+	gs_free char *config_main_file = NULL;
+	gs_free char *config_description = NULL;
 	gs_strfreev char **no_auto_default = NULL;
+	gs_strfreev char **warnings = NULL;
 	gs_free char *configure_and_quit = NULL;
 	gboolean intern_config_needs_rewrite;
 	const char *s;
@@ -2592,23 +2791,34 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 	                              priv->system_config_dir,
 	                              &config_main_file,
 	                              &config_description,
+	                              &warnings,
 	                              error);
 	if (!keyfile)
 		return FALSE;
 
-	/* Initialize read only private members */
+	/* Initialize read-only private members */
 
 	if (priv->cli.no_auto_default_file)
 		priv->no_auto_default_file = g_strdup (priv->cli.no_auto_default_file);
 	else
 		priv->no_auto_default_file = g_strdup (DEFAULT_NO_AUTO_DEFAULT_FILE);
 
-	priv->monitor_connection_files = nm_config_keyfile_get_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "monitor-connection-files", FALSE);
-
-	priv->log_level = nm_strstrip (g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_LOGGING, "level", NULL));
-	priv->log_domains = nm_strstrip (g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_LOGGING, "domains", NULL));
-
-	configure_and_quit = nm_strstrip (g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "configure-and-quit", NULL));
+	priv->monitor_connection_files = nm_config_keyfile_get_boolean (keyfile,
+	                                                                NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                                                                NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES,
+	                                                                FALSE);
+	priv->log_level = nm_strstrip (g_key_file_get_string (keyfile,
+	                                                      NM_CONFIG_KEYFILE_GROUP_LOGGING,
+	                                                      NM_CONFIG_KEYFILE_KEY_LOGGING_LEVEL,
+	                                                      NULL));
+	priv->log_domains = nm_strstrip (g_key_file_get_string (keyfile,
+	                                                        NM_CONFIG_KEYFILE_GROUP_LOGGING,
+	                                                        NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS,
+	                                                        NULL));
+	configure_and_quit = nm_strstrip (g_key_file_get_string (keyfile,
+	                                                         NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                                                         NM_CONFIG_KEYFILE_KEY_MAIN_CONFIGURE_AND_QUIT,
+	                                                         NULL));
 	priv->configure_and_quit = string_to_configure_and_quit (configure_and_quit, error);
 	if (priv->configure_and_quit == NM_CONFIG_CONFIGURE_AND_QUIT_INVALID)
 		return FALSE;
@@ -2632,12 +2842,7 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 	                                             keyfile_intern);
 
 	priv->config_data = g_object_ref (priv->config_data_orig);
-
-	g_free (config_main_file);
-	g_free (config_description);
-	g_key_file_unref (keyfile);
-	if (keyfile_intern)
-		g_key_file_unref (keyfile_intern);
+	priv->warnings = g_steal_pointer (&warnings);
 	return TRUE;
 }
 
@@ -2673,6 +2878,7 @@ finalize (GObject *gobject)
 	g_free (priv->log_level);
 	g_free (priv->log_domains);
 	g_strfreev (priv->atomic_section_prefixes);
+	g_strfreev (priv->warnings);
 
 	_nm_config_cmd_line_options_clear (&priv->cli);
 
diff --git a/src/nm-config.h b/src/nm-config.h
index c65572ce..66f1b69c 100644
--- a/src/nm-config.h
+++ b/src/nm-config.h
@@ -52,28 +52,48 @@
 #define NM_CONFIG_KEYFILE_GROUP_MAIN                        "main"
 #define NM_CONFIG_KEYFILE_GROUP_LOGGING                     "logging"
 #define NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY                "connectivity"
-#define NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS                  "global-dns"
-#define NM_CONFIG_KEYFILE_GROUP_CONFIG                      ".config"
-
 #define NM_CONFIG_KEYFILE_GROUP_KEYFILE                     "keyfile"
 #define NM_CONFIG_KEYFILE_GROUP_IFUPDOWN                    "ifupdown"
+#define NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS                  "global-dns"
+#define NM_CONFIG_KEYFILE_GROUP_CONFIG                      ".config"
 
+#define NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY       "assume-ipv6ll-only"
 #define NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT              "auth-polkit"
 #define NM_CONFIG_KEYFILE_KEY_MAIN_AUTOCONNECT_RETRIES_DEFAULT "autoconnect-retries-default"
-#define NM_CONFIG_KEYFILE_KEY_MAIN_DHCP                     "dhcp"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_CONFIGURE_AND_QUIT       "configure-and-quit"
 #define NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG                    "debug"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_DHCP                     "dhcp"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_DNS                      "dns"
 #define NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE            "hostname-mode"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER           "ignore-carrier"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES "monitor-connection-files"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT          "no-auto-default"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS                  "plugins"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_RC_MANAGER               "rc-manager"
 #define NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER             "slaves-order"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_SYSTEMD_RESOLVED         "systemd-resolved"
+
+#define NM_CONFIG_KEYFILE_KEY_LOGGING_AUDIT                 "audit"
 #define NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND               "backend"
-#define NM_CONFIG_KEYFILE_KEY_CONFIG_ENABLE                 "enable"
-#define NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS            ".was"
+#define NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS               "domains"
+#define NM_CONFIG_KEYFILE_KEY_LOGGING_LEVEL                 "level"
+
+#define NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_ENABLED          "enabled"
+#define NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_INTERVAL         "interval"
+#define NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_RESPONSE         "response"
+#define NM_CONFIG_KEYFILE_KEY_CONNECTIVITY_URI              "uri"
+
 #define NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH                  "path"
 #define NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES     "unmanaged-devices"
 #define NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME              "hostname"
-#define NM_CONFIG_KEYFILE_KEY_IFNET_AUTO_REFRESH            "auto_refresh"
-#define NM_CONFIG_KEYFILE_KEY_IFNET_MANAGED                 "managed"
+
 #define NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED              "managed"
-#define NM_CONFIG_KEYFILE_KEY_AUDIT                         "audit"
+
+#define NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_SEARCHES           "searches"
+#define NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_OPTIONS            "options"
+
+#define NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_SERVERS     "servers"
+#define NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_DOMAIN_OPTIONS     "options"
 
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED                "managed"
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER         "ignore-carrier"
@@ -82,6 +102,12 @@
 #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_KEY_MATCH_DEVICE           "match-device"
+#define NM_CONFIG_KEYFILE_KEY_STOP_MATCH             "stop-match"
+
+#define NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS            ".was"   /* check-config-options skip */
+#define NM_CONFIG_KEYFILE_KEY_CONFIG_ENABLE                 "enable" /* check-config-options skip */
+
 #define NM_CONFIG_KEYFILE_KEYPREFIX_WAS                     ".was."
 #define NM_CONFIG_KEYFILE_KEYPREFIX_SET                     ".set."
 
@@ -155,7 +181,7 @@ void nm_config_set_no_auto_default_for_device  (NMConfig *config, NMDevice *devi
 
 NMConfig *nm_config_new (const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes, GError **error);
 NMConfig *nm_config_setup (const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes, GError **error);
-void nm_config_reload (NMConfig *config, NMConfigChangeFlags reload_flags);
+void nm_config_reload (NMConfig *config, NMConfigChangeFlags reload_flags, gboolean emit_warnings);
 
 const NMConfigState *nm_config_state_get (NMConfig *config);
 
@@ -252,6 +278,9 @@ const GHashTable *nm_config_device_state_get_all (NMConfig *self);
 const NMConfigDeviceStateData *nm_config_device_state_get (NMConfig *self,
                                                            int ifindex);
 
+const char *const *nm_config_get_warnings (NMConfig *config);
+void nm_config_clear_warnings (NMConfig *config);
+
 /*****************************************************************************/
 
 #endif /* __NETWORKMANAGER_CONFIG_H__ */
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index 8a6e955a..b72413d2 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -17,22 +17,23 @@
  *
  * Copyright (C) 2011 Thomas Bechtold <thomasbechtold@jpberlin.de>
  * Copyright (C) 2011 Dan Williams <dcbw@redhat.com>
- * Copyright (C) 2016,2017 Red Hat, Inc.
+ * Copyright (C) 2016 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
 
 #include "nm-connectivity.h"
 
-#include <string.h>
-
 #if WITH_CONCHECK
 #include <curl/curl.h>
 #endif
 
 #include "c-list/src/c-list.h"
+#include "nm-core-internal.h"
 #include "nm-config.h"
 #include "NetworkManagerUtils.h"
+#include "nm-dbus-manager.h"
+#include "dns/nm-dns-manager.h"
 
 #define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n"
 
@@ -60,6 +61,14 @@ nm_connectivity_state_to_string (NMConnectivityState state)
 
 /*****************************************************************************/
 
+typedef struct {
+	guint ref_count;
+	char *uri;
+	char *host;
+	char *port;
+	char *response;
+} ConConfig;
+
 struct _NMConnectivityCheckHandle {
 	CList handles_lst;
 	NMConnectivity *self;
@@ -68,22 +77,35 @@ struct _NMConnectivityCheckHandle {
 
 	char *ifspec;
 
+	const char *completed_log_message;
+	char *completed_log_message_free;
+
 #if WITH_CONCHECK
 	struct {
-		char *response;
+		ConConfig *con_config;
 
+		GCancellable *resolve_cancellable;
+		CURLM *curl_mhandle;
 		CURL *curl_ehandle;
 		struct curl_slist *request_headers;
+		struct curl_slist *hosts;
+
+		gsize response_good_cnt;
 
-		GString *recv_msg;
+		guint curl_timer;
+		int ch_ifindex;
 	} concheck;
 #endif
 
-	const char *completed_log_message;
-	char *completed_log_message_free;
-	NMConnectivityState completed_state;
+	guint64 request_counter;
+
+	int addr_family;
 
 	guint timeout_id;
+
+	NMConnectivityState completed_state;
+
+	bool fail_reason_no_dbus_connection:1;
 };
 
 enum {
@@ -97,17 +119,12 @@ static guint signals[LAST_SIGNAL] = { 0 };
 typedef struct {
 	CList handles_lst_head;
 	CList completed_handles_lst_head;
-	char *uri;
-	char *response;
-	gboolean enabled;
-	guint interval;
 	NMConfig *config;
-#if WITH_CONCHECK
-	struct {
-		CURLM *curl_mhandle;
-		guint curl_timer;
-	} concheck;
-#endif
+	ConConfig *con_config;
+	guint interval;
+
+	bool enabled:1;
+	bool uri_valid:1;
 } NMConnectivityPrivate;
 
 struct _NMConnectivity {
@@ -139,15 +156,53 @@ NM_DEFINE_SINGLETON_GETTER (NMConnectivity, nm_connectivity_get, NM_TYPE_CONNECT
             _nm_log (__level, _NMLOG2_DOMAIN, 0, \
                      (cb_data->ifspec ? &cb_data->ifspec[3] : NULL), \
                      NULL, \
-                     "connectivity: (%s) " \
+                     "connectivity: (%s,IPv%c,%"G_GUINT64_FORMAT") " \
                      _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                     (cb_data->ifspec ? &cb_data->ifspec[3] : "") \
+                     (cb_data->ifspec ? &cb_data->ifspec[3] : ""), \
+                     nm_utils_addr_family_to_char (cb_data->addr_family), \
+                     cb_data->request_counter \
                      _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
         } \
     } G_STMT_END
 
 /*****************************************************************************/
 
+static ConConfig *
+_con_config_ref (ConConfig *con_config)
+{
+	if (con_config) {
+		nm_assert (con_config->ref_count > 0);
+		++con_config->ref_count;
+	}
+	return con_config;
+}
+
+static void
+_con_config_unref (ConConfig *con_config)
+{
+	if (!con_config)
+		return;
+
+	nm_assert (con_config->ref_count > 0);
+
+	if (--con_config->ref_count != 0)
+		return;
+
+	g_free (con_config->uri);
+	g_free (con_config->host);
+	g_free (con_config->port);
+	g_free (con_config->response);
+	g_slice_free (ConConfig, con_config);
+}
+
+static const char *
+_con_config_get_response (const ConConfig *con_config)
+{
+	return con_config->response ?: NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE;
+}
+
+/*****************************************************************************/
+
 static void
 cb_data_complete (NMConnectivityCheckHandle *cb_data,
                   NMConnectivityState state,
@@ -171,8 +226,6 @@ cb_data_complete (NMConnectivityCheckHandle *cb_data,
 
 #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 it's not safe to
 		 * remove *any* handle from within a libcurl callback. That is
@@ -187,13 +240,16 @@ cb_data_complete (NMConnectivityCheckHandle *cb_data,
 		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_multi_remove_handle (cb_data->concheck.curl_mhandle,
+		                          cb_data->concheck.curl_ehandle);
 		curl_easy_cleanup (cb_data->concheck.curl_ehandle);
+		curl_multi_cleanup (cb_data->concheck.curl_mhandle);
 
 		curl_slist_free_all (cb_data->concheck.request_headers);
+		curl_slist_free_all (cb_data->concheck.hosts);
 	}
+	nm_clear_g_source (&cb_data->concheck.curl_timer);
+	nm_clear_g_cancellable (&cb_data->concheck.resolve_cancellable);
 #endif
 
 	nm_clear_g_source (&cb_data->timeout_id);
@@ -212,9 +268,7 @@ cb_data_complete (NMConnectivityCheckHandle *cb_data,
 	 * not use the self pointer too. */
 
 #if WITH_CONCHECK
-	g_free (cb_data->concheck.response);
-	if (cb_data->concheck.recv_msg)
-		g_string_free (cb_data->concheck.recv_msg, TRUE);
+	_con_config_unref (cb_data->concheck.con_config);
 #endif
 	g_free (cb_data->ifspec);
 	if (cb_data->completed_log_message_free)
@@ -265,12 +319,6 @@ _complete_queued (NMConnectivity *self)
 	nm_g_object_unref (self_keep_alive);
 }
 
-static const char *
-_check_handle_get_response (NMConnectivityCheckHandle *cb_data)
-{
-	return cb_data->concheck.response ?: NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE;
-}
-
 static gboolean
 _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 {
@@ -285,11 +333,12 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 
 	ret = curl_multi_socket_action (mhandle, sockfd, ev_bitmask, &running_handles);
 	if (ret != CURLM_OK) {
-		_LOGD ("connectivity check failed: %d", ret);
+		_LOGD ("connectivity check failed: (%d) %s", ret, curl_easy_strerror (ret));
 		success = FALSE;
 	}
 
 	while ((msg = curl_multi_info_read (mhandle, &m_left))) {
+		const char *response;
 
 		if (msg->msg != CURLMSG_DONE)
 			continue;
@@ -297,7 +346,8 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 		/* 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) {
-			_LOGD ("curl cannot extract cb_data for easy handle, skipping msg");
+			_LOGD ("curl cannot extract cb_data for easy handle, skipping msg: (%d) %s",
+			       eret, curl_easy_strerror (eret));
 			success = FALSE;
 			continue;
 		}
@@ -314,26 +364,47 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 			cb_data_queue_completed (cb_data,
 			                         NM_CONNECTIVITY_LIMITED,
 			                         NULL,
-			                         g_strdup_printf ("check failed with curl status %d", msg->data.result));
-		} 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_queue_completed (cb_data,
-			                         NM_CONNECTIVITY_FULL,
-			                         "no content, as expected",
-			                         NULL);
-		} 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_queue_completed (cb_data,
-			                         NM_CONNECTIVITY_PORTAL,
-			                         "unexpected short response",
-			                         NULL);
+			                         g_strdup_printf ("check failed: (%d) %s",
+			                                          msg->data.result,
+			                                          curl_easy_strerror (msg->data.result)));
+			continue;
+		}
+
+		response = _con_config_get_response (cb_data->concheck.con_config);
+
+		if (   response[0] == '\0'
+		    && (curl_easy_getinfo (msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code) == CURLE_OK)) {
+
+			if (response_code == 204) {
+				/* We expected an empty response, and we got a 204 response code (no content).
+				 * We may or may not have received any content (we would ignore it).
+				 * Anyway, the response_code 204 means we are good. */
+				cb_data_queue_completed (cb_data,
+				                         NM_CONNECTIVITY_FULL,
+				                         "no content, as expected",
+				                         NULL);
+				continue;
+			}
+
+			if (   response_code == 200
+			    && cb_data->concheck.response_good_cnt == 0) {
+				/* we expected no response, and indeed we got an empty reply (with status code 200) */
+				cb_data_queue_completed (cb_data,
+				                         NM_CONNECTIVITY_FULL,
+				                         "empty response, as expected",
+				                         NULL);
+				continue;
+			}
 		}
+
+		/* 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_queue_completed (cb_data,
+		                         NM_CONNECTIVITY_PORTAL,
+		                         "unexpected short response",
+		                         NULL);
 	}
 
 	/* if we return a failure, we don't know what went wrong. It's likely serious, because
@@ -346,29 +417,27 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 static gboolean
 _con_curl_timeout_cb (gpointer user_data)
 {
-	gs_unref_object NMConnectivity *self = g_object_ref (NM_CONNECTIVITY (user_data));
-	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	NMConnectivityCheckHandle *cb_data = user_data;
 
-	priv->concheck.curl_timer = 0;
-	_con_curl_check_connectivity (priv->concheck.curl_mhandle, CURL_SOCKET_TIMEOUT, 0);
-	_complete_queued (self);
+	cb_data->concheck.curl_timer = 0;
+	_con_curl_check_connectivity (cb_data->concheck.curl_mhandle, CURL_SOCKET_TIMEOUT, 0);
+	_complete_queued (cb_data->self);
 	return G_SOURCE_REMOVE;
 }
 
 static int
 multi_timer_cb (CURLM *multi, long timeout_ms, void *userdata)
 {
-	NMConnectivity *self = NM_CONNECTIVITY (userdata);
-	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	NMConnectivityCheckHandle *cb_data = userdata;
 
-	nm_clear_g_source (&priv->concheck.curl_timer);
+	nm_clear_g_source (&cb_data->concheck.curl_timer);
 	if (timeout_ms != -1)
-		priv->concheck.curl_timer = g_timeout_add (timeout_ms, _con_curl_timeout_cb, self);
+		cb_data->concheck.curl_timer = g_timeout_add (timeout_ms, _con_curl_timeout_cb, cb_data);
 	return 0;
 }
 
 typedef struct {
-	NMConnectivity *self;
+	NMConnectivityCheckHandle *cb_data;
 	GIOChannel *ch;
 
 	/* this is a very simplistic weak-pointer. If ConCurlSockData gets
@@ -385,8 +454,7 @@ static gboolean
 _con_curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_data)
 {
 	ConCurlSockData *fdp = user_data;
-	gs_unref_object NMConnectivity *self = g_object_ref (fdp->self);
-	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	NMConnectivityCheckHandle *cb_data = fdp->cb_data;
 	int fd = g_io_channel_unix_get_fd (ch);
 	int action = 0;
 	gboolean fdp_destroyed = FALSE;
@@ -402,7 +470,7 @@ _con_curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_
 	nm_assert (!fdp->destroy_notify);
 	fdp->destroy_notify = &fdp_destroyed;
 
-	success = _con_curl_check_connectivity (priv->concheck.curl_mhandle, fd, action);
+	success = _con_curl_check_connectivity (cb_data->concheck.curl_mhandle, fd, action);
 
 	if (fdp_destroyed) {
 		/* hups. fdp got invalidated during _con_curl_check_connectivity(). That's fine,
@@ -414,7 +482,7 @@ _con_curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_
 			fdp->ev = 0;
 	}
 
-	_complete_queued (self);
+	_complete_queued (cb_data->self);
 
 	return success ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
 }
@@ -422,8 +490,7 @@ _con_curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_
 static int
 multi_socket_cb (CURL *e_handle, curl_socket_t fd, int what, void *userdata, void *socketp)
 {
-	NMConnectivity *self = NM_CONNECTIVITY (userdata);
-	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	NMConnectivityCheckHandle *cb_data = userdata;
 	ConCurlSockData *fdp = socketp;
 	GIOCondition condition = 0;
 
@@ -433,7 +500,7 @@ multi_socket_cb (CURL *e_handle, curl_socket_t fd, int what, void *userdata, voi
 		if (fdp) {
 			if (fdp->destroy_notify)
 				*fdp->destroy_notify = TRUE;
-			curl_multi_assign (priv->concheck.curl_mhandle, fd, NULL);
+			curl_multi_assign (cb_data->concheck.curl_mhandle, fd, NULL);
 			nm_clear_g_source (&fdp->ev);
 			g_io_channel_unref (fdp->ch);
 			g_slice_free (ConCurlSockData, fdp);
@@ -441,9 +508,9 @@ multi_socket_cb (CURL *e_handle, curl_socket_t fd, int what, void *userdata, voi
 	} else {
 		if (!fdp) {
 			fdp = g_slice_new0 (ConCurlSockData);
-			fdp->self = self;
+			fdp->cb_data = cb_data;
 			fdp->ch = g_io_channel_unix_new (fd);
-			curl_multi_assign (priv->concheck.curl_mhandle, fd, fdp);
+			curl_multi_assign (cb_data->concheck.curl_mhandle, fd, fdp);
 		} else
 			nm_clear_g_source (&fdp->ev);
 
@@ -489,6 +556,8 @@ easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
 {
 	NMConnectivityCheckHandle *cb_data = userdata;
 	size_t len = size * nmemb;
+	size_t response_len;
+	size_t check_len;
 	const char *response;
 
 	if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) {
@@ -496,26 +565,68 @@ easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
 		return 0;
 	}
 
-	if (!cb_data->concheck.recv_msg)
-		cb_data->concheck.recv_msg = g_string_sized_new (len + 10);
+	if (len == 0) {
+		/* no data. That can happen, it's fine. */
+		return len;
+	}
 
-	g_string_append_len (cb_data->concheck.recv_msg, buffer, len);
+	response = _con_config_get_response (cb_data->concheck.con_config);;
 
-	response = _check_handle_get_response (cb_data);;
-	if (   response
-	    && cb_data->concheck.recv_msg->len >= strlen (response)) {
-		/* We already have enough data -- check response */
-		if (g_str_has_prefix (cb_data->concheck.recv_msg->str, response)) {
-			cb_data_queue_completed (cb_data,
-			                         NM_CONNECTIVITY_FULL,
-			                         "expected response",
-			                         NULL);
-		} else {
+	if (response[0] == '\0') {
+		/* no response expected. We are however graceful and accept any
+		 * extra response that we might receive. We determine the empty
+		 * response based on the status code 204.
+		 *
+		 * Continue receiving... */
+		cb_data->concheck.response_good_cnt += len;
+
+		if (cb_data->concheck.response_good_cnt > (gsize) (100 * 1024)) {
+			/* we expect an empty response. We accept either
+			 * 1) status code 204 and any response
+			 * 2) status code 200 and an empty response.
+			 *
+			 * Here, we want to continue receiving data, to see whether we have
+			 * case 1). Arguably, the server shouldn't send us 204 with a non-empty
+			 * response, but we accept that also with a non-empty response, so
+			 * keep receiving.
+			 *
+			 * However, if we get an excessive amount of data, we put a stop on it
+			 * and fail. */
 			cb_data_queue_completed (cb_data,
 			                         NM_CONNECTIVITY_PORTAL,
-			                         "unexpected response",
+			                         "unexpected non-empty response",
 			                         NULL);
+			return 0;
 		}
+
+		return len;
+	}
+
+	nm_assert (cb_data->concheck.response_good_cnt < strlen (response));
+
+	response_len = strlen (response);
+
+	check_len = NM_MIN (len,
+	                    response_len - cb_data->concheck.response_good_cnt);
+
+	if (strncmp (&response[cb_data->concheck.response_good_cnt],
+	             buffer,
+	             check_len) != 0) {
+		cb_data_queue_completed (cb_data,
+		                         NM_CONNECTIVITY_PORTAL,
+		                         "unexpected response",
+		                         NULL);
+		return 0;
+	}
+
+	cb_data->concheck.response_good_cnt += len;
+
+	if (cb_data->concheck.response_good_cnt >= response_len) {
+		/* We already have enough data, and it matched. */
+		cb_data_queue_completed (cb_data,
+		                         NM_CONNECTIVITY_FULL,
+		                         "expected response",
+		                         NULL);
 		return 0;
 	}
 
@@ -551,66 +662,235 @@ _idle_cb (gpointer user_data)
 		g_set_error (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT,
 		             "no interface specified for connectivity check");
 		cb_data_complete (cb_data, NM_CONNECTIVITY_ERROR, "missing interface");
+	} else if (cb_data->fail_reason_no_dbus_connection) {
+		gs_free_error GError *error = NULL;
+
+		g_set_error (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT,
+		             "no D-Bus connection");
+		cb_data_complete (cb_data, NM_CONNECTIVITY_ERROR, "no D-Bus connection");
 	} else
 		cb_data_complete (cb_data, NM_CONNECTIVITY_FAKE, "fake result");
 	return G_SOURCE_REMOVE;
 }
 
+static void
+do_curl_request (NMConnectivityCheckHandle *cb_data)
+{
+	CURLM *mhandle;
+	CURL *ehandle;
+	long resolve;
+
+	mhandle = curl_multi_init ();
+	if (!mhandle) {
+		cb_data_complete (cb_data, NM_CONNECTIVITY_ERROR, "curl error");
+		return;
+	}
+
+	ehandle = curl_easy_init ();
+	if (!ehandle) {
+		curl_multi_cleanup (mhandle);
+		cb_data_complete (cb_data, NM_CONNECTIVITY_ERROR, "curl error");
+		return;
+	}
+
+	cb_data->concheck.curl_mhandle = mhandle;
+	cb_data->concheck.curl_ehandle = ehandle;
+	cb_data->concheck.request_headers = curl_slist_append (NULL, "Connection: close");
+	cb_data->timeout_id = g_timeout_add_seconds (20, _timeout_cb, cb_data);
+
+	curl_multi_setopt (mhandle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
+	curl_multi_setopt (mhandle, CURLMOPT_SOCKETDATA, cb_data);
+	curl_multi_setopt (mhandle, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
+	curl_multi_setopt (mhandle, CURLMOPT_TIMERDATA, cb_data);
+	curl_multi_setopt (mhandle, CURLOPT_VERBOSE, 1);
+
+	switch (cb_data->addr_family) {
+	case AF_INET:
+		resolve = CURL_IPRESOLVE_V4;
+		break;
+	case AF_INET6:
+		resolve = CURL_IPRESOLVE_V6;
+		break;
+	case AF_UNSPEC:
+		resolve = CURL_IPRESOLVE_WHATEVER;
+		break;
+	default:
+		resolve = CURL_IPRESOLVE_WHATEVER;
+		g_warn_if_reached ();
+	}
+
+	curl_easy_setopt (ehandle, CURLOPT_URL, cb_data->concheck.con_config->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_easy_setopt (ehandle, CURLOPT_RESOLVE, cb_data->concheck.hosts);
+	curl_easy_setopt (ehandle, CURLOPT_IPRESOLVE, resolve);
+
+	curl_multi_add_handle (mhandle, ehandle);
+}
+
+static void
+resolve_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+	NMConnectivityCheckHandle *cb_data;
+	gs_unref_variant GVariant *result = NULL;
+	gs_unref_variant GVariant *addresses = NULL;
+	gsize no_addresses;
+	int ifindex;
+	int addr_family;
+	gsize len = 0;
+	gsize i;
+	gs_free_error GError *error = NULL;
+
+	result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), res, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	cb_data = user_data;
+
+	g_clear_object (&cb_data->concheck.resolve_cancellable);
+
+	if (!result) {
+		/* Never mind. Just let do curl do its own resolving. */
+		_LOG2D ("can't resolve a name via systemd-resolved: %s", error->message);
+		do_curl_request (cb_data);
+		return;
+	}
+
+	addresses = g_variant_get_child_value (result, 0);
+	no_addresses = g_variant_n_children (addresses);
+
+	for (i = 0; i < no_addresses; i++) {
+		gs_unref_variant GVariant *address = NULL;
+		char str_addr[NM_UTILS_INET_ADDRSTRLEN];
+		gs_free char *host_entry = NULL;
+		const guchar *address_buf;
+
+		g_variant_get_child (addresses, i, "(ii@ay)", &ifindex, &addr_family, &address);
+
+		if (   cb_data->addr_family != AF_UNSPEC
+		    && cb_data->addr_family != addr_family)
+			continue;
+
+		address_buf = g_variant_get_fixed_array (address, &len, 1);
+		if (   (addr_family == AF_INET  && len != sizeof (struct in_addr))
+		    || (addr_family == AF_INET6 && len != sizeof (struct in6_addr)))
+			continue;
+
+		host_entry = g_strdup_printf ("%s:%s:%s",
+		                              cb_data->concheck.con_config->host,
+		                              cb_data->concheck.con_config->port ?: "80",
+		                              nm_utils_inet_ntop (addr_family, address_buf, str_addr));
+		cb_data->concheck.hosts = curl_slist_append (cb_data->concheck.hosts, host_entry);
+		_LOG2T ("adding '%s' to curl resolve list", host_entry);
+	}
+
+	do_curl_request (cb_data);
+}
+
+#define SD_RESOLVED_DNS ((guint64) (1LL << 0))
+
 NMConnectivityCheckHandle *
 nm_connectivity_check_start (NMConnectivity *self,
+                             int addr_family,
+                             int ifindex,
                              const char *iface,
                              NMConnectivityCheckCallback callback,
                              gpointer user_data)
 {
 	NMConnectivityPrivate *priv;
 	NMConnectivityCheckHandle *cb_data;
+	static guint64 request_counter = 0;
 
 	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;
+	cb_data->request_counter = ++request_counter;
 	c_list_link_tail (&priv->handles_lst_head, &cb_data->handles_lst);
 	cb_data->callback = callback;
 	cb_data->user_data = user_data;
 	cb_data->completed_state = NM_CONNECTIVITY_UNKNOWN;
+	cb_data->addr_family = addr_family;
+	cb_data->concheck.con_config = _con_config_ref (priv->con_config);
 
 	if (iface)
 		cb_data->ifspec = g_strdup_printf ("if!%s", 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;
+
+	if (   iface
+	    && ifindex > 0
+	    && priv->enabled
+	    && priv->uri_valid) {
+		gboolean has_systemd_resolved;
+
+		cb_data->concheck.ch_ifindex = ifindex;
+
+		/* note that we pick up support for systemd-resolved right away when we need it.
+		 * We don't need to remember the setting, because we can (cheaply) check anew
+		 * on each request.
+		 *
+		 * Yes, this makes NMConnectivity singleton dependent on NMDnsManager singleton.
+		 * Well, not really: it makes connectivity-check-start dependent on NMDnsManager
+		 * which merely means, not to start a connectivity check, late during shutdown. */
+		has_systemd_resolved = nm_dns_manager_has_systemd_resolved (nm_dns_manager_get ());
+
+		if (has_systemd_resolved) {
+			GDBusConnection *dbus_connection;
+
+			dbus_connection = nm_dbus_manager_get_dbus_connection (nm_dbus_manager_get ());
+			if (!dbus_connection) {
+				/* we have no D-Bus connection? That might happen in configure and quit mode.
+				 *
+				 * Anyway, something is very odd, just fail connectivity check. */
+				_LOG2D ("start fake request (fail due to no D-Bus connection)");
+				cb_data->fail_reason_no_dbus_connection = TRUE;
+				cb_data->timeout_id = g_idle_add (_idle_cb, cb_data);
+				return cb_data;
+			}
+
+			cb_data->concheck.resolve_cancellable = g_cancellable_new ();
+
+			g_dbus_connection_call (nm_dbus_manager_get_dbus_connection (nm_dbus_manager_get ()),
+			                        "org.freedesktop.resolve1",
+			                        "/org/freedesktop/resolve1",
+			                        "org.freedesktop.resolve1.Manager",
+			                        "ResolveHostname",
+			                        g_variant_new ("(isit)",
+			                                       (gint32) cb_data->concheck.ch_ifindex,
+			                                       cb_data->concheck.con_config->host,
+			                                       (gint32) cb_data->addr_family,
+			                                       SD_RESOLVED_DNS),
+			                        G_VARIANT_TYPE ("(a(iiay)st)"),
+			                        G_DBUS_CALL_FLAGS_NONE,
+			                        -1,
+			                        cb_data->concheck.resolve_cancellable,
+			                        resolve_cb,
+			                        cb_data);
+			_LOG2D ("start request to '%s' (try resolving '%s' using systemd-resolved)",
+			        cb_data->concheck.con_config->uri,
+			        cb_data->concheck.con_config->host);
+		} else {
+			_LOG2D ("start request to '%s' (systemd-resolved not available)",
+			        cb_data->concheck.con_config->uri);
+			do_curl_request (cb_data);
 		}
+
+		return cb_data;
 	}
 #endif
 
 	_LOG2D ("start fake request");
 	cb_data->timeout_id = g_idle_add (_idle_cb, cb_data);
+
 	return cb_data;
 }
 
@@ -646,42 +926,126 @@ nm_connectivity_get_interval (NMConnectivity *self)
 	       : 0;
 }
 
+static gboolean
+host_and_port_from_uri (const char *uri, char **host, char **port)
+{
+	const char *p = uri;
+	const char *host_begin = NULL;
+	size_t host_len = 0;
+	const char *port_begin = NULL;
+	size_t port_len = 0;
+
+	/* scheme */
+	while (*p != ':' && *p != '/') {
+		if (!*p++)
+			return FALSE;
+	}
+
+	/* :// */
+	if (*p++ != ':')
+		return FALSE;
+	if (*p++ != '/')
+		return FALSE;
+	if (*p++ != '/')
+		return FALSE;
+	/* host */
+	if (*p == '[')
+		return FALSE;
+	host_begin = p;
+	while (*p && *p != ':' && *p != '/') {
+		host_len++;
+		p++;
+	}
+	if (host_len == 0)
+		return FALSE;
+	*host = g_strndup (host_begin, host_len);
+
+	/* port */
+	if (*p++ == ':') {
+		port_begin = p;
+		while (*p && *p != '/') {
+			port_len++;
+			p++;
+		}
+		if (port_len)
+			*port = g_strndup (port_begin, port_len);
+	}
+
+	return TRUE;
+}
+
 static void
 update_config (NMConnectivity *self, NMConfigData *config_data)
 {
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-	const char *uri, *response;
 	guint interval;
 	gboolean enabled;
 	gboolean changed = FALSE;
-
-	/* Set the URI. */
-	uri = nm_config_data_get_connectivity_uri (config_data);
-	if (uri && !*uri)
-		uri = NULL;
-	changed = g_strcmp0 (uri, priv->uri) != 0;
-	if (uri) {
-		char *scheme = g_uri_parse_scheme (uri);
-
-		if (!scheme) {
-			_LOGE ("invalid URI '%s' for connectivity check.", uri);
-			uri = NULL;
-		} else if (strcasecmp (scheme, "https") == 0) {
-			_LOGW ("use of HTTPS for connectivity checking is not reliable and is discouraged (URI: %s)", uri);
-		} else if (strcasecmp (scheme, "http") != 0) {
-			_LOGE ("scheme of '%s' uri doesn't use a scheme that is allowed for connectivity check.", uri);
-			uri = NULL;
+	const char *cur_uri = priv->con_config ? priv->con_config->uri : NULL;
+	const char *cur_response = priv->con_config ? priv->con_config->response : NULL;
+	const char *new_response;
+	const char *new_uri;
+	gboolean new_uri_valid = priv->uri_valid;
+	gboolean new_host_port = FALSE;
+	gs_free char *new_host = NULL;
+	gs_free char *new_port = NULL;
+
+	new_uri = nm_config_data_get_connectivity_uri (config_data);
+	if (!nm_streq0 (new_uri, cur_uri)) {
+
+		new_uri_valid = (new_uri && *new_uri);
+		if (new_uri_valid) {
+			gs_free char *scheme = g_uri_parse_scheme (new_uri);
+			gboolean is_https = FALSE;
+
+			if (!scheme) {
+				_LOGE ("invalid URI '%s' for connectivity check.", new_uri);
+				new_uri_valid = FALSE;
+			} else if (g_ascii_strcasecmp (scheme, "https") == 0) {
+				_LOGW ("use of HTTPS for connectivity checking is not reliable and is discouraged (URI: %s)", new_uri);
+				is_https = TRUE;
+			} else if (g_ascii_strcasecmp (scheme, "http") != 0) {
+				_LOGE ("scheme of '%s' uri doesn't use a scheme that is allowed for connectivity check.", new_uri);
+				new_uri_valid = FALSE;
+			}
+			if (new_uri_valid) {
+				new_host_port = TRUE;
+				if (!host_and_port_from_uri (new_uri, &new_host, &new_port)) {
+					_LOGE ("cannot parse host and port from '%s'", new_uri);
+					new_uri_valid = FALSE;
+				} else if (!new_port && is_https)
+					new_port = g_strdup ("443");
+			}
 		}
 
-		if (scheme)
-			g_free (scheme);
+		if (   new_uri_valid
+		    || priv->uri_valid != new_uri_valid)
+			changed = TRUE;
 	}
-	if (changed) {
-		g_free (priv->uri);
-		priv->uri = g_strdup (uri);
+
+	new_response = nm_config_data_get_connectivity_response (config_data);
+	if (!nm_streq0 (new_response, cur_response))
+		changed = TRUE;
+
+	if (   !priv->con_config
+	    || !nm_streq0 (new_uri, priv->con_config->uri)
+	    || !nm_streq0 (new_response, priv->con_config->response)) {
+		if (!new_host_port) {
+			new_host = priv->con_config ? g_strdup (priv->con_config->host) : NULL;
+			new_port = priv->con_config ? g_strdup (priv->con_config->port) : NULL;
+		}
+		_con_config_unref (priv->con_config);
+		priv->con_config = g_slice_new (ConConfig);
+		*priv->con_config = (ConConfig) {
+			.ref_count = 1,
+			.uri       = g_strdup (new_uri),
+			.response  = g_strdup (new_response),
+			.host      = g_steal_pointer (&new_host),
+			.port      = g_steal_pointer (&new_port),
+		};
 	}
+	priv->uri_valid = new_uri_valid;
 
-	/* Set the interval. */
 	interval = nm_config_data_get_connectivity_interval (config_data);
 	interval = MIN (interval, (7 * 24 * 3600));
 	if (priv->interval != interval) {
@@ -691,11 +1055,8 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 
 	enabled = FALSE;
 #if WITH_CONCHECK
-	/* connectivity checking also requires a valid URI, interval and
-	 * curl_mhandle */
-	if (   priv->uri
-	    && priv->interval
-	    && priv->concheck.curl_mhandle)
+	if (   priv->uri_valid
+	    && priv->interval)
 		enabled = nm_config_data_get_connectivity_enabled (config_data);
 #endif
 
@@ -704,16 +1065,6 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 		changed = TRUE;
 	}
 
-	/* Set the response. */
-	response = nm_config_data_get_connectivity_response (config_data);
-	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);
-		priv->response = g_strdup (response);
-		changed = TRUE;
-	}
-
 	if (changed)
 		g_signal_emit (self, signals[CONFIG_CHANGED], 0);
 }
@@ -734,6 +1085,9 @@ static void
 nm_connectivity_init (NMConnectivity *self)
 {
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+#if WITH_CONCHECK
+	CURLcode ret;
+#endif
 
 	c_list_init (&priv->handles_lst_head);
 	c_list_init (&priv->completed_handles_lst_head);
@@ -745,17 +1099,10 @@ nm_connectivity_init (NMConnectivity *self)
 	                  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);
+	ret = curl_global_init (CURL_GLOBAL_ALL);
+	if (ret != CURLE_OK) {
+		_LOGE ("unable to init cURL, connectivity check will not work: (%d) %s",
+		       ret, curl_easy_strerror (ret));
 	}
 #endif
 
@@ -776,13 +1123,9 @@ dispose (GObject *object)
 	                                      handles_lst)))
 		cb_data_complete (cb_data, NM_CONNECTIVITY_DISPOSING, "shutting down");
 
-	g_clear_pointer (&priv->uri, g_free);
-	g_clear_pointer (&priv->response, g_free);
+	nm_clear_pointer (&priv->con_config, _con_config_unref);
 
 #if WITH_CONCHECK
-	nm_clear_g_source (&priv->concheck.curl_timer);
-
-	curl_multi_cleanup (priv->concheck.curl_mhandle);
 	curl_global_cleanup ();
 #endif
 
diff --git a/src/nm-connectivity.h b/src/nm-connectivity.h
index 99333ced..f262298a 100644
--- a/src/nm-connectivity.h
+++ b/src/nm-connectivity.h
@@ -73,6 +73,8 @@ typedef void (*NMConnectivityCheckCallback) (NMConnectivity *self,
                                              gpointer user_data);
 
 NMConnectivityCheckHandle *nm_connectivity_check_start (NMConnectivity *self,
+                                                        int family,
+                                                        int ifindex,
                                                         const char *iface,
                                                         NMConnectivityCheckCallback callback,
                                                         gpointer user_data);
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index a65ac636..6f55e62a 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -23,10 +23,8 @@
 
 #include "nm-core-utils.h"
 
-#include <errno.h>
 #include <fcntl.h>
 #include <fnmatch.h>
-#include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <resolv.h>
@@ -51,6 +49,10 @@
 #include "nm-setting-wireless.h"
 #include "nm-setting-wireless-security.h"
 
+#ifdef __NM_SD_UTILS_H__
+#error "nm-core-utils.c should stay independent of systemd utils. Are you looking for NetworkMangerUtils.c? "
+#endif
+
 G_STATIC_ASSERT (sizeof (NMUtilsTestFlags) <= sizeof (int));
 
 /* we read _nm_utils_testing without memory barrier. This is thread-safe,
@@ -242,16 +244,20 @@ nm_ethernet_address_is_valid (gconstpointer addr, gssize len)
 gconstpointer
 nm_utils_ipx_address_clear_host_address (int family, gpointer dst, gconstpointer src, guint8 plen)
 {
-	g_return_val_if_fail (src, NULL);
 	g_return_val_if_fail (dst, NULL);
 
 	switch (family) {
 	case AF_INET:
 		g_return_val_if_fail (plen <= 32, NULL);
+
+		if (!src) {
+			/* allow "self-assignment", by specifying %NULL as source. */
+			src = dst;
+		}
+
 		*((guint32 *) dst) = nm_utils_ip4_address_clear_host_address (*((guint32 *) src), plen);
 		break;
 	case AF_INET6:
-		g_return_val_if_fail (plen <= 128, NULL);
 		nm_utils_ip6_address_clear_host_address (dst, src, plen);
 		break;
 	default:
@@ -437,6 +443,7 @@ nm_utils_modprobe (GError **error, gboolean suppress_error_logging, const char *
 	/* construct the argument list */
 	argv = g_ptr_array_sized_new (4);
 	g_ptr_array_add (argv, "/sbin/modprobe");
+	g_ptr_array_add (argv, "--use-blacklist");
 	g_ptr_array_add (argv, (char *) arg1);
 
 	va_start (ap, arg1);
@@ -571,7 +578,7 @@ _kc_cb_timeout_grace_period (void *user_data)
 		/* ESRCH means, process does not exist or is already a zombie. */
 		if (errsv != ESRCH) {
 			nm_log_err (LOGD_CORE | data->log_domain, "%s: kill(SIGKILL) returned unexpected return value %d: (%s, %d)",
-			            data->log_name, ret, strerror (errsv), errsv);
+			            data->log_name, ret, nm_strerror_native (errsv), errsv);
 		}
 	} else {
 		nm_log_dbg (data->log_domain, "%s: process not terminated after %ld usec. Sending SIGKILL signal",
@@ -657,7 +664,7 @@ nm_utils_kill_child_async (pid_t pid, int sig, NMLogDomain log_domain,
 		/* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */
 		if (errsv != ECHILD) {
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)",
-			            LOG_NAME_ARGS, strerror (errsv), errsv);
+			            LOG_NAME_ARGS, nm_strerror_native (errsv), errsv);
 			_kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1);
 			return;
 		}
@@ -669,7 +676,7 @@ nm_utils_kill_child_async (pid_t pid, int sig, NMLogDomain log_domain,
 		/* ESRCH means, process does not exist or is already a zombie. */
 		if (errsv != ESRCH) {
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error sending %s: %s (%d)",
-			            LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+			            LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv);
 			_kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1);
 			return;
 		}
@@ -683,7 +690,7 @@ nm_utils_kill_child_async (pid_t pid, int sig, NMLogDomain log_domain,
 		} else {
 			errsv = errno;
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed due to unexpected return value %ld by waitpid (%s, %d) after sending %s",
-			            LOG_NAME_ARGS, (long) ret, strerror (errsv), errsv, _kc_signal_to_string (sig));
+			            LOG_NAME_ARGS, (long) ret, nm_strerror_native (errsv), errsv, _kc_signal_to_string (sig));
 			_kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1);
 		}
 		return;
@@ -705,7 +712,7 @@ nm_utils_kill_child_async (pid_t pid, int sig, NMLogDomain log_domain,
 	g_child_watch_add (pid, _kc_cb_watch_child, data);
 }
 
-static inline gulong
+static gulong
 _sleep_duration_convert_ms_to_us (guint32 sleep_duration_msec)
 {
 	if (sleep_duration_msec > 0) {
@@ -723,7 +730,7 @@ _sleep_duration_convert_ms_to_us (guint32 sleep_duration_msec)
  * @log_domain: log debug information for this domain. Errors and warnings are logged both
  * as %LOGD_CORE and @log_domain.
  * @log_name: name of the process to kill for logging.
- * @child_status: (out) (allow-none): return the exit status of the child, if no error occured.
+ * @child_status: (out) (allow-none): return the exit status of the child, if no error occurred.
  * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value
  * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter has not effect.
  * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate.
@@ -732,7 +739,7 @@ _sleep_duration_convert_ms_to_us (guint32 sleep_duration_msec)
  * Kill a child process synchronously and wait. The function first checks if the child already terminated
  * and if it did, return the exit status. Otherwise send one @sig signal. @sig  will always be
  * sent unless the child already exited. If the child does not exit within @wait_before_kill_msec milliseconds,
- * the function will send %SIGKILL and waits for the child indefinitly. If @wait_before_kill_msec is zero, no
+ * the function will send %SIGKILL and waits for the child indefinitely. If @wait_before_kill_msec is zero, no
  * %SIGKILL signal will be sent.
  *
  * In case of error, errno is preserved to contain the last reason of failure.
@@ -765,7 +772,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 		/* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */
 		if (errsv != ECHILD) {
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)",
-			            LOG_NAME_ARGS, strerror (errsv), errsv);
+			            LOG_NAME_ARGS, nm_strerror_native (errsv), errsv);
 			goto out;
 		}
 	}
@@ -776,7 +783,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 		/* ESRCH means, process does not exist or is already a zombie. */
 		if (errsv != ESRCH) {
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed to send %s: %s (%d)",
-			            LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+			            LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv);
 		} else {
 			/* let's try again with waitpid, probably there was a race... */
 			ret = waitpid (pid, &status, 0);
@@ -787,7 +794,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 			} else {
 				errsv = errno;
 				nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed due to unexpected return value %ld by waitpid (%s, %d) after sending %s",
-				            LOG_NAME_ARGS, (long) ret, strerror (errsv), errsv, _kc_signal_to_string (sig));
+				            LOG_NAME_ARGS, (long) ret, nm_strerror_native (errsv), errsv, _kc_signal_to_string (sig));
 			}
 		}
 		goto out;
@@ -818,7 +825,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 				/* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */
 				if (errsv != ECHILD) {
 					nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": after sending %s, waitpid failed with %s (%d)%s",
-					            LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv,
+					            LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv,
 					           was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : "");
 					goto out;
 				}
@@ -857,7 +864,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 				/* ESRCH means, process does not exist or is already a zombie. */
 				if (errsv != ESRCH) {
 					nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed to send SIGKILL (after sending %s), %s (%d)",
-								LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+								LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv);
 					goto out;
 				}
 			}
@@ -875,7 +882,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 
 		if (errsv != EINTR) {
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": after sending %s%s, waitpid failed with %s (%d)%s",
-			            LOG_NAME_ARGS, _kc_signal_to_string (sig), send_kill ? " and SIGKILL" : "", strerror (errsv), errsv,
+			            LOG_NAME_ARGS, _kc_signal_to_string (sig), send_kill ? " and SIGKILL" : "", nm_strerror_native (errsv), errsv,
 			            _kc_waited_to_string (buf_wait, wait_start_us));
 			goto out;
 		}
@@ -909,7 +916,7 @@ out:
  * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate.
  *   Set to zero, to use the default (meaning 20 wakeups per seconds).
  * @max_wait_msec: if 0, waits indefinitely until the process is gone (or a zombie). Otherwise, this
- *   is the maxium wait time until returning. If @max_wait_msec is non-zero but smaller then @wait_before_kill_msec,
+ *   is the maximum wait time until returning. If @max_wait_msec is non-zero but smaller then @wait_before_kill_msec,
  *   we will not send a final %SIGKILL.
  *
  * Kill a non-child process synchronously and wait. This function will not return before the
@@ -963,7 +970,7 @@ nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, NMLogDomain
 			            LOG_NAME_ARGS, _kc_signal_to_string (sig));
 		} else {
 			nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to send %s: %s (%d)",
-			             LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+			             LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv);
 		}
 		return;
 	}
@@ -1014,7 +1021,7 @@ nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, NMLogDomain
 				            was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : "");
 			} else {
 				nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to kill(%ld, 0): %s (%d)%s",
-				             LOG_NAME_ARGS, (long int) pid, strerror (errsv), errsv,
+				             LOG_NAME_ARGS, (long int) pid, nm_strerror_native (errsv), errsv,
 				             was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : "");
 			}
 			return;
@@ -1050,7 +1057,7 @@ nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, NMLogDomain
 						            LOG_NAME_ARGS, _kc_waited_to_string (buf_wait, wait_start_us));
 					} else {
 						nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to send SIGKILL (after sending %s), %s (%d)%s",
-						             LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv,
+						             LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv,
 						             _kc_waited_to_string (buf_wait, wait_start_us));
 					}
 					return;
@@ -1129,13 +1136,17 @@ nm_utils_read_link_absolute (const char *link_file, GError **error)
 		return ln;
 
 	dirname = g_path_get_dirname (link_file);
-	if (!g_path_is_absolute (link_file)) {
-		gs_free char *dirname_rel = dirname;
+	if (!g_path_is_absolute (dirname)) {
 		gs_free char *current_dir = g_get_current_dir ();
 
-		dirname = g_build_filename (current_dir, dirname_rel, NULL);
-	}
-	ln_abs = g_build_filename (dirname, ln, NULL);
+		/* @link_file argument was not an absolute path in the first place.
+		 * That actually may be a bug, because the CWD is not well defined
+		 * in most cases. Anyway, apparently we were able to load the file
+		 * even from a relative path. So, when making the link absolute, we
+		 * also need to prepend the CWD. */
+		ln_abs = g_build_filename (current_dir, dirname, ln, NULL);
+	} else
+		ln_abs = g_build_filename (dirname, ln, NULL);
 	g_free (dirname);
 	g_free (ln);
 	return ln_abs;
@@ -1939,173 +1950,6 @@ nm_utils_cmp_connection_by_autoconnect_priority (NMConnection *a, NMConnection *
 
 /*****************************************************************************/
 
-static gint64 monotonic_timestamp_offset_sec;
-static int monotonic_timestamp_clock_mode = 0;
-
-static void
-monotonic_timestamp_get (struct timespec *tp)
-{
-	int clock_mode = 0;
-	int err = 0;
-
-	switch (monotonic_timestamp_clock_mode) {
-	case 0:
-		/* the clock is not yet initialized (first run) */
-		err = clock_gettime (CLOCK_BOOTTIME, tp);
-		if (err == -1 && errno == EINVAL) {
-			clock_mode = 2;
-			err = clock_gettime (CLOCK_MONOTONIC, tp);
-		} else
-			clock_mode = 1;
-		break;
-	case 1:
-		/* default, return CLOCK_BOOTTIME */
-		err = clock_gettime (CLOCK_BOOTTIME, tp);
-		break;
-	case 2:
-		/* fallback, return CLOCK_MONOTONIC. Kernels prior to 2.6.39
-		 * (released on 18 May, 2011) don't support CLOCK_BOOTTIME. */
-		err = clock_gettime (CLOCK_MONOTONIC, tp);
-		break;
-	}
-
-	g_assert (err == 0); (void)err;
-	g_assert (tp->tv_nsec >= 0 && tp->tv_nsec < NM_UTILS_NS_PER_SECOND);
-
-	if (G_LIKELY (clock_mode == 0))
-		return;
-
-	/* Calculate an offset for the time stamp.
-	 *
-	 * We always want positive values, because then we can initialize
-	 * a timestamp with 0 and be sure, that it will be less then any
-	 * value nm_utils_get_monotonic_timestamp_*() might return.
-	 * For this to be true also for nm_utils_get_monotonic_timestamp_s() at
-	 * early boot, we have to shift the timestamp to start counting at
-	 * least from 1 second onward.
-	 *
-	 * Another advantage of shifting is, that this way we make use of the whole 31 bit
-	 * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_s()
-	 * wraps (~68 years).
-	 **/
-	monotonic_timestamp_offset_sec = (- ((gint64) tp->tv_sec)) + 1;
-	monotonic_timestamp_clock_mode = clock_mode;
-
-	if (nm_logging_enabled (LOGL_DEBUG, LOGD_CORE)) {
-		time_t now = time (NULL);
-		struct tm tm;
-		char s[255];
-
-		strftime (s, sizeof (s), "%Y-%m-%d %H:%M:%S", localtime_r (&now, &tm));
-		nm_log_dbg (LOGD_CORE, "monotonic timestamp started counting 1.%09ld seconds ago with "
-		                       "an offset of %lld.0 seconds to %s (local time is %s)",
-		                       tp->tv_nsec, (long long) -monotonic_timestamp_offset_sec,
-		                       clock_mode == 1 ? "CLOCK_BOOTTIME" : "CLOCK_MONOTONIC", s);
-	}
-}
-
-/**
- * nm_utils_get_monotonic_timestamp_ns:
- *
- * Returns: a monotonically increasing time stamp in nanoseconds,
- * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
- *
- * The returned value will start counting at an undefined point
- * in the past and will always be positive.
- *
- * All the nm_utils_get_monotonic_timestamp_*s functions return the same
- * timestamp but in different scales (nsec, usec, msec, sec).
- **/
-gint64
-nm_utils_get_monotonic_timestamp_ns (void)
-{
-	struct timespec tp = { 0 };
-
-	monotonic_timestamp_get (&tp);
-
-	/* Although the result will always be positive, we return a signed
-	 * integer, which makes it easier to calculate time differences (when
-	 * you want to subtract signed values).
-	 **/
-	return (((gint64) tp.tv_sec) + monotonic_timestamp_offset_sec) * NM_UTILS_NS_PER_SECOND +
-	       tp.tv_nsec;
-}
-
-/**
- * nm_utils_get_monotonic_timestamp_us:
- *
- * Returns: a monotonically increasing time stamp in microseconds,
- * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
- *
- * The returned value will start counting at an undefined point
- * in the past and will always be positive.
- *
- * All the nm_utils_get_monotonic_timestamp_*s functions return the same
- * timestamp but in different scales (nsec, usec, msec, sec).
- **/
-gint64
-nm_utils_get_monotonic_timestamp_us (void)
-{
-	struct timespec tp = { 0 };
-
-	monotonic_timestamp_get (&tp);
-
-	/* Although the result will always be positive, we return a signed
-	 * integer, which makes it easier to calculate time differences (when
-	 * you want to subtract signed values).
-	 **/
-	return (((gint64) tp.tv_sec) + monotonic_timestamp_offset_sec) * ((gint64) G_USEC_PER_SEC) +
-	       (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/G_USEC_PER_SEC));
-}
-
-/**
- * nm_utils_get_monotonic_timestamp_ms:
- *
- * Returns: a monotonically increasing time stamp in milliseconds,
- * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
- *
- * The returned value will start counting at an undefined point
- * in the past and will always be positive.
- *
- * All the nm_utils_get_monotonic_timestamp_*s functions return the same
- * timestamp but in different scales (nsec, usec, msec, sec).
- **/
-gint64
-nm_utils_get_monotonic_timestamp_ms (void)
-{
-	struct timespec tp = { 0 };
-
-	monotonic_timestamp_get (&tp);
-
-	/* Although the result will always be positive, we return a signed
-	 * integer, which makes it easier to calculate time differences (when
-	 * you want to subtract signed values).
-	 **/
-	return (((gint64) tp.tv_sec) + monotonic_timestamp_offset_sec) * ((gint64) 1000) +
-	       (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000));
-}
-
-/**
- * nm_utils_get_monotonic_timestamp_s:
- *
- * Returns: nm_utils_get_monotonic_timestamp_ms() in seconds (throwing
- * away sub second parts). The returned value will always be positive.
- *
- * This value wraps after roughly 68 years which should be fine for any
- * practical purpose.
- *
- * All the nm_utils_get_monotonic_timestamp_*s functions return the same
- * timestamp but in different scales (nsec, usec, msec, sec).
- **/
-gint32
-nm_utils_get_monotonic_timestamp_s (void)
-{
-	struct timespec tp = { 0 };
-
-	monotonic_timestamp_get (&tp);
-	return (((gint64) tp.tv_sec) + monotonic_timestamp_offset_sec);
-}
-
 typedef struct
 {
 	const char *name;
@@ -2192,7 +2036,7 @@ _log_connection_get_property (NMSetting *setting, const char *name)
 		return g_strdup ("****");
 
 	if (!_nm_setting_get_property (setting, name, &val))
-		g_return_val_if_reached (FALSE);
+		return g_strdup ("<unknown>");
 
 	if (G_VALUE_HOLDS_STRING (&val)) {
 		const char *val_s;
@@ -2295,7 +2139,7 @@ nm_utils_log_connection_diff (NMConnection *connection,
 		return;
 	}
 
-	/* FIXME: it doesn't nicely show the content of NMSettingVpn, becuase nm_connection_diff() does not
+	/* FIXME: it doesn't nicely show the content of NMSettingVpn, because nm_connection_diff() does not
 	 * expand the hash values. */
 
 	sorted_hashes = _log_connection_sort_hashes (connection, diff_base, connection_diff);
@@ -2385,49 +2229,6 @@ out:
 	g_array_free (sorted_hashes, TRUE);
 }
 
-/**
- * nm_utils_monotonic_timestamp_as_boottime:
- * @timestamp: the monotonic-timestamp that should be converted into CLOCK_BOOTTIME.
- * @timestamp_ns_per_tick: How many nano seconds make one unit of @timestamp? E.g. if
- * @timestamp is in unit seconds, pass %NM_UTILS_NS_PER_SECOND; @timestamp in nano
- * seconds, pass 1; @timestamp in milli seconds, pass %NM_UTILS_NS_PER_SECOND/1000; etc.
- *
- * Returns: the monotonic-timestamp as CLOCK_BOOTTIME, as returned by clock_gettime().
- * The unit is the same as the passed in @timestamp basd on @timestamp_ns_per_tick.
- * E.g. if you passed @timestamp in as seconds, it will return boottime in seconds.
- * If @timestamp is a non-positive, it returns -1. Note that a (valid) monotonic-timestamp
- * is always positive.
- *
- * On older kernels that don't support CLOCK_BOOTTIME, the returned time is instead CLOCK_MONOTONIC.
- **/
-gint64
-nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_per_tick)
-{
-	gint64 offset;
-
-	/* only support ns-per-tick being a multiple of 10. */
-	g_return_val_if_fail (timestamp_ns_per_tick == 1
-	                      || (timestamp_ns_per_tick > 0 &&
-	                          timestamp_ns_per_tick <= NM_UTILS_NS_PER_SECOND &&
-	                          timestamp_ns_per_tick % 10 == 0),
-	                      -1);
-
-	/* Check that the timestamp is in a valid range. */
-	g_return_val_if_fail (timestamp >= 0, -1);
-
-	/* if the caller didn't yet ever fetch a monotonic-timestamp, he cannot pass any meaningful
-	 * value (because he has no idea what these timestamps would be). That would be a bug. */
-	g_return_val_if_fail (monotonic_timestamp_clock_mode != 0, -1);
-
-	/* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */
-	offset = monotonic_timestamp_offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick);
-
-	/* check for overflow. */
-	g_return_val_if_fail (offset > 0 || timestamp < G_MAXINT64 + offset, G_MAXINT64);
-
-	return timestamp - offset;
-}
-
 #define IPV6_PROPERTY_DIR "/proc/sys/net/ipv6/conf/"
 #define IPV4_PROPERTY_DIR "/proc/sys/net/ipv4/conf/"
 G_STATIC_ASSERT (sizeof (IPV4_PROPERTY_DIR) == sizeof (IPV6_PROPERTY_DIR));
@@ -2595,11 +2396,11 @@ _uuid_data_init (UuidData *uuid_data,
 	uuid_data->is_fake = is_fake;
 	if (packed) {
 		G_STATIC_ASSERT_EXPR (sizeof (uuid_data->str) >= (sizeof (*uuid) * 2 + 1));
-		_nm_utils_bin2hexstr_full (uuid,
-		                           sizeof (*uuid),
-		                           '\0',
-		                           FALSE,
-		                           uuid_data->str);
+		nm_utils_bin2hexstr_full (uuid,
+		                          sizeof (*uuid),
+		                          '\0',
+		                          FALSE,
+		                          uuid_data->str);
 	} else {
 		G_STATIC_ASSERT_EXPR (sizeof (uuid_data->str) >= 37);
 		_nm_utils_uuid_unparse (uuid, uuid_data->str);
@@ -2632,14 +2433,14 @@ again:
 		if (   nm_utils_file_get_contents (-1, "/etc/machine-id", 100*1024, 0, &content, NULL, NULL) >= 0
 		    || nm_utils_file_get_contents (-1, LOCALSTATEDIR"/lib/dbus/machine-id", 100*1024, 0, &content, NULL, NULL) >= 0) {
 			g_strstrip (content);
-			if (_nm_utils_hexstr2bin_full (content,
-			                               FALSE,
-			                               FALSE,
-			                               NULL,
-			                               16,
-			                               (guint8 *) &uuid,
-			                               sizeof (uuid),
-			                               NULL)) {
+			if (nm_utils_hexstr2bin_full (content,
+			                              FALSE,
+			                              FALSE,
+			                              NULL,
+			                              16,
+			                              (guint8 *) &uuid,
+			                              sizeof (uuid),
+			                              NULL)) {
 				if (!nm_utils_uuid_is_null (&uuid)) {
 					/* an all-zero machine-id is not valid. */
 					is_fake = FALSE;
@@ -2661,7 +2462,7 @@ again:
 			if (nm_utils_host_id_get (&seed_bin, &seed_len)) {
 				/* we have no valid machine-id. Generate a fake one by hashing
 				 * the secret-key. This key is commonly persisted, so it should be
-				 * stable accross reboots (despite having a broken system without
+				 * stable across reboots (despite having a broken system without
 				 * proper machine-id). */
 				fake_type = "secret-key";
 				hash_seed = "ab085f06-b629-46d1-a553-84eeba5683b6";
@@ -2758,7 +2559,7 @@ _host_id_read_timestamp (gboolean use_secret_key_file,
 	 * the secret_key) if we are unable to access the secret_key file in the first place.
 	 *
 	 * Pick a random timestamp from the past two years. Yes, this timestamp
-	 * is not stable accross restarts, but apparently neither is the host-id
+	 * is not stable across restarts, but apparently neither is the host-id
 	 * nor the secret_key itself. */
 
 #define EPOCH_TWO_YEARS  (G_GINT64_CONSTANT (2 * 365 * 24 * 3600) * NM_UTILS_NS_PER_SECOND)
@@ -3292,14 +3093,12 @@ nm_utils_ipv6_interface_identifier_get_from_token (NMUtilsIPv6IfaceId *iid,
 /**
  * nm_utils_inet6_interface_identifier_to_token:
  * @iid: %NMUtilsIPv6IfaceId interface identifier
- * @buf: the destination buffer or %NULL
+ * @buf: the destination buffer of at least %NM_UTILS_INET_ADDRSTRLEN
+ *   bytes.
  *
  * Converts the interface identifier to a string token.
- * If the destination buffer it set, set it is used to store the
- * resulting token, otherwise an internal static buffer is used.
- * The buffer needs to be %NM_UTILS_INET_ADDRSTRLEN characters long.
  *
- * Returns: a statically allocated array. Do not g_free().
+ * Returns: the input buffer filled with the id as string.
  */
 const char *
 nm_utils_inet6_interface_identifier_to_token (NMUtilsIPv6IfaceId iid, char *buf)
@@ -3328,7 +3127,7 @@ nm_utils_stable_id_generated_complete (const char *stable_id_generated)
 	guint8 buf[NM_UTILS_CHECKSUM_LENGTH_SHA1];
 	char *base64;
 
-	/* for NM_UTILS_STABLE_TYPE_GENERATED we genererate a possibly long string
+	/* for NM_UTILS_STABLE_TYPE_GENERATED we generate a possibly long string
 	 * by doing text-substitutions in nm_utils_stable_id_parse().
 	 *
 	 * Let's shorten the (possibly) long stable_id to something more compact. */
@@ -3382,7 +3181,7 @@ nm_utils_stable_id_parse (const char *stable_id,
 	 * of ${...} patterns.
 	 *
 	 * At first, it looks a bit like bash parameter substitution.
-	 * In contrast however, the process is unambigious so that the resulting
+	 * In contrast however, the process is unambiguous so that the resulting
 	 * effective id differs if:
 	 *  - the original, untranslated stable-id differs
 	 *  - or any of the subsitutions differs.
@@ -3446,7 +3245,7 @@ nm_utils_stable_id_parse (const char *stable_id,
 			_stable_id_append (str, hwaddr);
 		else if (g_str_has_prefix (&stable_id[i], "${RANDOM}")) {
 			/* RANDOM makes not so much sense for cloned-mac-address
-			 * as the result is simmilar to specyifing "cloned-mac-address=random".
+			 * as the result is similar to specyifing "cloned-mac-address=random".
 			 * It makes however sense for RFC 7217 Stable Privacy IPv6 addresses
 			 * where this is effectively the only way to generate a different
 			 * (random) host identifier for each connect.
@@ -3780,12 +3579,55 @@ nm_utils_dhcp_client_id_mac (int arp_type,
 	return g_bytes_new_take (client_id_buf, hwaddr_len + 1);
 }
 
+#define HASH_KEY ((const guint8[16]) { 0x80, 0x11, 0x8c, 0xc2, 0xfe, 0x4a, 0x03, 0xee, 0x3e, 0xd6, 0x0c, 0x6f, 0x36, 0x39, 0x14, 0x09 })
+
+/**
+ * nm_utils_create_dhcp_iaid:
+ * @legacy_unstable_byteorder: legacy behavior is to generate a u32 iaid which
+ *   is endianness dependent. This is to preserve backward compatibility.
+ *   For non-legacy behavior, the returned integer is in stable endianness,
+ *   and corresponds to legacy behavior on little endian systems.
+ * @interface_id: the seed for hashing when generating the ID. Usually,
+ *   this is the interface name.
+ * @interface_id_len: length of @interface_id
+ *
+ * This corresponds to systemd's dhcp_identifier_set_iaid() for generating
+ * a IAID for the interface.
+ *
+ * Returns: the IAID in host byte order. */
+guint32
+nm_utils_create_dhcp_iaid (gboolean legacy_unstable_byteorder,
+                           const guint8 *interface_id,
+                           gsize interface_id_len)
+{
+	guint64 u64;
+	guint32 u32;
+
+	u64 = c_siphash_hash (HASH_KEY, interface_id, interface_id_len);
+	u32 = (u64 & 0xffffffffu) ^ (u64 >> 32);
+	if (legacy_unstable_byteorder) {
+		/* legacy systemd code dhcp_identifier_set_iaid() generates the iaid
+		 * dependent on the host endianness. Since this function returns the IAID
+		 * in native-byte order, we need to account for that.
+		 *
+		 * On little endian systems, we want the legacy-behavior is identical to
+		 * the endianness-agnostic behavior. So, we need to swap the bytes on
+		 * big-endian systems.
+		 *
+		 * (https://github.com/systemd/systemd/pull/10614). */
+		return htole32 (u32);
+	} else {
+		/* we return the value as-is, in native byte order. */
+		return u32;
+	}
+}
+
 /**
  * nm_utils_dhcp_client_id_systemd_node_specific_full:
  * @legacy_unstable_byteorder: historically, the code would generate a iaid
  *   dependent on host endianness. This is undesirable, if backward compatibility
  *   are not a concern, generate stable endianness.
- * @interface_id: a binary identifer that is hashed into the DUID.
+ * @interface_id: a binary identifier that is hashed into the DUID.
  *   Comonly this is the interface-name, but it may be the MAC address.
  * @interface_id_len: the length of @interface_id.
  * @machine_id: the binary identifier for the machine. It is hashed
@@ -3805,7 +3647,6 @@ nm_utils_dhcp_client_id_systemd_node_specific_full (gboolean legacy_unstable_byt
                                                     const guint8 *machine_id,
                                                     gsize machine_id_len)
 {
-	const guint8 HASH_KEY[16] = { 0x80, 0x11, 0x8c, 0xc2, 0xfe, 0x4a, 0x03, 0xee, 0x3e, 0xd6, 0x0c, 0x6f, 0x36, 0x39, 0x14, 0x09 };
 	const guint16 DUID_TYPE_EN = 2;
 	const guint32 SYSTEMD_PEN = 43793;
 	struct _nm_packed {
@@ -3834,20 +3675,10 @@ nm_utils_dhcp_client_id_systemd_node_specific_full (gboolean legacy_unstable_byt
 
 	client_id->type = 255;
 
-	u64 = c_siphash_hash (HASH_KEY, interface_id, interface_id_len);
-	u32 = (u64 & 0xffffffffu) ^ (u64 >> 32);
-	if (legacy_unstable_byteorder) {
-		/* original systemd code dhcp_identifier_set_iaid() generates the iaid
-		 * in native endianness. Do that too, to preserve compatibility
-		 * (https://github.com/systemd/systemd/pull/10614). */
-		u32 = bswap_32 (u32);
-	} else {
-		/* generate fixed byteorder, in a way that on little endian systems
-		 * the values agree. Meaning: legacy behavior is identical to this
-		 * on little endian. */
-		u32 = be32toh (u32);
-	}
-	unaligned_write_ne32 (&client_id->iaid, u32);
+	u32 = nm_utils_create_dhcp_iaid (legacy_unstable_byteorder,
+	                                 interface_id,
+	                                 interface_id_len);
+	unaligned_write_be32 (&client_id->iaid, u32);
 
 	unaligned_write_be16 (&client_id->duid.type, DUID_TYPE_EN);
 
@@ -4191,7 +4022,7 @@ nm_utils_get_reverse_dns_domains_ip6 (const struct in6_addr *ip, guint8 plen, GP
 		return;
 
 	memcpy (&addr, ip, sizeof (struct in6_addr));
-	nm_utils_ip6_address_clear_host_address (&addr, &addr, plen);
+	nm_utils_ip6_address_clear_host_address (&addr, NULL, plen);
 
 	/* Number of nibbles to include in domains */
 	nibbles = (plen - 1) / 4 + 1;
@@ -4315,7 +4146,7 @@ nm_utils_read_plugin_paths (const char *dirname, const char *prefix)
 			errsv = errno;
 			nm_log_warn (LOGD_CORE,
 			             "plugin: skip invalid file %s (error during stat: %s)",
-			             data.path, strerror (errsv));
+			             data.path, nm_strerror_native (errsv));
 			goto skip;
 		}
 
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index 2e75ea62..1b0d39ed 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -27,6 +27,8 @@
 
 #include "nm-connection.h"
 
+#include "nm-utils/nm-time-utils.h"
+
 /*****************************************************************************/
 
 #define NM_PLATFORM_LIFETIME_PERMANENT G_MAXUINT32
@@ -237,9 +239,6 @@ gboolean nm_wildcard_match_check (const char *str,
 
 /*****************************************************************************/
 
-const char *nm_utils_get_ip_config_method (NMConnection *connection,
-                                           GType         ip_setting_type);
-
 gboolean nm_utils_connection_has_default_route (NMConnection *connection,
                                                 int addr_family,
                                                 gboolean *out_is_never_default);
@@ -256,19 +255,6 @@ void nm_utils_log_connection_diff (NMConnection *connection,
                                    const char *prefix,
                                    const char *dbus_path);
 
-gint64 nm_utils_get_monotonic_timestamp_ns (void);
-gint64 nm_utils_get_monotonic_timestamp_us (void);
-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);
 
@@ -405,6 +391,10 @@ GBytes *nm_utils_dhcp_client_id_mac (int arp_type,
                                      const guint8 *hwaddr,
                                      gsize hwaddr_len);
 
+guint32 nm_utils_create_dhcp_iaid (gboolean legacy_unstable_byteorder,
+                                   const guint8 *interface_id,
+                                   gsize interface_id_len);
+
 GBytes *nm_utils_dhcp_client_id_systemd_node_specific_full (gboolean legacy_unstable_byteorder,
                                                             const guint8 *interface_id,
                                                             gsize interface_id_len,
@@ -495,4 +485,8 @@ const char *nm_activation_type_to_string (NMActivationType activation_type);
 
 const char *nm_utils_parse_dns_domain (const char *domain, gboolean *is_routing);
 
+/*****************************************************************************/
+
+#define NM_VPN_ROUTE_METRIC_DEFAULT     50
+
 #endif /* __NM_CORE_UTILS_H__ */
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c
index a5c7c12b..00fa6617 100644
--- a/src/nm-dbus-manager.c
+++ b/src/nm-dbus-manager.c
@@ -26,8 +26,6 @@
 #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"
@@ -1182,7 +1180,7 @@ _nm_dbus_manager_obj_notify (NMDBusObject *obj,
 	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
+	 * (interfaces x properties) is static and possibly small, this naive search is effectively
 	 * O(1). We might wanna introduce some index to lookup the properties in question faster.
 	 *
 	 * The nice part of this implementation is however, that the order in which properties
@@ -1470,6 +1468,14 @@ static const GDBusInterfaceInfo interface_info_objmgr = NM_DEFINE_GDBUS_INTERFAC
 
 /*****************************************************************************/
 
+GDBusConnection *
+nm_dbus_manager_get_dbus_connection (NMDBusManager *self)
+{
+	g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), NULL);
+
+	return NM_DBUS_MANAGER_GET_PRIVATE (self)->connection;
+}
+
 void
 nm_dbus_manager_start (NMDBusManager *self,
                        NMDBusManagerSetPropertyHandler set_property_handler,
diff --git a/src/nm-dbus-manager.h b/src/nm-dbus-manager.h
index 04c42bb0..89acd7c8 100644
--- a/src/nm-dbus-manager.h
+++ b/src/nm-dbus-manager.h
@@ -51,6 +51,8 @@ typedef void (*NMDBusManagerSetPropertyHandler) (NMDBusObject *obj,
 
 gboolean nm_dbus_manager_acquire_bus (NMDBusManager *self);
 
+GDBusConnection *nm_dbus_manager_get_dbus_connection (NMDBusManager *self);
+
 void nm_dbus_manager_start (NMDBusManager *self,
                             NMDBusManagerSetPropertyHandler set_property_handler,
                             gpointer set_property_handler_data);
diff --git a/src/nm-dcb.c b/src/nm-dcb.c
index 5a46fa00..a63fdf3d 100644
--- a/src/nm-dcb.c
+++ b/src/nm-dcb.c
@@ -21,7 +21,6 @@
 #include "nm-default.h"
 
 #include <sys/wait.h>
-#include <string.h>
 
 #include "nm-dcb.h"
 #include "platform/nm-platform.h"
diff --git a/src/nm-dhcp4-config.c b/src/nm-dhcp4-config.c
index ceafddf5..fe0df3a7 100644
--- a/src/nm-dhcp4-config.c
+++ b/src/nm-dhcp4-config.c
@@ -22,8 +22,6 @@
 
 #include "nm-dhcp4-config.h"
 
-#include <string.h>
-
 #include "nm-dbus-interface.h"
 #include "nm-utils.h"
 #include "nm-dbus-object.h"
diff --git a/src/nm-dhcp6-config.c b/src/nm-dhcp6-config.c
index d477521a..59266d55 100644
--- a/src/nm-dhcp6-config.c
+++ b/src/nm-dhcp6-config.c
@@ -22,8 +22,6 @@
 
 #include "nm-dhcp6-config.h"
 
-#include <string.h>
-
 #include "nm-dbus-interface.h"
 #include "nm-utils.h"
 #include "nm-dbus-object.h"
diff --git a/src/nm-dispatcher.c b/src/nm-dispatcher.c
index 368b781a..c0ef1bb6 100644
--- a/src/nm-dispatcher.c
+++ b/src/nm-dispatcher.c
@@ -23,9 +23,6 @@
 
 #include "nm-dispatcher.h"
 
-#include <string.h>
-#include <errno.h>
-
 #include "nm-dispatcher-api.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
@@ -965,7 +962,7 @@ dispatcher_dir_changed (GFileMonitor *monitor,
 		else if (errsv == 0)
 			_LOGD ("%s script directory '%s' has no scripts", item->description, item->dir);
 		else {
-			_LOGD ("%s script directory '%s' error reading (%s)", item->description, item->dir, strerror (errsv));
+			_LOGD ("%s script directory '%s' error reading (%s)", item->description, item->dir, nm_strerror_native (errsv));
 			item->has_scripts = TRUE;
 		}
 	} else {
diff --git a/src/nm-firewall-manager.c b/src/nm-firewall-manager.c
index 5b5e7cfa..a4b079b4 100644
--- a/src/nm-firewall-manager.c
+++ b/src/nm-firewall-manager.c
@@ -22,8 +22,6 @@
 
 #include "nm-firewall-manager.h"
 
-#include <string.h>
-
 #include "NetworkManagerUtils.h"
 #include "c-list/src/c-list.h"
 
diff --git a/src/nm-hostname-manager.c b/src/nm-hostname-manager.c
index 88ff1604..d405a320 100644
--- a/src/nm-hostname-manager.c
+++ b/src/nm-hostname-manager.c
@@ -23,8 +23,6 @@
 #include "nm-hostname-manager.h"
 
 #include <sys/stat.h>
-#include <errno.h>
-#include <string.h>
 
 #if HAVE_SELINUX
 #include <selinux/selinux.h>
diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c
index 6ae2b9d2..1ef9f5ae 100644
--- a/src/nm-iface-helper.c
+++ b/src/nm-iface-helper.c
@@ -23,11 +23,9 @@
 #include <glib-unix.h>
 #include <getopt.h>
 #include <locale.h>
-#include <errno.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
-#include <string.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <signal.h>
@@ -223,14 +221,14 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 	}
 
 	if (changed & NM_NDISC_CONFIG_HOP_LIMIT)
-		nm_platform_sysctl_set_ip6_hop_limit_safe (NM_PLATFORM_GET, global_opt.ifname, rdata->hop_limit);
+		nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (NM_PLATFORM_GET, global_opt.ifname, rdata->hop_limit);
 
 	if (changed & NM_NDISC_CONFIG_MTU) {
-		char val[16];
-		char sysctl_path_buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-		g_snprintf (val, sizeof (val), "%d", rdata->mtu);
-		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, sysctl_path_buf, global_opt.ifname, "mtu")), val);
+		nm_platform_sysctl_ip_conf_set_int64 (NM_PLATFORM_GET,
+		                                      AF_INET6,
+		                                      global_opt.ifname,
+		                                      "mtu",
+		                                      rdata->mtu);
 	}
 
 	nm_ip6_config_merge (existing, ndisc_config, NM_IP_CONFIG_MERGE_DEFAULT, 0);
@@ -389,7 +387,7 @@ main (int argc, char *argv[])
 	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];
+	int errsv;
 
 	c_list_init (&gl.dad_failed_lst_head);
 
@@ -398,11 +396,11 @@ main (int argc, char *argv[])
 	if (!do_early_setup (&argc, &argv))
 		return 1;
 
-	nm_logging_set_syslog_identifier ("nm-iface-helper");
-	nm_logging_set_prefix ("%s[%ld] (%s): ",
-	                       _NMLOG_PREFIX_NAME,
-	                       (long) getpid (),
-	                       global_opt.ifname ?: "???");
+	nm_logging_init_pre ("nm-iface-helper",
+	                     g_strdup_printf ("%s[%ld] (%s): ",
+	                                      _NMLOG_PREFIX_NAME,
+	                                      (long) getpid (),
+	                                      global_opt.ifname ?: "???"));
 
 	if (global_opt.g_fatal_warnings) {
 		GLogLevelFlags fatal_mask;
@@ -426,7 +424,8 @@ main (int argc, char *argv[])
 
 	gl.ifindex = nmp_utils_if_nametoindex (global_opt.ifname);
 	if (gl.ifindex <= 0) {
-		fprintf (stderr, _("Failed to find interface index for %s (%s)\n"), global_opt.ifname, strerror (errno));
+		errsv = errno;
+		fprintf (stderr, _("Failed to find interface index for %s (%s)\n"), global_opt.ifname, nm_strerror_native (errsv));
 		return 1;
 	}
 	pidfile = g_strdup_printf (NMIH_PID_FILE_FMT, gl.ifindex);
@@ -451,12 +450,10 @@ main (int argc, char *argv[])
 
 	if (global_opt.become_daemon && !global_opt.debug) {
 		if (daemon (0, 0) < 0) {
-			int saved_errno;
-
-			saved_errno = errno;
+			errsv = errno;
 			fprintf (stderr, _("Could not daemonize: %s [error %u]\n"),
-			         g_strerror (saved_errno),
-			         saved_errno);
+			         nm_strerror_native (errsv),
+			         errsv);
 			return 1;
 		}
 		if (nm_main_utils_write_pidfile (pidfile))
@@ -467,8 +464,8 @@ main (int argc, char *argv[])
 	gl.main_loop = g_main_loop_new (NULL, FALSE);
 	setup_signals ();
 
-	nm_logging_syslog_openlog (global_opt.logging_backend,
-	                           global_opt.debug);
+	nm_logging_init (global_opt.logging_backend,
+	                 global_opt.debug);
 
 	_LOGI (LOGD_CORE, "nm-iface-helper (version " NM_DIST_VERSION ") is starting...");
 
@@ -500,7 +497,11 @@ main (int argc, char *argv[])
 	}
 
 	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");
+		nm_platform_sysctl_ip_conf_set (NM_PLATFORM_GET,
+		                                AF_INET,
+		                                global_opt.ifname,
+		                                "promote_secondaries",
+		                                "1");
 
 		dhcp4_client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (),
 		                                          nm_platform_get_multi_idx (NM_PLATFORM_GET),
@@ -552,10 +553,10 @@ main (int argc, char *argv[])
 		if (iid)
 			nm_ndisc_set_iid (ndisc, *iid);
 
-		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, sysctl_path_buf, global_opt.ifname, "accept_ra")), "1");
-		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, sysctl_path_buf, global_opt.ifname, "accept_ra_defrtr")), "0");
-		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, sysctl_path_buf, global_opt.ifname, "accept_ra_pinfo")), "0");
-		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, sysctl_path_buf, global_opt.ifname, "accept_ra_rtr_pref")), "0");
+		nm_platform_sysctl_ip_conf_set (NM_PLATFORM_GET, AF_INET6, global_opt.ifname, "accept_ra",          "1");
+		nm_platform_sysctl_ip_conf_set (NM_PLATFORM_GET, AF_INET6, global_opt.ifname, "accept_ra_defrtr",   "0");
+		nm_platform_sysctl_ip_conf_set (NM_PLATFORM_GET, AF_INET6, global_opt.ifname, "accept_ra_pinfo",    "0");
+		nm_platform_sysctl_ip_conf_set (NM_PLATFORM_GET, AF_INET6, global_opt.ifname, "accept_ra_rtr_pref", "0");
 
 		g_signal_connect (NM_PLATFORM_GET,
 		                  NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index ce7f7fc4..1c06a42c 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -23,7 +23,6 @@
 
 #include "nm-ip4-config.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <resolv.h>
 #include <linux/rtnetlink.h>
@@ -1051,6 +1050,7 @@ nm_ip4_config_create_setting (const NMIP4Config *self)
 	NMDedupMultiIter ipconf_iter;
 	const NMPlatformIP4Address *address;
 	const NMPlatformIP4Route *route;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	s_ip4 = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ());
 
@@ -1095,7 +1095,7 @@ nm_ip4_config_create_setting (const NMIP4Config *self)
 		g_object_set (s_ip4,
 		              NM_SETTING_IP_CONFIG_GATEWAY,
 		              nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway,
-		                                   NULL),
+		                                   sbuf),
 		              NULL);
 	}
 
@@ -1130,7 +1130,7 @@ nm_ip4_config_create_setting (const NMIP4Config *self)
 	for (i = 0; i < nnameservers; i++) {
 		guint32 nameserver = nm_ip4_config_get_nameserver (self, i);
 
-		nm_setting_ip_config_add_dns (s_ip4, nm_utils_inet4_ntop (nameserver, NULL));
+		nm_setting_ip_config_add_dns (s_ip4, nm_utils_inet4_ntop (nameserver, sbuf));
 	}
 	for (i = 0; i < nsearches; i++) {
 		const char *search = nm_ip4_config_get_search (self, i);
@@ -1511,6 +1511,7 @@ nm_ip4_config_subtract (NMIP4Config *dst,
 static gboolean
 _nm_ip4_config_intersect_helper (NMIP4Config *dst,
                                  const NMIP4Config *src,
+                                 gboolean intersect_addresses,
                                  gboolean intersect_routes,
                                  guint32 default_route_metric_penalty,
                                  gboolean update_dst)
@@ -1533,24 +1534,26 @@ _nm_ip4_config_intersect_helper (NMIP4Config *dst,
 		g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	changed = FALSE;
-	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, dst, &a) {
-		if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
-		                                     &src_priv->idx_ip4_addresses,
-		                                     NMP_OBJECT_UP_CAST (a)))
-			continue;
-
-		if (!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) {
-		_notify_addresses (dst);
-		result = TRUE;
+	if (intersect_addresses) {
+		changed = FALSE;
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, dst, &a) {
+			if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
+			                                     &src_priv->idx_ip4_addresses,
+			                                     NMP_OBJECT_UP_CAST (a)))
+				continue;
+
+			if (!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) {
+			_notify_addresses (dst);
+			result = TRUE;
+		}
 	}
 
 	/* ignore nameservers */
@@ -1599,12 +1602,12 @@ _nm_ip4_config_intersect_helper (NMIP4Config *dst,
 		_notify (dst, PROP_GATEWAY);
 	}
 
-skip_routes:
 	if (changed) {
 		_notify_routes (dst);
 		result = TRUE;
 	}
 
+skip_routes:
 	/* ignore domains */
 	/* ignore dns searches */
 	/* ignore dns options */
@@ -1622,6 +1625,8 @@ skip_routes:
  * nm_ip4_config_intersect:
  * @dst: a configuration to be updated
  * @src: another configuration
+ * @intersect_addresses: whether addresses should be intersected
+ * @intersect_routes: whether routes should be intersected
  * @default_route_metric_penalty: the default route metric penalty
  *
  * Computes the intersection between @src and @dst and updates @dst in place
@@ -1630,16 +1635,24 @@ skip_routes:
 void
 nm_ip4_config_intersect (NMIP4Config *dst,
                          const NMIP4Config *src,
+                         gboolean intersect_addresses,
                          gboolean intersect_routes,
                          guint32 default_route_metric_penalty)
 {
-	_nm_ip4_config_intersect_helper (dst, src, intersect_routes, default_route_metric_penalty, TRUE);
+	_nm_ip4_config_intersect_helper (dst,
+	                                 src,
+	                                 intersect_addresses,
+	                                 intersect_routes,
+	                                 default_route_metric_penalty,
+	                                 TRUE);
 }
 
 /**
  * nm_ip4_config_intersect_alloc:
  * @a: a configuration
  * @b: another configuration
+ * @intersect_addresses: whether addresses should be intersected
+ * @intersect_routes: whether routes should be intersected
  * @default_route_metric_penalty: the default route metric penalty
  *
  * Computes the intersection between @a and @b and returns the result in a newly
@@ -1654,17 +1667,24 @@ nm_ip4_config_intersect (NMIP4Config *dst,
 NMIP4Config *
 nm_ip4_config_intersect_alloc (const NMIP4Config *a,
                                const NMIP4Config *b,
+                               gboolean intersect_addresses,
                                gboolean intersect_routes,
                                guint32 default_route_metric_penalty)
 {
 	NMIP4Config *a_copy;
 
 	if (_nm_ip4_config_intersect_helper ((NMIP4Config *) a, b,
+	                                     intersect_addresses,
 	                                     intersect_routes,
-	                                     default_route_metric_penalty, FALSE)) {
+	                                     default_route_metric_penalty,
+	                                     FALSE)) {
 		a_copy = nm_ip4_config_clone (a);
-		_nm_ip4_config_intersect_helper (a_copy, b, intersect_routes,
-		                                 default_route_metric_penalty, TRUE);
+		_nm_ip4_config_intersect_helper (a_copy,
+		                                 b,
+		                                 intersect_addresses,
+		                                 intersect_routes,
+		                                 default_route_metric_penalty,
+		                                 TRUE);
 		return a_copy;
 	} else
 		return NULL;
@@ -2039,9 +2059,11 @@ nm_ip_config_dump (const NMIPConfig *self,
 	}
 
 	for (i = 0; i < nm_ip_config_get_num_nameservers (self); i++) {
+		char buf[NM_UTILS_INET_ADDRSTRLEN];
+
 		ptr = nm_ip_config_get_nameserver (self, i);
 		nm_log (level, domain, NULL, NULL, " dns      : %s",
-		        nm_utils_inet_ntop (addr_family, ptr, NULL));
+		        nm_utils_inet_ntop (addr_family, ptr, buf));
 	}
 
 	for (i = 0; i < nm_ip_config_get_num_domains (self); i++)
@@ -2110,7 +2132,7 @@ nm_ip4_config_add_address (NMIP4Config *self, const NMPlatformIP4Address *new)
 {
 	g_return_if_fail (self);
 	g_return_if_fail (new);
-	g_return_if_fail (new->plen > 0 && new->plen <= 32);
+	g_return_if_fail (new->plen <= 32);
 	g_return_if_fail (NM_IP4_CONFIG_GET_PRIVATE (self)->ifindex > 0);
 
 	_add_address (self, NULL, new);
@@ -2150,7 +2172,7 @@ nm_ip4_config_get_first_address (const NMIP4Config *self)
 const NMPlatformIP4Address *
 _nmtst_ip4_config_get_address (const NMIP4Config *self, guint i)
 {
-	NMDedupMultiIter iter;
+	NMDedupMultiIter iter = { };
 	const NMPlatformIP4Address *a = NULL;
 	guint j;
 
@@ -2258,7 +2280,7 @@ _add_route (NMIP4Config *self,
  * nm_ip4_config_add_route:
  * @self: the #NMIP4Config
  * @new: the new route to add to @self
- * @out_obj_new: (allow-none): (out): the added route object. Must be unrefed
+ * @out_obj_new: (allow-none) (out): the added route object. Must be unrefed
  *   by caller.
  *
  * Adds the new route to @self.  If a route with the same basic properties
@@ -2898,7 +2920,7 @@ nm_ip4_config_nmpobj_remove (NMIP4Config *self,
 
 /*****************************************************************************/
 
-static inline void
+static void
 hash_u32 (GChecksum *sum, guint32 n)
 {
 	g_checksum_update (sum, (const guint8 *) &n, sizeof (n));
@@ -3024,6 +3046,7 @@ get_property (GObject *object, guint prop_id,
 	const NMPlatformIP4Route *route;
 	GVariantBuilder builder_data, builder_legacy;
 	guint i;
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
 
 	switch (prop_id) {
 	case PROP_IFINDEX:
@@ -3061,14 +3084,14 @@ get_property (GObject *object, guint prop_id,
 				g_variant_builder_init (&addr_builder, G_VARIANT_TYPE ("a{sv}"));
 				g_variant_builder_add (&addr_builder, "{sv}",
 				                       "address",
-				                       g_variant_new_string (nm_utils_inet4_ntop (address->address, NULL)));
+				                       g_variant_new_string (nm_utils_inet4_ntop (address->address, addr_str)));
 				g_variant_builder_add (&addr_builder, "{sv}",
 				                       "prefix",
 				                       g_variant_new_uint32 (address->plen));
 				if (address->peer_address != address->address) {
 					g_variant_builder_add (&addr_builder, "{sv}",
 					                       "peer",
-					                       g_variant_new_string (nm_utils_inet4_ntop (address->peer_address, NULL)));
+					                       g_variant_new_string (nm_utils_inet4_ntop (address->peer_address, addr_str)));
 				}
 
 				if (*address->label) {
@@ -3123,14 +3146,14 @@ out_addresses_cached:
 			g_variant_builder_init (&route_builder, G_VARIANT_TYPE ("a{sv}"));
 			g_variant_builder_add (&route_builder, "{sv}",
 			                       "dest",
-			                       g_variant_new_string (nm_utils_inet4_ntop (route->network, NULL)));
+			                       g_variant_new_string (nm_utils_inet4_ntop (route->network, addr_str)));
 			g_variant_builder_add (&route_builder, "{sv}",
 			                       "prefix",
 			                       g_variant_new_uint32 (route->plen));
 			if (route->gateway) {
 				g_variant_builder_add (&route_builder, "{sv}",
 				                       "next-hop",
-				                       g_variant_new_string (nm_utils_inet4_ntop (route->gateway, NULL)));
+				                       g_variant_new_string (nm_utils_inet4_ntop (route->gateway, addr_str)));
 			}
 			g_variant_builder_add (&route_builder, "{sv}",
 			                       "metric",
@@ -3172,9 +3195,8 @@ out_routes_cached:
 		break;
 	case PROP_GATEWAY:
 		if (priv->best_default_route) {
-			g_value_set_string (value,
-			                    nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway,
-			                                         NULL));
+			g_value_take_string (value,
+			                    nm_utils_inet4_ntop_dup (NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway));
 		} else
 			g_value_set_string (value, NULL);
 		break;
@@ -3183,7 +3205,6 @@ out_routes_cached:
 
 		for (i = 0; i < priv->nameservers->len; i++) {
 			GVariantBuilder nested_builder;
-			char addr_str[NM_UTILS_INET_ADDRSTRLEN];
 
 			nm_utils_inet4_ntop (g_array_index (priv->nameservers, in_addr_t, i),
 			                     addr_str);
@@ -3220,8 +3241,6 @@ out_routes_cached:
 	case PROP_WINS_SERVER_DATA:
 		g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("as"));
 		for (i = 0; i < priv->wins->len; i++) {
-			char addr_str[NM_UTILS_INET_ADDRSTRLEN];
-
 			g_variant_builder_add (&builder_data,
 			                       "s",
 			                       nm_utils_inet4_ntop (g_array_index (priv->wins, in_addr_t, i),
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index 07fb7f12..6b4bfd64 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -189,10 +189,12 @@ void nm_ip4_config_subtract (NMIP4Config *dst,
                              guint32 default_route_metric_penalty);
 void nm_ip4_config_intersect (NMIP4Config *dst,
                               const NMIP4Config *src,
+                              gboolean intersect_addresses,
                               gboolean intersect_routes,
                               guint32 default_route_metric_penalty);
 NMIP4Config *nm_ip4_config_intersect_alloc (const NMIP4Config *a,
                                             const NMIP4Config *b,
+                                            gboolean intersect_addresses,
                                             gboolean intersect_routes,
                                             guint32 default_route_metric_penalty);
 gboolean nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relevant_changes);
@@ -543,12 +545,14 @@ nm_ip_config_best_default_route_get (const NMIPConfig *self)
 static inline void
 nm_ip_config_intersect (NMIPConfig *dst,
                         const NMIPConfig *src,
+                        gboolean intersect_addresses,
                         gboolean intersect_routes,
                         guint32 default_route_metric_penalty)
 {
 	_NM_IP_CONFIG_DISPATCH_SET_OP (, dst, src,
 	                               nm_ip4_config_intersect,
 	                               nm_ip6_config_intersect,
+	                               intersect_addresses,
 	                               intersect_routes,
 	                               default_route_metric_penalty);
 }
@@ -591,6 +595,7 @@ nm_ip_config_replace (NMIPConfig *dst,
 static inline NMIPConfig *
 nm_ip_config_intersect_alloc (const NMIPConfig *a,
                               const NMIPConfig *b,
+                              gboolean intersect_addresses,
                               gboolean intersect_routes,
                               guint32 default_route_metric_penalty)
 {
@@ -598,6 +603,7 @@ nm_ip_config_intersect_alloc (const NMIPConfig *a,
 		nm_assert (NM_IS_IP4_CONFIG (b));
 		return (NMIPConfig *) nm_ip4_config_intersect_alloc ((const NMIP4Config *) a,
 		                                                     (const NMIP4Config *) b,
+		                                                     intersect_addresses,
 		                                                     intersect_routes,
 		                                                     default_route_metric_penalty);
 	} else {
@@ -605,6 +611,7 @@ nm_ip_config_intersect_alloc (const NMIPConfig *a,
 		nm_assert (NM_IS_IP6_CONFIG (b));
 		return (NMIPConfig *) nm_ip6_config_intersect_alloc ((const NMIP6Config *) a,
 		                                                     (const NMIP6Config *) b,
+		                                                     intersect_addresses,
 		                                                     intersect_routes,
 		                                                     default_route_metric_penalty);
 	}
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index 42240e69..99a9ff88 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -23,7 +23,6 @@
 
 #include "nm-ip6-config.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <resolv.h>
 #include <linux/rtnetlink.h>
@@ -485,6 +484,8 @@ nm_ip6_config_add_dependent_routes (NMIP6Config *self,
 
 		if (NM_FLAGS_HAS (my_addr->n_ifa_flags, IFA_F_NOPREFIXROUTE))
 			continue;
+		if (my_addr->plen == 0)
+			continue;
 
 		has_peer = !IN6_IS_ADDR_UNSPECIFIED (&my_addr->peer_address);
 
@@ -713,6 +714,7 @@ nm_ip6_config_create_setting (const NMIP6Config *self)
 	NMSettingIPConfig *s_ip6;
 	guint nnameservers, nsearches, noptions;
 	const char *method = NULL;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 	int i;
 	NMDedupMultiIter ipconf_iter;
 	const NMPlatformIP6Address *address;
@@ -765,7 +767,7 @@ nm_ip6_config_create_setting (const NMIP6Config *self)
 		g_object_set (s_ip6,
 		              NM_SETTING_IP_CONFIG_GATEWAY,
 		              nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (priv->best_default_route)->gateway,
-		                                   NULL),
+		                                   sbuf),
 		              NULL);
 	}
 
@@ -804,7 +806,7 @@ nm_ip6_config_create_setting (const NMIP6Config *self)
 	for (i = 0; i < nnameservers; i++) {
 		const struct in6_addr *nameserver = nm_ip6_config_get_nameserver (self, i);
 
-		nm_setting_ip_config_add_dns (s_ip6, nm_utils_inet6_ntop (nameserver, NULL));
+		nm_setting_ip_config_add_dns (s_ip6, nm_utils_inet6_ntop (nameserver, sbuf));
 	}
 	for (i = 0; i < nsearches; i++) {
 		const char *search = nm_ip6_config_get_search (self, i);
@@ -1084,6 +1086,7 @@ nm_ip6_config_subtract (NMIP6Config *dst,
 static gboolean
 _nm_ip6_config_intersect_helper (NMIP6Config *dst,
                                  const NMIP6Config *src,
+                                 gboolean intersect_addresses,
                                  gboolean intersect_routes,
                                  guint32 default_route_metric_penalty,
                                  gboolean update_dst)
@@ -1106,24 +1109,26 @@ _nm_ip6_config_intersect_helper (NMIP6Config *dst,
 		g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	changed = FALSE;
-	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, dst, &a) {
-		if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
-		                                     &src_priv->idx_ip6_addresses,
-		                                     NMP_OBJECT_UP_CAST (a)))
-			continue;
+	if (intersect_addresses) {
+		changed = FALSE;
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, dst, &a) {
+			if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
+			                                     &src_priv->idx_ip6_addresses,
+			                                     NMP_OBJECT_UP_CAST (a)))
+				continue;
 
-		if (!update_dst)
-			return TRUE;
+			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) {
-		_notify_addresses (dst);
-		result = TRUE;
+			if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
+			                                       ipconf_iter.current) != 1)
+				nm_assert_not_reached ();
+			changed = TRUE;
+		}
+		if (changed) {
+			_notify_addresses (dst);
+			result = TRUE;
+		}
 	}
 
 	/* ignore nameservers */
@@ -1191,6 +1196,8 @@ skip_routes:
  * nm_ip6_config_intersect:
  * @dst: a configuration to be updated
  * @src: another configuration
+ * @intersect_addresses: whether addresses should be intersected
+ * @intersect_routes: whether routes should be intersected
  * @default_route_metric_penalty: the default route metric penalty
  *
  * Computes the intersection between @src and @dst and updates @dst in place
@@ -1199,16 +1206,24 @@ skip_routes:
 void
 nm_ip6_config_intersect (NMIP6Config *dst,
                          const NMIP6Config *src,
+                         gboolean intersect_addresses,
                          gboolean intersect_routes,
                          guint32 default_route_metric_penalty)
 {
-	_nm_ip6_config_intersect_helper (dst, src, intersect_routes, default_route_metric_penalty, TRUE);
+	_nm_ip6_config_intersect_helper (dst,
+	                                 src,
+	                                 intersect_addresses,
+	                                 intersect_routes,
+	                                 default_route_metric_penalty,
+	                                 TRUE);
 }
 
 /**
  * nm_ip6_config_intersect_alloc:
  * @a: a configuration
  * @b: another configuration
+ * @intersect_addresses: whether addresses should be intersected
+ * @intersect_routes: whether routes should be intersected
  * @default_route_metric_penalty: the default route metric penalty
  *
  * Computes the intersection between @a and @b and returns the result in a newly
@@ -1223,17 +1238,25 @@ nm_ip6_config_intersect (NMIP6Config *dst,
 NMIP6Config *
 nm_ip6_config_intersect_alloc (const NMIP6Config *a,
                                const NMIP6Config *b,
+                               gboolean intersect_addresses,
                                gboolean intersect_routes,
                                guint32 default_route_metric_penalty)
 {
 	NMIP6Config *a_copy;
 
-	if (_nm_ip6_config_intersect_helper ((NMIP6Config *) a, b,
+	if (_nm_ip6_config_intersect_helper ((NMIP6Config *) a,
+	                                     b,
+	                                     intersect_addresses,
 	                                     intersect_routes,
-	                                     default_route_metric_penalty, FALSE)) {
+	                                     default_route_metric_penalty,
+	                                     FALSE)) {
 		a_copy = nm_ip6_config_clone (a);
-		_nm_ip6_config_intersect_helper (a_copy, b, intersect_routes,
-		                                 default_route_metric_penalty, TRUE);
+		_nm_ip6_config_intersect_helper (a_copy,
+		                                 b,
+		                                 intersect_addresses,
+		                                 intersect_routes,
+		                                 default_route_metric_penalty,
+		                                 TRUE);
 		return a_copy;
 	} else
 		return NULL;
@@ -1592,7 +1615,7 @@ nm_ip6_config_add_address (NMIP6Config *self, const NMPlatformIP6Address *new)
 {
 	g_return_if_fail (self);
 	g_return_if_fail (new);
-	g_return_if_fail (new->plen > 0 && new->plen <= 128);
+	g_return_if_fail (new->plen <= 128);
 	g_return_if_fail (NM_IP6_CONFIG_GET_PRIVATE (self)->ifindex > 0);
 
 	_add_address (self, NULL, new);
@@ -1922,7 +1945,7 @@ _add_route (NMIP6Config *self,
  * nm_ip6_config_add_route:
  * @self: the #NMIP6Config
  * @new: the new route to add to @self
- * @out_obj_new: (allow-none): (out): the added route object. Must be unrefed
+ * @out_obj_new: (allow-none) (out): the added route object. Must be unrefed
  *   by caller.
  *
  * Adds the new route to @self.  If a route with the same basic properties
@@ -2350,13 +2373,13 @@ nm_ip6_config_nmpobj_remove (NMIP6Config *self,
 
 /*****************************************************************************/
 
-static inline void
+static void
 hash_u32 (GChecksum *sum, guint32 n)
 {
 	g_checksum_update (sum, (const guint8 *) &n, sizeof (n));
 }
 
-static inline void
+static void
 hash_in6addr (GChecksum *sum, const struct in6_addr *a)
 {
 	if (a)
@@ -2473,6 +2496,7 @@ get_property (GObject *object, guint prop_id,
 	NMDedupMultiIter ipconf_iter;
 	const NMPlatformIP6Route *route;
 	GVariantBuilder builder_data, builder_legacy;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	switch (prop_id) {
 	case PROP_IFINDEX:
@@ -2509,7 +2533,7 @@ get_property (GObject *object, guint prop_id,
 				g_variant_builder_init (&addr_builder, G_VARIANT_TYPE ("a{sv}"));
 				g_variant_builder_add (&addr_builder, "{sv}",
 				                       "address",
-				                       g_variant_new_string (nm_utils_inet6_ntop (&address->address, NULL)));
+				                       g_variant_new_string (nm_utils_inet6_ntop (&address->address, sbuf)));
 				g_variant_builder_add (&addr_builder, "{sv}",
 				                       "prefix",
 				                       g_variant_new_uint32 (address->plen));
@@ -2517,7 +2541,7 @@ get_property (GObject *object, guint prop_id,
 				    && !IN6_ARE_ADDR_EQUAL (&address->peer_address, &address->address)) {
 					g_variant_builder_add (&addr_builder, "{sv}",
 					                       "peer",
-					                       g_variant_new_string (nm_utils_inet6_ntop (&address->peer_address, NULL)));
+					                       g_variant_new_string (nm_utils_inet6_ntop (&address->peer_address, sbuf)));
 				}
 
 				g_variant_builder_add (&builder_data, "a{sv}", &addr_builder);
@@ -2562,14 +2586,14 @@ out_addresses_cached:
 			g_variant_builder_init (&route_builder, G_VARIANT_TYPE ("a{sv}"));
 			g_variant_builder_add (&route_builder, "{sv}",
 			                       "dest",
-			                       g_variant_new_string (nm_utils_inet6_ntop (&route->network, NULL)));
+			                       g_variant_new_string (nm_utils_inet6_ntop (&route->network, sbuf)));
 			g_variant_builder_add (&route_builder, "{sv}",
 			                       "prefix",
 			                       g_variant_new_uint32 (route->plen));
 			if (!IN6_IS_ADDR_UNSPECIFIED (&route->gateway)) {
 				g_variant_builder_add (&route_builder, "{sv}",
 				                       "next-hop",
-				                       g_variant_new_string (nm_utils_inet6_ntop (&route->gateway, NULL)));
+				                       g_variant_new_string (nm_utils_inet6_ntop (&route->gateway, sbuf)));
 			}
 
 			g_variant_builder_add (&route_builder, "{sv}",
@@ -2607,9 +2631,8 @@ out_routes_cached:
 		break;
 	case PROP_GATEWAY:
 		if (priv->best_default_route) {
-			g_value_set_string (value,
-			                    nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (priv->best_default_route)->gateway,
-			                                         NULL));
+			g_value_take_string (value,
+			                     nm_utils_inet6_ntop_dup (&NMP_OBJECT_CAST_IP6_ROUTE (priv->best_default_route)->gateway));
 		} else
 			g_value_set_string (value, NULL);
 		break;
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index 862f237d..557e3796 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -130,10 +130,12 @@ void nm_ip6_config_subtract (NMIP6Config *dst,
                              guint32 default_route_metric_penalty);
 void nm_ip6_config_intersect (NMIP6Config *dst,
                               const NMIP6Config *src,
+                              gboolean intersect_addresses,
                               gboolean intersect_routes,
                               guint32 default_route_metric_penalty);
 NMIP6Config *nm_ip6_config_intersect_alloc (const NMIP6Config *a,
                                             const NMIP6Config *b,
+                                            gboolean intersect_addresses,
                                             gboolean intersect_routes,
                                             guint32 default_route_metric_penalty);
 gboolean nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboolean *relevant_changes);
diff --git a/src/nm-keep-alive.c b/src/nm-keep-alive.c
new file mode 100644
index 00000000..cfec138a
--- /dev/null
+++ b/src/nm-keep-alive.c
@@ -0,0 +1,526 @@
+/*
+ * NetworkManager -- Inhibition management
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-keep-alive.h"
+
+#include "settings/nm-settings-connection.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMKeepAlive,
+	PROP_ALIVE,
+);
+
+typedef struct {
+	GObject *owner;
+
+	NMSettingsConnection *connection;
+	GDBusConnection *dbus_connection;
+	char *dbus_client;
+
+	GCancellable *dbus_client_confirm_cancellable;
+	guint subscription_id;
+
+	bool armed:1;
+	bool disarmed:1;
+
+	bool alive:1;
+	bool dbus_client_confirmed:1;
+	bool dbus_client_watching:1;
+	bool connection_was_visible:1;
+} NMKeepAlivePrivate;
+
+struct _NMKeepAlive {
+	GObject parent;
+	NMKeepAlivePrivate _priv;
+};
+
+struct _NMKeepAliveClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMKeepAlive, nm_keep_alive, G_TYPE_OBJECT)
+
+#define NM_KEEP_ALIVE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMKeepAlive, NM_IS_KEEP_ALIVE)
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_CORE
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "keep-alive", __VA_ARGS__)
+
+/*****************************************************************************/
+
+static gboolean _is_alive_dbus_client (NMKeepAlive *self);
+static void cleanup_dbus_watch (NMKeepAlive *self);
+
+/*****************************************************************************/
+
+static gboolean
+_is_alive (NMKeepAlive *self)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+	nm_assert (!priv->disarmed);
+
+	if (!priv->armed) {
+		/* before arming, the instance is always alive. */
+		return TRUE;
+	}
+
+	if (priv->dbus_client_watching) {
+		if (_is_alive_dbus_client (self)) {
+			/* no matter what, the keep-alive is alive, because there is a D-Bus client
+			 * still around keeping it alive. */
+			return TRUE;
+		}
+		/* the D-Bus client is gone. The only other binding (below) for the connection's
+		 * visibility cannot keep the instance alive.
+		 *
+		 * As such, a D-Bus client watch is authoritative and overrules other conditions (that
+		 * we have so far). */
+		return FALSE;
+	}
+
+	if (   priv->connection
+	    && priv->connection_was_visible
+	    && !NM_FLAGS_HAS (nm_settings_connection_get_flags (priv->connection),
+	                      NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) {
+		/* note that we only declare the keep-alive as dead due to invisible
+		 * connection, if
+		 *    (1) we monitor a connection, obviously
+		 *    (2) the connection was visible earlier and is no longer. It was
+		 *        was invisible all the time, it does not suffice.
+		 */
+		return FALSE;
+	}
+
+	/* by default, the instance is alive. */
+	return TRUE;
+}
+
+static void
+_notify_alive (NMKeepAlive *self)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+	if (priv->disarmed) {
+		/* once disarmed, the alive state is frozen. */
+		return;
+	}
+
+	if (priv->alive == _is_alive (self))
+		return;
+	priv->alive = !priv->alive;
+	_LOGD ("instance is now %s", priv->alive ? "alive" : "dead");
+	_notify (self, PROP_ALIVE);
+}
+
+gboolean
+nm_keep_alive_is_alive (NMKeepAlive *self)
+{
+	return NM_KEEP_ALIVE_GET_PRIVATE (self)->alive;
+}
+
+/*****************************************************************************/
+
+static void
+connection_flags_changed (NMSettingsConnection *connection,
+                          NMKeepAlive          *self)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+	if (   !priv->connection_was_visible
+	    && NM_FLAGS_HAS (nm_settings_connection_get_flags (priv->connection),
+	                     NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) {
+		/* the profile was never visible but now it becomes visible.
+		 * Remember that.
+		 *
+		 * Before this happens (that is, if the device was invisible all along),
+		 * the keep alive instance is considered alive (w.r.t. watching the connection).
+		 *
+		 * The reason is to allow a user to manually activate an invisible profile and keep
+		 * it alive. At least, as long until the user logs out the first time (which is the
+		 * first time, the profiles changes from visible to invisible).
+		 *
+		 * Yes, that is odd. How to improve? */
+		priv->connection_was_visible = TRUE;
+	}
+	_notify_alive (self);
+}
+
+static void
+_set_settings_connection_watch_visible (NMKeepAlive *self,
+                                        NMSettingsConnection *connection,
+                                        gboolean emit_signal)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+	gs_unref_object NMSettingsConnection *old_connection = NULL;
+
+	if (priv->connection == connection)
+		return;
+
+	if (priv->connection) {
+		g_signal_handlers_disconnect_by_func (priv->connection,
+		                                      G_CALLBACK (connection_flags_changed),
+		                                      self);
+		old_connection = g_steal_pointer (&priv->connection);
+	}
+
+	if (   connection
+	    && !priv->disarmed) {
+		priv->connection = g_object_ref (connection);
+		priv->connection_was_visible = NM_FLAGS_HAS (nm_settings_connection_get_flags (priv->connection),
+		                                             NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE);
+		g_signal_connect (priv->connection,
+		                  NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
+		                  G_CALLBACK (connection_flags_changed),
+		                  self);
+	}
+
+	if (emit_signal)
+		_notify_alive (self);
+}
+
+void
+nm_keep_alive_set_settings_connection_watch_visible (NMKeepAlive         *self,
+                                                     NMSettingsConnection *connection)
+{
+	_set_settings_connection_watch_visible (self, connection, TRUE);
+}
+
+/*****************************************************************************/
+
+static void
+get_name_owner_cb (GObject *source_object,
+                   GAsyncResult *res,
+                   gpointer user_data)
+{
+	NMKeepAlive *self = user_data;
+	NMKeepAlivePrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *result = NULL;
+	const char *name_owner;
+
+	result = g_dbus_connection_call_finish ((GDBusConnection *) source_object,
+	                                        res,
+	                                        &error);
+	if (   !result
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	if (result) {
+		g_variant_get (result, "(&s)", &name_owner);
+
+		priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+		if (nm_streq (name_owner, priv->dbus_client)) {
+			/* all good, the name is confirmed. */
+			return;
+		}
+	}
+
+	_LOGD ("DBus client for keep alive is not on the bus");
+	cleanup_dbus_watch (self);
+	_notify_alive (self);
+}
+
+static gboolean
+_is_alive_dbus_client (NMKeepAlive *self)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+	if (!priv->dbus_client)
+		return FALSE;
+
+	if (!priv->dbus_client_confirmed) {
+		/* it's unconfirmed that the D-Bus client is really alive.
+		 * It looks like it is, but as we are claiming that to be
+		 * the case, issue an async GetNameOwner call to make sure. */
+		priv->dbus_client_confirmed = TRUE;
+		priv->dbus_client_confirm_cancellable = g_cancellable_new ();
+
+		g_dbus_connection_call (priv->dbus_connection,
+		                        "org.freedesktop.DBus",
+		                        "/org/freedesktop/DBus",
+		                        "org.freedesktop.DBus",
+		                        "GetNameOwner",
+		                        g_variant_new ("(s)", priv->dbus_client),
+		                        G_VARIANT_TYPE ("(s)"),
+		                        G_DBUS_CALL_FLAGS_NONE,
+		                        -1,
+		                        priv->dbus_client_confirm_cancellable,
+		                        get_name_owner_cb,
+		                        self);
+	}
+	return TRUE;
+}
+
+static void
+cleanup_dbus_watch (NMKeepAlive *self)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+	if (!priv->dbus_client)
+		return;
+
+	_LOGD ("Cleanup DBus client watch");
+
+	nm_clear_g_cancellable (&priv->dbus_client_confirm_cancellable);
+	nm_clear_g_free (&priv->dbus_client);
+	if (priv->dbus_connection) {
+		g_dbus_connection_signal_unsubscribe (priv->dbus_connection,
+		                                      priv->subscription_id);
+		g_clear_object (&priv->dbus_connection);
+	}
+}
+
+static void
+name_owner_changed_cb (GDBusConnection *connection,
+                       const char      *sender_name,
+                       const char      *object_path,
+                       const char      *interface_name,
+                       const char      *signal_name,
+                       GVariant        *parameters,
+                       gpointer         user_data)
+{
+	NMKeepAlive *self = NM_KEEP_ALIVE (user_data);
+	const char *old_owner;
+	const char *new_owner;
+
+	g_variant_get (parameters, "(&s&s&s)", NULL, &old_owner, &new_owner);
+
+	if (!nm_streq0 (new_owner, ""))
+		return;
+
+	_LOGD ("DBus client for keep alive disappeared from bus");
+	cleanup_dbus_watch (self);
+	_notify_alive (self);
+}
+
+void
+nm_keep_alive_set_dbus_client_watch (NMKeepAlive *self,
+                                     GDBusConnection *connection,
+                                     const char *client_address)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+	if (priv->disarmed)
+		return;
+
+	cleanup_dbus_watch (self);
+
+	if (client_address) {
+		_LOGD ("Registering dbus client watch for keep alive");
+
+		priv->dbus_client = g_strdup (client_address);
+		priv->dbus_client_watching = TRUE;
+		priv->dbus_client_confirmed = FALSE;
+		priv->dbus_connection = g_object_ref (connection);
+		priv->subscription_id = g_dbus_connection_signal_subscribe (connection,
+		                                                            "org.freedesktop.DBus",
+		                                                            "org.freedesktop.DBus",
+		                                                            "NameOwnerChanged",
+		                                                            "/org/freedesktop/DBus",
+		                                                            priv->dbus_client,
+		                                                            G_DBUS_SIGNAL_FLAGS_NONE,
+		                                                            name_owner_changed_cb,
+		                                                            self,
+		                                                            NULL);
+	} else
+		priv->dbus_client_watching = FALSE;
+
+	_notify_alive (self);
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_keep_alive_arm:
+ * @self: the #NMKeepAlive
+ *
+ * A #NMKeepAlive instance is unarmed by default. That means, it's
+ * alive and stays alive until being armed. Arming means, that the conditions
+ * start to be actively evaluated, that the alive state may change, and
+ * that property changed signals are emitted.
+ *
+ * The opposite is nm_keep_alive_disarm() which freezes the alive state
+ * for good. Once disarmed, the instance cannot be armed again. Arming an
+ * instance multiple times has no effect. Arming an already disarmed instance
+ * also has no effect. */
+void
+nm_keep_alive_arm (NMKeepAlive *self)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+	if (!priv->armed) {
+		priv->armed = TRUE;
+		_notify_alive (self);
+	}
+}
+
+/**
+ * nm_keep_alive_disarm:
+ * @self: the #NMKeepAlive instance
+ *
+ * Once the instance is disarmed, it will not change its alive state
+ * anymore and will not emit anymore property changed signals about
+ * alive state changed.
+ *
+ * As such, it will also free internal resources (since they no longer
+ * affect the externally visible state).
+ *
+ * Once disarmed, the instance is frozen and cannot change anymore.
+ */
+void
+nm_keep_alive_disarm (NMKeepAlive *self)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+	priv->disarmed = TRUE;
+
+	/* release internal data. */
+	_set_settings_connection_watch_visible (self, NULL, FALSE);
+	cleanup_dbus_watch (self);
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object,
+              guint prop_id,
+              GValue *value,
+              GParamSpec *pspec)
+{
+	NMKeepAlive *self = NM_KEEP_ALIVE (object);
+
+	switch (prop_id) {
+	case PROP_ALIVE:
+		g_value_set_boolean (value, nm_keep_alive_is_alive (self));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_keep_alive_get_owner:
+ * @self: the #NMKeepAlive
+ *
+ * Returns: the owner instance associated with this @self. This commonly
+ *   is set to be the target instance, which @self guards for being alive.
+ *   Returns a gpointer, but of course it's some GObject instance. */
+gpointer /* GObject * */
+nm_keep_alive_get_owner (NMKeepAlive *self)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+	nm_assert (!priv->owner || G_IS_OBJECT (priv->owner));
+
+	return priv->owner;
+}
+
+/**
+ * _nm_keep_alive_set_owner:
+ * @self: the #NMKeepAlive
+ * @owner: the owner to set or unset.
+ *
+ * Sets or unsets the owner instance. Think of the owner the target
+ * instance that is guarded by @self. It's the responsibility of the
+ * owner to set and properly unset this pointer. As the owner also
+ * controls the lifetime of the NMKeepAlive instance.
+ *
+ * This API is not to be called by everybody, but only the owner of
+ * @self.
+ */
+void
+_nm_keep_alive_set_owner (NMKeepAlive *self,
+                          GObject *owner)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+	nm_assert (!owner || G_IS_OBJECT (owner));
+
+	/* it's bad style to reset the owner object. You are supposed to
+	 * set it once, and clear it once. That's it. */
+	nm_assert (!owner || !priv->owner);
+
+	/* optimally, we would take a reference to @owner. But the
+	 * owner already owns a reference to the keep-alive, so we cannot
+	 * just own a reference back.
+	 *
+	 * We could register a weak-pointer here. But instead, declare that
+	 * owner is required to set itself as owner when creating the
+	 * keep-alive instance, and unset itself when it lets go of the
+	 * keep-alive instance (at latest, when the owner itself gets destroyed).
+	 */
+	priv->owner = owner;
+}
+
+/*****************************************************************************/
+
+static void
+nm_keep_alive_init (NMKeepAlive *self)
+{
+	NMKeepAlivePrivate *priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+
+	priv->alive = TRUE;
+
+	nm_assert (priv->alive == _is_alive (self));
+}
+
+NMKeepAlive *
+nm_keep_alive_new (void)
+{
+	return g_object_new (NM_TYPE_KEEP_ALIVE, NULL);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMKeepAlive *self = NM_KEEP_ALIVE (object);
+
+	nm_assert (!NM_KEEP_ALIVE_GET_PRIVATE (self)->owner);
+
+	/* disarm also happens to free all resources. */
+	nm_keep_alive_disarm (self);
+}
+
+static void
+nm_keep_alive_class_init (NMKeepAliveClass *keep_alive_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (keep_alive_class);
+
+	object_class->get_property = get_property;
+	object_class->dispose = dispose;
+
+	obj_properties[PROP_ALIVE] =
+	    g_param_spec_string (NM_KEEP_ALIVE_ALIVE, "", "",
+	                         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-keep-alive.h b/src/nm-keep-alive.h
new file mode 100644
index 00000000..160b2adb
--- /dev/null
+++ b/src/nm-keep-alive.h
@@ -0,0 +1,59 @@
+/*
+ * NetworkManager -- Inhibition management
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_KEEP_ALIVE_H__
+#define __NETWORKMANAGER_KEEP_ALIVE_H__
+
+#define NM_TYPE_KEEP_ALIVE         (nm_keep_alive_get_type ())
+#define NM_KEEP_ALIVE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), NM_TYPE_KEEP_ALIVE, NMKeepAlive))
+#define NM_KEEP_ALIVE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), NM_TYPE_KEEP_ALIVE, NMKeepAliveClass))
+#define NM_KEEP_ALIVE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NM_TYPE_KEEP_ALIVE, NMKeepAliveClass))
+#define NM_IS_KEEP_ALIVE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), NM_TYPE_KEEP_ALIVE))
+#define NM_IS_KEEP_ALIVE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), NM_TYPE_KEEP_ALIVE))
+
+
+#define NM_KEEP_ALIVE_ALIVE           "alive"
+
+typedef struct _NMKeepAliveClass NMKeepAliveClass;
+
+GType nm_keep_alive_get_type (void) G_GNUC_CONST;
+
+NMKeepAlive* nm_keep_alive_new (void);
+
+gboolean nm_keep_alive_is_alive (NMKeepAlive *self);
+
+void nm_keep_alive_arm (NMKeepAlive *self);
+void nm_keep_alive_disarm (NMKeepAlive *self);
+
+void nm_keep_alive_set_settings_connection_watch_visible (NMKeepAlive         *self,
+                                                          NMSettingsConnection *connection);
+
+void nm_keep_alive_set_dbus_client_watch (NMKeepAlive      *self,
+                                          GDBusConnection  *connection,
+                                          const char       *client_address);
+
+gpointer /* GObject * */ nm_keep_alive_get_owner (NMKeepAlive *self);
+
+/* _nm_keep_alive_set_owner() is reserved for the owner to set/unset itself. */
+void                     _nm_keep_alive_set_owner (NMKeepAlive *self,
+                                                   GObject *owner);
+
+#endif /* __NETWORKMANAGER_KEEP_ALIVE_H__ */
diff --git a/src/nm-logging.c b/src/nm-logging.c
index 9e7d3892..1ee94645 100644
--- a/src/nm-logging.c
+++ b/src/nm-logging.c
@@ -21,43 +21,69 @@
 
 #include "nm-default.h"
 
+#include "nm-logging.h"
+
 #include <dlfcn.h>
 #include <syslog.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include <errno.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <strings.h>
-#include <string.h>
 
 #if SYSTEMD_JOURNAL
 #define SD_JOURNAL_SUPPRESS_LOCATION
 #include <systemd/sd-journal.h>
 #endif
 
+#include "nm-utils/nm-time-utils.h"
 #include "nm-errors.h"
-#include "nm-core-utils.h"
-
-/* often we have some static string where we need to know the maximum length.
- * _MAX_LEN() returns @max but adds a debugging assertion that @str is indeed
- * shorter then @mac. */
-#define _MAX_LEN(max, str) \
-	({ \
-		const char *const _str = (str); \
-		\
-		nm_assert (_str && strlen (str) < (max)); \
-		(max); \
-	})
 
-void (*_nm_logging_clear_platform_logging_cache) (void);
+/*****************************************************************************/
 
-static void
-nm_log_handler (const char *log_domain,
-                GLogLevelFlags level,
-                const char *message,
-                gpointer ignored);
+/* Notes about thread-safety:
+ *
+ * NetworkManager generally is single-threaded and uses a (GLib) mainloop.
+ * However, nm-logging is in parts thread-safe. That means:
+ *
+ * - functions that configure logging (nm_logging_init(), nm_logging_setup()) and
+ *   most other functions MUST be called only from the main-thread. These functions
+ *   are expected to be called infrequently, so they may or may not use a mutex
+ *   (but the overhead is negligible here).
+ *
+ * - functions that do the actual logging logging (nm_log(), nm_logging_enabled()) are
+ *   thread-safe and may be used from multiple threads.
+ *    - When called from the not-main-thread, @mt_require_locking must be set to %TRUE.
+ *      In this case, a Mutex will be used for accessing the global state.
+ *    - When called from the main-thread, they may optionally pass @mt_require_locking %FALSE.
+ *      This avoids extra locking and is in particular interesting for nm_logging_enabled(),
+ *      which is expected to be called frequently and from the main-thread.
+ *
+ * Note that the logging macros honor %NM_THREAD_SAFE_ON_MAIN_THREAD define, to automatically
+ * set @mt_require_locking. That means, by default %NM_THREAD_SAFE_ON_MAIN_THREAD is "1",
+ * and code that only runs on the main-thread (which is the majority), can get away
+ * without locking.
+ */
+
+/*****************************************************************************/
+
+/* We have more then 32 logging domains. Assert that it compiles to a 64 bit sized enum */
+G_STATIC_ASSERT (sizeof (NMLogDomain) >= sizeof (guint64));
+
+/* Combined domains */
+#define LOGD_ALL_STRING     "ALL"
+#define LOGD_DEFAULT_STRING "DEFAULT"
+#define LOGD_DHCP_STRING    "DHCP"
+#define LOGD_IP_STRING      "IP"
+
+/*****************************************************************************/
+
+typedef enum {
+	LOG_BACKEND_GLIB,
+	LOG_BACKEND_SYSLOG,
+	LOG_BACKEND_JOURNAL,
+} LogBackend;
 
 typedef struct {
 	NMLogDomain num;
@@ -80,6 +106,52 @@ typedef struct {
 	GLogLevelFlags g_log_level;
 } LogLevelDesc;
 
+typedef struct {
+	char *logging_domains_to_string;
+} GlobalMain;
+
+typedef struct {
+	NMLogLevel log_level;
+	bool uses_syslog:1;
+	bool init_pre_done:1;
+	bool init_done:1;
+	bool debug_stderr:1;
+	const char *prefix;
+	const char *syslog_identifier;
+
+	/* before we setup syslog (during start), the backend defaults to GLIB, meaning:
+	 * we use g_log() for all logging. At that point, the application is not yet supposed
+	 * to do any logging and doing so indicates a bug.
+	 *
+	 * Afterwards, the backend is either SYSLOG or JOURNAL. From that point, also
+	 * g_log() is redirected to this backend via a logging handler. */
+	LogBackend log_backend;
+} Global;
+
+/*****************************************************************************/
+
+G_LOCK_DEFINE_STATIC (log);
+
+/* This data must only be accessed from the main-thread (and as
+ * such does not need any lock). */
+static GlobalMain gl_main = { };
+
+static union {
+	/* a union with an immutable and a mutable alias for the Global.
+	 * Since nm-logging must be thread-safe, we must take care at which
+	 * places we only read value ("imm") and where we modify them ("mut"). */
+	Global       mut;
+	const Global imm;
+} gl = {
+	.imm = {
+		/* nm_logging_setup ("INFO", LOGD_DEFAULT_STRING, NULL, NULL); */
+		.log_level = LOGL_INFO,
+		.log_backend = LOG_BACKEND_GLIB,
+		.syslog_identifier = "SYSLOG_IDENTIFIER="G_LOG_DOMAIN,
+		.prefix = "",
+	},
+};
+
 NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL] = {
 	/* nm_logging_setup ("INFO", LOGD_DEFAULT_STRING, NULL, NULL);
 	 *
@@ -90,102 +162,65 @@ NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL] = {
 	[LOGL_ERR]  = LOGD_DEFAULT,
 };
 
-static struct Global {
-	NMLogLevel log_level;
-	bool uses_syslog:1;
-	bool syslog_identifier_initialized:1;
-	bool debug_stderr:1;
-	const char *prefix;
-	const char *syslog_identifier;
-	enum {
-		/* before we setup syslog (during start), the backend defaults to GLIB, meaning:
-		 * we use g_log() for all logging. At that point, the application is not yet supposed
-		 * to do any logging and doing so indicates a bug.
-		 *
-		 * Afterwards, the backend is either SYSLOG or JOURNAL. From that point, also
-		 * g_log() is redirected to this backend via a logging handler. */
-		LOG_BACKEND_GLIB,
-		LOG_BACKEND_SYSLOG,
-		LOG_BACKEND_JOURNAL,
-	} log_backend;
-	char *logging_domains_to_string;
-	const LogLevelDesc level_desc[_LOGL_N];
-
-#define _DOMAIN_DESC_LEN 39
-	/* Would be nice to use C99 flexible array member here,
-	 * but that feature doesn't seem well supported. */
-	const LogDesc domain_desc[_DOMAIN_DESC_LEN];
-} global = {
-	/* nm_logging_setup ("INFO", LOGD_DEFAULT_STRING, NULL, NULL); */
-	.log_level = LOGL_INFO,
-	.log_backend = LOG_BACKEND_GLIB,
-	.syslog_identifier = "SYSLOG_IDENTIFIER="G_LOG_DOMAIN,
-	.prefix = "",
-	.level_desc = {
-		[LOGL_TRACE] = { "TRACE", "<trace>", LOG_DEBUG,   G_LOG_LEVEL_DEBUG,   },
-		[LOGL_DEBUG] = { "DEBUG", "<debug>", LOG_DEBUG,   G_LOG_LEVEL_DEBUG,   },
-		[LOGL_INFO]  = { "INFO",  "<info>",  LOG_INFO,    G_LOG_LEVEL_INFO,    },
-		[LOGL_WARN]  = { "WARN",  "<warn>",  LOG_WARNING, G_LOG_LEVEL_MESSAGE, },
-		[LOGL_ERR]   = { "ERR",   "<error>", LOG_ERR,     G_LOG_LEVEL_MESSAGE, },
-		[_LOGL_OFF]  = { "OFF",   NULL,      0,           0,                   },
-		[_LOGL_KEEP] = { "KEEP",  NULL,      0,           0,                   },
-	},
-	.domain_desc = {
-		{ LOGD_PLATFORM,  "PLATFORM" },
-		{ LOGD_RFKILL,    "RFKILL" },
-		{ LOGD_ETHER,     "ETHER" },
-		{ LOGD_WIFI,      "WIFI" },
-		{ LOGD_BT,        "BT" },
-		{ LOGD_MB,        "MB" },
-		{ LOGD_DHCP4,     "DHCP4" },
-		{ LOGD_DHCP6,     "DHCP6" },
-		{ LOGD_PPP,       "PPP" },
-		{ LOGD_WIFI_SCAN, "WIFI_SCAN" },
-		{ LOGD_IP4,       "IP4" },
-		{ LOGD_IP6,       "IP6" },
-		{ LOGD_AUTOIP4,   "AUTOIP4" },
-		{ LOGD_DNS,       "DNS" },
-		{ LOGD_VPN,       "VPN" },
-		{ LOGD_SHARING,   "SHARING" },
-		{ LOGD_SUPPLICANT,"SUPPLICANT" },
-		{ LOGD_AGENTS,    "AGENTS" },
-		{ LOGD_SETTINGS,  "SETTINGS" },
-		{ LOGD_SUSPEND,   "SUSPEND" },
-		{ LOGD_CORE,      "CORE" },
-		{ LOGD_DEVICE,    "DEVICE" },
-		{ LOGD_OLPC,      "OLPC" },
-		{ LOGD_INFINIBAND,"INFINIBAND" },
-		{ LOGD_FIREWALL,  "FIREWALL" },
-		{ LOGD_ADSL,      "ADSL" },
-		{ LOGD_BOND,      "BOND" },
-		{ LOGD_VLAN,      "VLAN" },
-		{ LOGD_BRIDGE,    "BRIDGE" },
-		{ LOGD_DBUS_PROPS,"DBUS_PROPS" },
-		{ LOGD_TEAM,      "TEAM" },
-		{ LOGD_CONCHECK,  "CONCHECK" },
-		{ LOGD_DCB,       "DCB" },
-		{ LOGD_DISPATCH,  "DISPATCH" },
-		{ LOGD_AUDIT,     "AUDIT" },
-		{ LOGD_SYSTEMD,   "SYSTEMD" },
-		{ LOGD_VPN_PLUGIN,"VPN_PLUGIN" },
-		{ LOGD_PROXY,     "PROXY" },
-		{ 0, NULL }
-		/* keep _DOMAIN_DESC_LEN in sync */
-	},
-};
+/*****************************************************************************/
 
-/* We have more then 32 logging domains. Assert that it compiles to a 64 bit sized enum */
-G_STATIC_ASSERT (sizeof (NMLogDomain) >= sizeof (guint64));
+static const LogLevelDesc level_desc[_LOGL_N] = {
+	[LOGL_TRACE] = { "TRACE", "<trace>", LOG_DEBUG,   G_LOG_LEVEL_DEBUG,   },
+	[LOGL_DEBUG] = { "DEBUG", "<debug>", LOG_DEBUG,   G_LOG_LEVEL_DEBUG,   },
+	[LOGL_INFO]  = { "INFO",  "<info>",  LOG_INFO,    G_LOG_LEVEL_INFO,    },
+	[LOGL_WARN]  = { "WARN",  "<warn>",  LOG_WARNING, G_LOG_LEVEL_MESSAGE, },
+	[LOGL_ERR]   = { "ERR",   "<error>", LOG_ERR,     G_LOG_LEVEL_MESSAGE, },
+	[_LOGL_OFF]  = { "OFF",   NULL,      0,           0,                   },
+	[_LOGL_KEEP] = { "KEEP",  NULL,      0,           0,                   },
+};
 
-/* Combined domains */
-#define LOGD_ALL_STRING     "ALL"
-#define LOGD_DEFAULT_STRING "DEFAULT"
-#define LOGD_DHCP_STRING    "DHCP"
-#define LOGD_IP_STRING      "IP"
+static const LogDesc domain_desc[] = {
+	{ LOGD_PLATFORM,  "PLATFORM" },
+	{ LOGD_RFKILL,    "RFKILL" },
+	{ LOGD_ETHER,     "ETHER" },
+	{ LOGD_WIFI,      "WIFI" },
+	{ LOGD_BT,        "BT" },
+	{ LOGD_MB,        "MB" },
+	{ LOGD_DHCP4,     "DHCP4" },
+	{ LOGD_DHCP6,     "DHCP6" },
+	{ LOGD_PPP,       "PPP" },
+	{ LOGD_WIFI_SCAN, "WIFI_SCAN" },
+	{ LOGD_IP4,       "IP4" },
+	{ LOGD_IP6,       "IP6" },
+	{ LOGD_AUTOIP4,   "AUTOIP4" },
+	{ LOGD_DNS,       "DNS" },
+	{ LOGD_VPN,       "VPN" },
+	{ LOGD_SHARING,   "SHARING" },
+	{ LOGD_SUPPLICANT,"SUPPLICANT" },
+	{ LOGD_AGENTS,    "AGENTS" },
+	{ LOGD_SETTINGS,  "SETTINGS" },
+	{ LOGD_SUSPEND,   "SUSPEND" },
+	{ LOGD_CORE,      "CORE" },
+	{ LOGD_DEVICE,    "DEVICE" },
+	{ LOGD_OLPC,      "OLPC" },
+	{ LOGD_INFINIBAND,"INFINIBAND" },
+	{ LOGD_FIREWALL,  "FIREWALL" },
+	{ LOGD_ADSL,      "ADSL" },
+	{ LOGD_BOND,      "BOND" },
+	{ LOGD_VLAN,      "VLAN" },
+	{ LOGD_BRIDGE,    "BRIDGE" },
+	{ LOGD_DBUS_PROPS,"DBUS_PROPS" },
+	{ LOGD_TEAM,      "TEAM" },
+	{ LOGD_CONCHECK,  "CONCHECK" },
+	{ LOGD_DCB,       "DCB" },
+	{ LOGD_DISPATCH,  "DISPATCH" },
+	{ LOGD_AUDIT,     "AUDIT" },
+	{ LOGD_SYSTEMD,   "SYSTEMD" },
+	{ LOGD_VPN_PLUGIN,"VPN_PLUGIN" },
+	{ LOGD_PROXY,     "PROXY" },
+	{ 0 },
+};
 
 /*****************************************************************************/
 
-static char *_domains_to_string (gboolean include_level_override);
+static char *_domains_to_string (gboolean include_level_override,
+                                 NMLogLevel log_level,
+                                 const NMLogDomain log_state[static _LOGL_N_REAL]);
 
 /*****************************************************************************/
 
@@ -211,48 +246,30 @@ _syslog_identifier_valid_domain (const char *domain)
 }
 
 static gboolean
-_syslog_identifier_assert (const struct Global *gl)
+_syslog_identifier_assert (const char *syslog_identifier)
 {
-	g_assert (gl);
-	g_assert (gl->syslog_identifier);
-	g_assert (g_str_has_prefix (gl->syslog_identifier, "SYSLOG_IDENTIFIER="));
-	g_assert (_syslog_identifier_valid_domain (&gl->syslog_identifier[NM_STRLEN ("SYSLOG_IDENTIFIER=")]));
+	g_assert (syslog_identifier);
+	g_assert (g_str_has_prefix (syslog_identifier, "SYSLOG_IDENTIFIER="));
+	g_assert (_syslog_identifier_valid_domain (&syslog_identifier[NM_STRLEN ("SYSLOG_IDENTIFIER=")]));
 	return TRUE;
 }
 
 static const char *
-syslog_identifier_domain (const struct Global *gl)
+syslog_identifier_domain (const char *syslog_identifier)
 {
-	nm_assert (_syslog_identifier_assert (gl));
-	return &gl->syslog_identifier[NM_STRLEN ("SYSLOG_IDENTIFIER=")];
+	nm_assert (_syslog_identifier_assert (syslog_identifier));
+	return &syslog_identifier[NM_STRLEN ("SYSLOG_IDENTIFIER=")];
 }
 
 #if SYSTEMD_JOURNAL
 static const char *
-syslog_identifier_full (const struct Global *gl)
+syslog_identifier_full (const char *syslog_identifier)
 {
-	nm_assert (_syslog_identifier_assert (gl));
-	return &gl->syslog_identifier[0];
+	nm_assert (_syslog_identifier_assert (syslog_identifier));
+	return &syslog_identifier[0];
 }
 #endif
 
-void
-nm_logging_set_syslog_identifier (const char *domain)
-{
-	if (global.log_backend != LOG_BACKEND_GLIB)
-		g_return_if_reached ();
-
-	if (!_syslog_identifier_valid_domain (domain))
-		g_return_if_reached ();
-
-	if (global.syslog_identifier_initialized)
-		g_return_if_reached ();
-
-	global.syslog_identifier_initialized = TRUE;
-	global.syslog_identifier = g_strdup_printf ("SYSLOG_IDENTIFIER=%s", domain);
-	nm_assert (_syslog_identifier_assert (&global));
-}
-
 /*****************************************************************************/
 
 static gboolean
@@ -262,8 +279,8 @@ match_log_level (const char  *level,
 {
 	int i;
 
-	for (i = 0; i < G_N_ELEMENTS (global.level_desc); i++) {
-		if (!g_ascii_strcasecmp (global.level_desc[i].name, level)) {
+	for (i = 0; i < G_N_ELEMENTS (level_desc); i++) {
+		if (!g_ascii_strcasecmp (level_desc[i].name, level)) {
 			*out_level = i;
 			return TRUE;
 		}
@@ -281,31 +298,42 @@ nm_logging_setup (const char  *level,
                   GError     **error)
 {
 	GString *unrecognized = NULL;
-	NMLogDomain new_logging[G_N_ELEMENTS (_nm_logging_enabled_state)];
-	NMLogLevel new_log_level = global.log_level;
+	NMLogDomain cur_log_state[_LOGL_N_REAL];
+	NMLogDomain new_log_state[_LOGL_N_REAL];
+	NMLogLevel cur_log_level;
+	NMLogLevel new_log_level;
 	char **tmp, **iter;
 	int i;
 	gboolean had_platform_debug;
 	gs_free char *domains_free = NULL;
 
+	NM_ASSERT_ON_MAIN_THREAD ();
+
 	g_return_val_if_fail (!bad_domains || !*bad_domains, FALSE);
 	g_return_val_if_fail (!error || !*error, FALSE);
 
-	/* domains */
-	if (!domains || !*domains)
-		domains = (domains_free = _domains_to_string (FALSE));
+	cur_log_level = gl.imm.log_level;
+	memcpy (cur_log_state, _nm_logging_enabled_state, sizeof (cur_log_state));
+
+	new_log_level = cur_log_level;
+
+	if (!domains || !*domains) {
+		domains_free = _domains_to_string (FALSE,
+		                                   cur_log_level,
+		                                   cur_log_state);
+		domains = domains_free;
+	}
 
-	for (i = 0; i < G_N_ELEMENTS (new_logging); i++)
-		new_logging[i] = 0;
+	for (i = 0; i < G_N_ELEMENTS (new_log_state); i++)
+		new_log_state[i] = 0;
 
-	/* levels */
 	if (level && *level) {
 		if (!match_log_level (level, &new_log_level, error))
 			return FALSE;
 		if (new_log_level == _LOGL_KEEP) {
-			new_log_level = global.log_level;
-			for (i = 0; i < G_N_ELEMENTS (new_logging); i++)
-				new_logging[i] = _nm_logging_enabled_state[i];
+			new_log_level = cur_log_level;
+			for (i = 0; i < G_N_ELEMENTS (new_log_state); i++)
+				new_log_state[i] = cur_log_state[i];
 		}
 	}
 
@@ -362,7 +390,7 @@ nm_logging_setup (const char  *level,
 			continue;
 
 		else {
-			for (diter = &global.domain_desc[0]; diter->name; diter++) {
+			for (diter = &domain_desc[0]; diter->name; diter++) {
 				if (!g_ascii_strcasecmp (diter->name, *iter)) {
 					bits = diter->num;
 					break;
@@ -386,34 +414,37 @@ nm_logging_setup (const char  *level,
 		}
 
 		if (domain_log_level == _LOGL_KEEP) {
-			for (i = 0; i < G_N_ELEMENTS (new_logging); i++)
-				new_logging[i] = (new_logging[i] & ~bits) | (_nm_logging_enabled_state[i] & bits);
+			for (i = 0; i < G_N_ELEMENTS (new_log_state); i++)
+				new_log_state[i] = (new_log_state[i] & ~bits) | (cur_log_state[i] & bits);
 		} else {
-			for (i = 0; i < G_N_ELEMENTS (new_logging); i++) {
+			for (i = 0; i < G_N_ELEMENTS (new_log_state); i++) {
 				if (i < domain_log_level)
-					new_logging[i] &= ~bits;
+					new_log_state[i] &= ~bits;
 				else {
-					new_logging[i] |= bits;
+					new_log_state[i] |= bits;
 					if (   (protect & bits)
 					    && i < LOGL_INFO)
-						new_logging[i] &= ~protect;
+						new_log_state[i] &= ~protect;
 				}
 			}
 		}
 	}
 	g_strfreev (tmp);
 
-	g_clear_pointer (&global.logging_domains_to_string, g_free);
+	g_clear_pointer (&gl_main.logging_domains_to_string, g_free);
 
-	had_platform_debug = nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM);
+	had_platform_debug = _nm_logging_enabled_lockfree (LOGL_DEBUG, LOGD_PLATFORM);
 
-	global.log_level = new_log_level;
-	for (i = 0; i < G_N_ELEMENTS (new_logging); i++)
-		_nm_logging_enabled_state[i] = new_logging[i];
+	G_LOCK (log);
+
+	gl.mut.log_level = new_log_level;
+	for (i = 0; i < G_N_ELEMENTS (new_log_state); i++)
+		_nm_logging_enabled_state[i] = new_log_state[i];
+
+	G_UNLOCK (log);
 
 	if (   had_platform_debug
-	    && _nm_logging_clear_platform_logging_cache
-	    && !nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
+	    && !_nm_logging_enabled_lockfree (LOGL_DEBUG, LOGD_PLATFORM)) {
 		/* when debug logging is enabled, platform will cache all access to
 		 * sysctl. When the user disables debug-logging, we want to clear that
 		 * cache right away. */
@@ -429,7 +460,9 @@ nm_logging_setup (const char  *level,
 const char *
 nm_logging_level_to_string (void)
 {
-	return global.level_desc[global.log_level].name;
+	NM_ASSERT_ON_MAIN_THREAD ();
+
+	return level_desc[gl.imm.log_level].name;
 }
 
 const char *
@@ -441,10 +474,10 @@ nm_logging_all_levels_to_string (void)
 		int i;
 
 		str = g_string_new (NULL);
-		for (i = 0; i < G_N_ELEMENTS (global.level_desc); i++) {
+		for (i = 0; i < G_N_ELEMENTS (level_desc); i++) {
 			if (str->len)
 				g_string_append_c (str, ',');
-			g_string_append (str, global.level_desc[i].name);
+			g_string_append (str, level_desc[i].name);
 		}
 	}
 
@@ -454,27 +487,34 @@ nm_logging_all_levels_to_string (void)
 const char *
 nm_logging_domains_to_string (void)
 {
-	if (G_UNLIKELY (!global.logging_domains_to_string))
-		global.logging_domains_to_string = _domains_to_string (TRUE);
+	NM_ASSERT_ON_MAIN_THREAD ();
+
+	if (G_UNLIKELY (!gl_main.logging_domains_to_string)) {
+		gl_main.logging_domains_to_string = _domains_to_string (TRUE,
+		                                                        gl.imm.log_level,
+		                                                        _nm_logging_enabled_state);
+	}
 
-	return global.logging_domains_to_string;
+	return gl_main.logging_domains_to_string;
 }
 
 static char *
-_domains_to_string (gboolean include_level_override)
+_domains_to_string (gboolean include_level_override,
+                    NMLogLevel log_level,
+                    const NMLogDomain log_state[static _LOGL_N_REAL])
 {
 	const LogDesc *diter;
 	GString *str;
 	int i;
 
-	/* We don't just return g_strdup (global.log_domains) because we want to expand
-	 * "DEFAULT" and "ALL".
+	/* We don't just return g_strdup() the logging domains that were set during
+	 * nm_logging_setup(), because we want to expand "DEFAULT" and "ALL".
 	 */
 
 	str = g_string_sized_new (75);
-	for (diter = &global.domain_desc[0]; diter->name; diter++) {
+	for (diter = &domain_desc[0]; diter->name; diter++) {
 		/* If it's set for any lower level, it will also be set for LOGL_ERR */
-		if (!(diter->num & _nm_logging_enabled_state[LOGL_ERR]))
+		if (!(diter->num & log_state[LOGL_ERR]))
 			continue;
 
 		if (str->len)
@@ -485,17 +525,17 @@ _domains_to_string (gboolean include_level_override)
 			continue;
 
 		/* Check if it's logging at a lower level than the default. */
-		for (i = 0; i < global.log_level; i++) {
-			if (diter->num & _nm_logging_enabled_state[i]) {
-				g_string_append_printf (str, ":%s", global.level_desc[i].name);
+		for (i = 0; i < log_level; i++) {
+			if (diter->num & log_state[i]) {
+				g_string_append_printf (str, ":%s", level_desc[i].name);
 				break;
 			}
 		}
 		/* Check if it's logging at a higher level than the default. */
-		if (!(diter->num & _nm_logging_enabled_state[global.log_level])) {
-			for (i = global.log_level + 1; i < G_N_ELEMENTS (_nm_logging_enabled_state); i++) {
-				if (diter->num & _nm_logging_enabled_state[i]) {
-					g_string_append_printf (str, ":%s", global.level_desc[i].name);
+		if (!(diter->num & log_state[log_level])) {
+			for (i = log_level + 1; i < _LOGL_N_REAL; i++) {
+				if (diter->num & log_state[i]) {
+					g_string_append_printf (str, ":%s", level_desc[i].name);
 					break;
 				}
 			}
@@ -513,7 +553,7 @@ nm_logging_all_domains_to_string (void)
 		const LogDesc *diter;
 
 		str = g_string_new (LOGD_DEFAULT_STRING);
-		for (diter = &global.domain_desc[0]; diter->name; diter++) {
+		for (diter = &domain_desc[0]; diter->name; diter++) {
 			g_string_append_c (str, ',');
 			g_string_append (str, diter->name);
 			if (diter->num == LOGD_DHCP6)
@@ -543,11 +583,31 @@ nm_logging_get_level (NMLogDomain domain)
 
 	G_STATIC_ASSERT (LOGL_TRACE == 0);
 	while (   sl > LOGL_TRACE
-	       && nm_logging_enabled (sl - 1, domain))
+	       && _nm_logging_enabled_lockfree (sl - 1, domain))
 		sl--;
 	return sl;
 }
 
+gboolean
+_nm_logging_enabled_locking (NMLogLevel level,
+                             NMLogDomain domain)
+{
+	gboolean v;
+
+	G_LOCK (log);
+	v = _nm_logging_enabled_lockfree (level, domain);
+	G_UNLOCK (log);
+	return v;
+}
+
+gboolean
+_nm_log_enabled_impl (gboolean mt_require_locking,
+                      NMLogLevel level,
+                      NMLogDomain domain)
+{
+	return nm_logging_enabled_mt (mt_require_locking, level, domain);
+}
+
 #if SYSTEMD_JOURNAL
 static void
 _iovec_set (struct iovec *iov, const void *str, gsize len)
@@ -583,20 +643,32 @@ _iovec_set_format (struct iovec *iov, gpointer *iov_free, const char *format, ..
 		char *const _buf = g_alloca (_size); \
 		int _len; \
 		\
+		G_STATIC_ASSERT_EXPR ((reserve_extra) + (NM_STRLEN (format) + 3) <= 96); \
+		\
 		_len = g_snprintf (_buf, _size, ""format"", ##__VA_ARGS__);\
 		\
 		nm_assert (_len >= 0); \
-		nm_assert (_len <= _size); \
+		nm_assert (_len < _size); \
 		nm_assert (_len == strlen (_buf)); \
 		\
 		_iovec_set ((iov), _buf, _len); \
 	} G_STMT_END
+
+#define _iovec_set_format_str_a(iov, max_str_len, format, str_arg) \
+	G_STMT_START { \
+		const char *_str_arg = (str_arg); \
+		\
+		nm_assert (_str_arg && strlen (_str_arg) < (max_str_len)); \
+		_iovec_set_format_a ((iov), (max_str_len), format, str_arg); \
+	} G_STMT_END
+
 #endif
 
 void
 _nm_log_impl (const char *file,
               guint line,
               const char *func,
+              gboolean mt_require_locking,
               NMLogLevel level,
               NMLogDomain domain,
               int error,
@@ -608,15 +680,37 @@ _nm_log_impl (const char *file,
 	va_list args;
 	char *msg;
 	GTimeVal tv;
-	int errno_saved;
-
-	if ((guint) level >= G_N_ELEMENTS (_nm_logging_enabled_state))
-		g_return_if_reached ();
+	int errsv;
+	const NMLogDomain *cur_log_state;
+	NMLogDomain cur_log_state_copy[_LOGL_N_REAL];
+	Global g_copy;
+	const Global *g;
+
+	if (G_UNLIKELY (mt_require_locking)) {
+		G_LOCK (log);
+		/* we evaluate logging-enabled under lock. There is still a race that
+		 * we might log the message below *after* logging was disabled. That means,
+		 * when disabling logging, we might still log messages. */
+		if (!_nm_logging_enabled_lockfree (level, domain)) {
+			G_UNLOCK (log);
+			return;
+		}
+		g_copy = gl.imm;
+		memcpy (cur_log_state_copy, _nm_logging_enabled_state, sizeof (cur_log_state_copy));
+		G_UNLOCK (log);
+		g = &g_copy;
+		cur_log_state = cur_log_state_copy;
+	} else {
+		NM_ASSERT_ON_MAIN_THREAD ();
+		if (!_nm_logging_enabled_lockfree (level, domain))
+			return;
+		g = &gl.imm;
+		cur_log_state = _nm_logging_enabled_state;
+	}
 
-	if (!(_nm_logging_enabled_state[level] & domain))
-		return;
+	(void) cur_log_state;
 
-	errno_saved = errno;
+	errsv = errno;
 
 	/* Make sure that %m maps to the specified error */
 	if (error != 0) {
@@ -630,19 +724,19 @@ _nm_log_impl (const char *file,
 	va_end (args);
 
 #define MESSAGE_FMT "%s%-7s [%ld.%04ld] %s"
-#define MESSAGE_ARG(global, tv, msg) \
-    (global).prefix, \
-    (global).level_desc[level].level_str, \
+#define MESSAGE_ARG(prefix, tv, msg) \
+    prefix, \
+    level_desc[level].level_str, \
     (tv).tv_sec, \
     ((tv).tv_usec / 100), \
     (msg)
 
 	g_get_current_time (&tv);
 
-	if (global.debug_stderr)
-		g_printerr (MESSAGE_FMT"\n", MESSAGE_ARG (global, tv, msg));
+	if (g->debug_stderr)
+		g_printerr (MESSAGE_FMT"\n", MESSAGE_ARG (g->prefix, tv, msg));
 
-	switch (global.log_backend) {
+	switch (g->log_backend) {
 #if SYSTEMD_JOURNAL
 	case LOG_BACKEND_JOURNAL:
 		{
@@ -657,18 +751,18 @@ _nm_log_impl (const char *file,
 			now = nm_utils_get_monotonic_timestamp_ns ();
 			boottime = nm_utils_monotonic_timestamp_as_boottime (now, 1);
 
-			_iovec_set_format_a (iov++, 30, "PRIORITY=%d", global.level_desc[level].syslog_level);
-			_iovec_set_format (iov++, iov_free++, "MESSAGE="MESSAGE_FMT, MESSAGE_ARG (global, tv, msg));
-			_iovec_set_string (iov++, syslog_identifier_full (&global));
+			_iovec_set_format_a (iov++, 30, "PRIORITY=%d", level_desc[level].syslog_level);
+			_iovec_set_format (iov++, iov_free++, "MESSAGE="MESSAGE_FMT, MESSAGE_ARG (g->prefix, tv, msg));
+			_iovec_set_string (iov++, syslog_identifier_full (g->syslog_identifier));
 			_iovec_set_format_a (iov++, 30, "SYSLOG_PID=%ld", (long) getpid ());
 			{
 				const LogDesc *diter;
 				int i_domain = _NUM_MAX_FIELDS_SYSLOG_FACILITY;
 				const char *s_domain_1 = NULL;
 				NMLogDomain dom_all = domain;
-				NMLogDomain dom = dom_all & _nm_logging_enabled_state[level];
+				NMLogDomain dom = dom_all & cur_log_state[level];
 
-				for (diter = &global.domain_desc[0]; diter->name; diter++) {
+				for (diter = &domain_desc[0]; diter->name; diter++) {
 					if (!NM_FLAGS_ANY (dom_all, diter->num))
 						continue;
 
@@ -690,7 +784,7 @@ _nm_log_impl (const char *file,
 					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);
+							_iovec_set_format_str_a (iov++, 30, "SYSLOG_FACILITY=%s", diter->name);
 							i_domain--;
 						}
 						dom &= ~diter->num;
@@ -701,9 +795,9 @@ _nm_log_impl (const char *file,
 				if (s_domain_all)
 					_iovec_set (iov++, s_domain_all->str, s_domain_all->len);
 				else
-					_iovec_set_format_a (iov++, _MAX_LEN (30, s_domain_1), "NM_LOG_DOMAINS=%s", s_domain_1);
+					_iovec_set_format_str_a (iov++, 30, "NM_LOG_DOMAINS=%s", s_domain_1);
 			}
-			_iovec_set_format_a (iov++, _MAX_LEN (15, global.level_desc[level].name), "NM_LOG_LEVEL=%s", global.level_desc[level].name);
+			_iovec_set_format_str_a (iov++, 15, "NM_LOG_LEVEL=%s", level_desc[level].name);
 			if (func)
 				_iovec_set_format (iov++, iov_free++, "CODE_FUNC=%s", func);
 			_iovec_set_format (iov++, iov_free++, "CODE_FILE=%s", file ?: "");
@@ -728,18 +822,41 @@ _nm_log_impl (const char *file,
 		break;
 #endif
 	case LOG_BACKEND_SYSLOG:
-		syslog (global.level_desc[level].syslog_level,
-		        MESSAGE_FMT, MESSAGE_ARG (global, tv, msg));
+		syslog (level_desc[level].syslog_level,
+		        MESSAGE_FMT, MESSAGE_ARG (g->prefix, tv, msg));
 		break;
 	default:
-		g_log (syslog_identifier_domain (&global), global.level_desc[level].g_log_level,
-		       MESSAGE_FMT, MESSAGE_ARG (global, tv, msg));
+		g_log (syslog_identifier_domain (g->syslog_identifier), level_desc[level].g_log_level,
+		       MESSAGE_FMT, MESSAGE_ARG (g->prefix, tv, msg));
 		break;
 	}
 
 	g_free (msg);
 
-	errno = errno_saved;
+	errno = errsv;
+}
+
+/*****************************************************************************/
+
+void
+_nm_utils_monotonic_timestamp_initialized (const struct timespec *tp,
+                                           gint64 offset_sec,
+                                           gboolean is_boottime)
+{
+	NM_ASSERT_ON_MAIN_THREAD ();
+
+	if (_nm_logging_enabled_lockfree (LOGL_DEBUG, LOGD_CORE)) {
+		time_t now = time (NULL);
+		struct tm tm;
+		char s[255];
+
+		strftime (s, sizeof (s), "%Y-%m-%d %H:%M:%S", localtime_r (&now, &tm));
+		nm_log_dbg (LOGD_CORE, "monotonic timestamp started counting 1.%09ld seconds ago with "
+		                       "an offset of %lld.0 seconds to %s (local time is %s)",
+		                       tp->tv_nsec,
+		                       (long long) -offset_sec,
+		                       is_boottime ? "CLOCK_BOOTTIME" : "CLOCK_MONOTONIC", s);
+	}
 }
 
 /*****************************************************************************/
@@ -774,10 +891,14 @@ nm_log_handler (const char *log_domain,
 		break;
 	}
 
-	if (global.debug_stderr)
-		g_printerr ("%s%s\n", global.prefix, message ?: "");
+	/* we don't need any locking here. The glib log handler gets only registered
+	 * once during nm_logging_init() and the global data is not modified afterwards. */
+	nm_assert (gl.imm.init_done);
 
-	switch (global.log_backend) {
+	if (gl.imm.debug_stderr)
+		g_printerr ("%s%s\n", gl.imm.prefix, message ?: "");
+
+	switch (gl.imm.log_backend) {
 #if SYSTEMD_JOURNAL
 	case LOG_BACKEND_JOURNAL:
 		{
@@ -787,8 +908,8 @@ nm_log_handler (const char *log_domain,
 			boottime = nm_utils_monotonic_timestamp_as_boottime (now, 1);
 
 			sd_journal_send ("PRIORITY=%d", syslog_priority,
-			                 "MESSAGE=%s%s", global.prefix, message ?: "",
-			                 syslog_identifier_full (&global),
+			                 "MESSAGE=%s%s", gl.imm.prefix, message ?: "",
+			                 syslog_identifier_full (gl.imm.syslog_identifier),
 			                 "SYSLOG_PID=%ld", (long) getpid (),
 			                 "SYSLOG_FACILITY=GLIB",
 			                 "GLIB_DOMAIN=%s", log_domain ?: "",
@@ -800,7 +921,7 @@ nm_log_handler (const char *log_domain,
 		break;
 #endif
 	default:
-		syslog (syslog_priority, "%s%s", global.prefix, message ?: "");
+		syslog (syslog_priority, "%s%s", gl.imm.prefix, message ?: "");
 		break;
 	}
 }
@@ -808,44 +929,63 @@ nm_log_handler (const char *log_domain,
 gboolean
 nm_logging_syslog_enabled (void)
 {
-	return global.uses_syslog;
+	NM_ASSERT_ON_MAIN_THREAD ();
+
+	return gl.imm.uses_syslog;
 }
 
 void
-nm_logging_set_prefix (const char *format, ...)
+nm_logging_init_pre (const char *syslog_identifier,
+                     char *prefix_take)
 {
-	char *prefix;
-	va_list ap;
+	/* this function may be called zero or one times, and only
+	 * - on the main thread
+	 * - not after nm_logging_init(). */
+
+	NM_ASSERT_ON_MAIN_THREAD ();
 
-	/* prefix can only be set once, to a non-empty string. Also, after
-	 * nm_logging_syslog_openlog() the prefix cannot be set either. */
-	if (global.log_backend != LOG_BACKEND_GLIB)
+	if (gl.imm.init_pre_done)
 		g_return_if_reached ();
-	if (global.prefix[0])
+
+	if (gl.imm.init_done)
 		g_return_if_reached ();
 
-	va_start (ap, format);
-	prefix = g_strdup_vprintf (format, ap);
-	va_end (ap);
+	if (!_syslog_identifier_valid_domain (syslog_identifier))
+		g_return_if_reached ();
 
-	if (!prefix || !prefix[0])
+	if (!prefix_take || !prefix_take[0])
 		g_return_if_reached ();
 
+	G_LOCK (log);
+
+	gl.mut.init_pre_done = TRUE;
+
+	gl.mut.syslog_identifier = g_strdup_printf ("SYSLOG_IDENTIFIER=%s", syslog_identifier);
+	nm_assert (_syslog_identifier_assert (gl.imm.syslog_identifier));
+
 	/* we pass the allocated string on and never free it. */
-	global.prefix = prefix;
+	gl.mut.prefix = prefix_take;
+
+	G_UNLOCK (log);
 }
 
 void
-nm_logging_syslog_openlog (const char *logging_backend, gboolean debug)
+nm_logging_init (const char *logging_backend, gboolean debug)
 {
 	gboolean fetch_monotonic_timestamp = FALSE;
 	gboolean obsolete_debug_backend = FALSE;
+	LogBackend x_log_backend;
+
+	/* this function may be called zero or one times, and only on the
+	 * main thread. */
+
+	NM_ASSERT_ON_MAIN_THREAD ();
 
 	nm_assert (NM_IN_STRSET (""NM_CONFIG_DEFAULT_LOGGING_BACKEND,
 	                         NM_LOG_CONFIG_BACKEND_JOURNAL,
 	                         NM_LOG_CONFIG_BACKEND_SYSLOG));
 
-	if (global.log_backend != LOG_BACKEND_GLIB)
+	if (gl.imm.init_done)
 		g_return_if_reached ();
 
 	if (!logging_backend)
@@ -862,26 +1002,36 @@ nm_logging_syslog_openlog (const char *logging_backend, gboolean debug)
 		obsolete_debug_backend = TRUE;
 	}
 
+
+	G_LOCK (log);
+
 #if SYSTEMD_JOURNAL
 	if (!nm_streq (logging_backend, NM_LOG_CONFIG_BACKEND_SYSLOG)) {
-		global.log_backend = LOG_BACKEND_JOURNAL;
-		global.uses_syslog = TRUE;
-		global.debug_stderr = debug;
+		x_log_backend = LOG_BACKEND_JOURNAL;
+
+		/* We only log the monotonic-timestamp with structured logging (journal).
+		 * Only in this case, fetch the timestamp. */
 		fetch_monotonic_timestamp = TRUE;
 	} else
 #endif
 	{
-		global.log_backend = LOG_BACKEND_SYSLOG;
-		global.uses_syslog = TRUE;
-		global.debug_stderr = debug;
-		openlog (syslog_identifier_domain (&global), LOG_PID, LOG_DAEMON);
+		x_log_backend = LOG_BACKEND_SYSLOG;
+		openlog (syslog_identifier_domain (gl.imm.syslog_identifier), LOG_PID, LOG_DAEMON);
 	}
 
-	g_log_set_handler (syslog_identifier_domain (&global),
+	gl.mut.init_done = TRUE;
+	gl.mut.log_backend = x_log_backend;
+	gl.mut.uses_syslog = TRUE;
+	gl.mut.debug_stderr = debug;
+
+	g_log_set_handler (syslog_identifier_domain (gl.imm.syslog_identifier),
 	                   G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
 	                   nm_log_handler,
 	                   NULL);
 
+	G_UNLOCK (log);
+
+
 	if (fetch_monotonic_timestamp) {
 		/* ensure we read a monotonic timestamp. Reading the timestamp the first
 		 * time causes a logging message. We don't want to do that during _nm_log_impl. */
diff --git a/src/nm-logging.h b/src/nm-logging.h
index 0737bbd6..a824890a 100644
--- a/src/nm-logging.h
+++ b/src/nm-logging.h
@@ -22,71 +22,16 @@
 #ifndef __NETWORKMANAGER_LOGGING_H__
 #define __NETWORKMANAGER_LOGGING_H__
 
-#include "nm-core-types.h"
-
 #ifdef __NM_TEST_UTILS_H__
 #error nm-test-utils.h must be included as last header
 #endif
 
+#include "nm-utils/nm-logging-fwd.h"
+
 #define NM_LOG_CONFIG_BACKEND_DEBUG   "debug"
 #define NM_LOG_CONFIG_BACKEND_SYSLOG  "syslog"
 #define NM_LOG_CONFIG_BACKEND_JOURNAL "journal"
 
-/* Log domains */
-typedef enum  { /*< skip >*/
-	LOGD_NONE       = 0LL,
-	LOGD_PLATFORM   = (1LL << 0), /* Platform services */
-	LOGD_RFKILL     = (1LL << 1),
-	LOGD_ETHER      = (1LL << 2),
-	LOGD_WIFI       = (1LL << 3),
-	LOGD_BT         = (1LL << 4),
-	LOGD_MB         = (1LL << 5), /* mobile broadband */
-	LOGD_DHCP4      = (1LL << 6),
-	LOGD_DHCP6      = (1LL << 7),
-	LOGD_PPP        = (1LL << 8),
-	LOGD_WIFI_SCAN  = (1LL << 9),
-	LOGD_IP4        = (1LL << 10),
-	LOGD_IP6        = (1LL << 11),
-	LOGD_AUTOIP4    = (1LL << 12),
-	LOGD_DNS        = (1LL << 13),
-	LOGD_VPN        = (1LL << 14),
-	LOGD_SHARING    = (1LL << 15), /* Connection sharing/dnsmasq */
-	LOGD_SUPPLICANT = (1LL << 16), /* WiFi and 802.1x */
-	LOGD_AGENTS     = (1LL << 17), /* Secret agents */
-	LOGD_SETTINGS   = (1LL << 18), /* Settings */
-	LOGD_SUSPEND    = (1LL << 19), /* Suspend/Resume */
-	LOGD_CORE       = (1LL << 20), /* Core daemon and policy stuff */
-	LOGD_DEVICE     = (1LL << 21), /* Device state and activation */
-	LOGD_OLPC       = (1LL << 22),
-	LOGD_INFINIBAND = (1LL << 23),
-	LOGD_FIREWALL   = (1LL << 24),
-	LOGD_ADSL       = (1LL << 25),
-	LOGD_BOND       = (1LL << 26),
-	LOGD_VLAN       = (1LL << 27),
-	LOGD_BRIDGE     = (1LL << 28),
-	LOGD_DBUS_PROPS = (1LL << 29),
-	LOGD_TEAM       = (1LL << 30),
-	LOGD_CONCHECK   = (1LL << 31),
-	LOGD_DCB        = (1LL << 32), /* Data Center Bridging */
-	LOGD_DISPATCH   = (1LL << 33),
-	LOGD_AUDIT      = (1LL << 34),
-	LOGD_SYSTEMD    = (1LL << 35),
-	LOGD_VPN_PLUGIN = (1LL << 36),
-	LOGD_PROXY      = (1LL << 37),
-
-	__LOGD_MAX,
-	LOGD_ALL       = (((__LOGD_MAX - 1LL) << 1) - 1LL),
-	LOGD_DEFAULT   = LOGD_ALL & ~(
-	                              LOGD_DBUS_PROPS |
-	                              LOGD_WIFI_SCAN |
-	                              LOGD_VPN_PLUGIN |
-	                              0),
-
-	/* aliases: */
-	LOGD_DHCP       = LOGD_DHCP4 | LOGD_DHCP6,
-	LOGD_IP         = LOGD_IP4 | LOGD_IP6,
-} NMLogDomain;
-
 static inline NMLogDomain
 LOGD_IP_from_af (int addr_family)
 {
@@ -97,22 +42,6 @@ LOGD_IP_from_af (int addr_family)
 	g_return_val_if_reached (LOGD_NONE);
 }
 
-/* Log levels */
-typedef enum  { /*< skip >*/
-	LOGL_TRACE,
-	LOGL_DEBUG,
-	LOGL_INFO,
-	LOGL_WARN,
-	LOGL_ERR,
-
-	_LOGL_N_REAL, /* the number of actual logging levels */
-
-	_LOGL_OFF = _LOGL_N_REAL, /* special logging level that is always disabled. */
-	_LOGL_KEEP,               /* special logging level to indicate that the logging level should not be changed. */
-
-	_LOGL_N, /* the number of logging levels including "OFF" */
-} NMLogLevel;
-
 #define nm_log_err(domain, ...)     nm_log (LOGL_ERR,   (domain),  NULL, NULL, __VA_ARGS__)
 #define nm_log_warn(domain, ...)    nm_log (LOGL_WARN,  (domain),  NULL, NULL, __VA_ARGS__)
 #define nm_log_info(domain, ...)    nm_log (LOGL_INFO,  (domain),  NULL, NULL, __VA_ARGS__)
@@ -125,10 +54,12 @@ typedef enum  { /*< skip >*/
 /* A wrapper for the _nm_log_impl() function that adds call site information.
  * Contrary to nm_log(), it unconditionally calls the function without
  * checking whether logging for the given level and domain is enabled. */
-#define _nm_log(level, domain, error, ifname, con_uuid, ...) \
+#define _nm_log_mt(mt_require_locking, level, domain, error, ifname, con_uuid, ...) \
     G_STMT_START { \
-        _nm_log_impl (__FILE__, __LINE__, \
+        _nm_log_impl (__FILE__, \
+                      __LINE__, \
                       _NM_LOG_FUNC, \
+                      (mt_require_locking), \
                       (level), \
                       (domain), \
                       (error), \
@@ -137,6 +68,9 @@ typedef enum  { /*< skip >*/
                       ""__VA_ARGS__); \
     } G_STMT_END
 
+#define _nm_log(level, domain, error, ifname, con_uuid, ...) \
+	_nm_log_mt (!(NM_THREAD_SAFE_ON_MAIN_THREAD), level, domain, error, ifname, con_uuid, __VA_ARGS__)
+
 /* 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, ...) \
@@ -206,29 +140,38 @@ _nm_log_ptr_is_debug (NMLogLevel level)
                 prefix, \
                 __VA_ARGS__)
 
-void _nm_log_impl (const char *file,
-                   guint line,
-                   const char *func,
-                   NMLogLevel level,
-                   NMLogDomain domain,
-                   int error,
-                   const char *ifname,
-                   const char *con_uuid,
-                   const char *fmt,
-                   ...) _nm_printf (9, 10);
-
 const char *nm_logging_level_to_string (void);
 const char *nm_logging_domains_to_string (void);
 
+/*****************************************************************************/
+
 extern NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL];
+
 static inline gboolean
-nm_logging_enabled (NMLogLevel level, NMLogDomain domain)
+_nm_logging_enabled_lockfree (NMLogLevel level, NMLogDomain domain)
 {
 	nm_assert (((guint) level) < G_N_ELEMENTS (_nm_logging_enabled_state));
 	return    (((guint) level) < G_N_ELEMENTS (_nm_logging_enabled_state))
 	       && !!(_nm_logging_enabled_state[level] & domain);
 }
 
+gboolean _nm_logging_enabled_locking (NMLogLevel level, NMLogDomain domain);
+
+static inline gboolean
+nm_logging_enabled_mt (gboolean mt_require_locking, NMLogLevel level, NMLogDomain domain)
+{
+	if (mt_require_locking)
+		return _nm_logging_enabled_locking (level, domain);
+
+	NM_ASSERT_ON_MAIN_THREAD ();
+	return _nm_logging_enabled_lockfree (level, domain);
+}
+
+#define nm_logging_enabled(level, domain) \
+	nm_logging_enabled_mt (!(NM_THREAD_SAFE_ON_MAIN_THREAD), level, domain)
+
+/*****************************************************************************/
+
 NMLogLevel nm_logging_get_level (NMLogDomain domain);
 
 const char *nm_logging_all_levels_to_string (void);
@@ -239,10 +182,11 @@ gboolean nm_logging_setup (const char  *level,
                            char       **bad_domains,
                            GError     **error);
 
-void nm_logging_set_syslog_identifier (const char *domain);
-void nm_logging_set_prefix (const char *format, ...) _nm_printf (1, 2);
+void nm_logging_init_pre (const char *syslog_identifier,
+                          char *prefix_take);
+
+void     nm_logging_init (const char *logging_backend, gboolean debug);
 
-void     nm_logging_syslog_openlog (const char *logging_backend, gboolean debug);
 gboolean nm_logging_syslog_enabled (void);
 
 /*****************************************************************************/
@@ -353,8 +297,6 @@ gboolean nm_logging_syslog_enabled (void);
 #define _LOG3t_err(errsv, ...) G_STMT_START { if (FALSE) { _NMLOG3_err (errsv, LOGL_TRACE, __VA_ARGS__); } } G_STMT_END
 #endif
 
-extern void (*_nm_logging_clear_platform_logging_cache) (void);
-
 /*****************************************************************************/
 
 #define __NMLOG_DEFAULT(level, domain, prefix, ...) \
@@ -374,4 +316,8 @@ extern void (*_nm_logging_clear_platform_logging_cache) (void);
 		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
 	} G_STMT_END
 
+/*****************************************************************************/
+
+extern void _nm_logging_clear_platform_logging_cache (void);
+
 #endif /* __NETWORKMANAGER_LOGGING_H__ */
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 3ddc3b92..0bf6a751 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -25,8 +25,6 @@
 
 #include <stdlib.h>
 #include <fcntl.h>
-#include <errno.h>
-#include <string.h>
 #include <unistd.h>
 
 #include "nm-utils/nm-c-list.h"
@@ -39,6 +37,7 @@
 #include "platform/nm-platform.h"
 #include "platform/nmp-object.h"
 #include "nm-hostname-manager.h"
+#include "nm-keep-alive.h"
 #include "nm-rfkill-manager.h"
 #include "dhcp/nm-dhcp-manager.h"
 #include "settings/nm-settings.h"
@@ -79,6 +78,7 @@ typedef enum {
 	ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL,
 	ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER,
 	ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE,
+	ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2,
 } AsyncOpType;
 
 typedef struct {
@@ -95,6 +95,7 @@ typedef struct {
 				struct {
 					GDBusMethodInvocation *invocation;
 					NMConnection *connection;
+					NMSettingsConnectionPersistMode persist;
 				} add_and_activate;
 			};
 		} ac_auth;
@@ -318,6 +319,7 @@ static NMActiveConnection *_new_active_connection (NMManager *self,
                                                    NMAuthSubject *subject,
                                                    NMActivationType activation_type,
                                                    NMActivationReason activation_reason,
+                                                   NMActivationStateFlags initial_state_flags,
                                                    GError **error);
 
 static void policy_activating_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data);
@@ -367,9 +369,11 @@ static void _internal_activation_auth_done (NMManager *self,
                                             gboolean success,
                                             const char *error_desc);
 static void _add_and_activate_auth_done (NMManager *self,
+                                         AsyncOpType async_op_type,
                                          NMActiveConnection *active,
                                          NMConnection *connection,
                                          GDBusMethodInvocation *invocation,
+                                         NMSettingsConnectionPersistMode persist,
                                          gboolean success,
                                          const char *error_desc);
 static void _activation_auth_done (NMManager *self,
@@ -395,7 +399,7 @@ _connection_is_vpn (NMConnection *connection)
 
 	/* 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. */
+	 * on the existence of a [vpn] section. */
 	return !!nm_connection_get_setting_vpn (connection);
 }
 
@@ -482,18 +486,24 @@ _async_op_data_new_ac_auth_activate_user (NMManager *self,
 
 static AsyncOpData *
 _async_op_data_new_ac_auth_add_and_activate (NMManager *self,
+                                             AsyncOpType async_op_type,
                                              NMActiveConnection *active_take,
                                              GDBusMethodInvocation *invocation_take,
-                                             NMConnection *connection_take)
+                                             NMConnection *connection_take,
+                                             NMSettingsConnectionPersistMode persist)
 {
 	AsyncOpData *async_op_data;
 
+	nm_assert (NM_IN_SET (async_op_type, ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE,
+	                                     ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2));
+
 	async_op_data = g_slice_new0 (AsyncOpData);
-	async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE;
+	async_op_data->async_op_type = async_op_type;
 	async_op_data->self = g_object_ref (self);
 	async_op_data->ac_auth.active = active_take;
 	async_op_data->ac_auth.add_and_activate.invocation = invocation_take;
 	async_op_data->ac_auth.add_and_activate.connection = connection_take;
+	async_op_data->ac_auth.add_and_activate.persist = persist;
 	c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst);
 	return async_op_data;
 }
@@ -529,10 +539,13 @@ _async_op_complete_ac_auth_cb (NMActiveConnection *active,
 		                       error_desc);
 		break;
 	case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE:
+	case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2:
 		_add_and_activate_auth_done (async_op_data->self,
+		                             async_op_data->async_op_type,
 		                             async_op_data->ac_auth.active,
 		                             async_op_data->ac_auth.add_and_activate.connection,
 		                             async_op_data->ac_auth.add_and_activate.invocation,
+		                             async_op_data->ac_auth.add_and_activate.persist,
 		                             success,
 		                             error_desc);
 		g_object_unref (async_op_data->ac_auth.add_and_activate.connection);
@@ -1176,7 +1189,7 @@ _reload_auth_cb (NMAuthChain *chain,
 		goto out;
 	}
 
-	nm_config_reload (priv->config, reload_type);
+	nm_config_reload (priv->config, reload_type, TRUE);
 	g_dbus_method_invocation_return_value (context, NULL);
 
 out:
@@ -1440,6 +1453,14 @@ nm_manager_update_metered (NMManager *self)
 	}
 }
 
+NMMetered
+nm_manager_get_metered (NMManager *self)
+{
+	g_return_val_if_fail (NM_IS_MANAGER (self), NM_METERED_UNKNOWN);
+
+	return NM_MANAGER_GET_PRIVATE (self)->metered;
+}
+
 static void
 nm_manager_update_state (NMManager *self)
 {
@@ -1518,6 +1539,7 @@ check_if_startup_complete (NMManager *self)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMDevice *device;
+	const char *reason;
 
 	if (!priv->startup)
 		return;
@@ -1525,15 +1547,19 @@ check_if_startup_complete (NMManager *self)
 	if (!priv->devices_inited)
 		return;
 
-	if (!nm_settings_get_startup_complete (priv->settings)) {
-		_LOGD (LOGD_CORE, "check_if_startup_complete returns FALSE because of NMSettings");
+	reason = nm_settings_get_startup_complete_blocked_reason (priv->settings);
+	if (reason) {
+		_LOGD (LOGD_CORE, "startup complete is waiting for connection (%s)",
+		       reason);
 		return;
 	}
 
 	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 (device));
+		reason = nm_device_has_pending_action_reason (device);
+		if (reason) {
+			_LOGD (LOGD_CORE, "startup complete is waiting for device '%s' (%s)",
+			       nm_device_get_iface (device),
+			       reason);
 			return;
 		}
 	}
@@ -1733,7 +1759,7 @@ find_parent_device_for_connection (NMManager *self, NMConnection *connection, NM
 	if (!parent_connection)
 		return NULL;
 
-	/* Check if the parent connection is currently activated or is comaptible
+	/* Check if the parent connection is currently activated or is compatible
 	 * with some known device.
 	 */
 	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
@@ -2461,7 +2487,12 @@ get_existing_connection (NMManager *self,
 	if (ifindex) {
 		int master_ifindex = nm_platform_link_get_master (priv->platform, ifindex);
 
-		if (master_ifindex) {
+		/* Check that the master is activating before assuming a
+		 * slave connection. However, ignore ovs-system master as
+		 * we never manage it.
+		 */
+		if (   master_ifindex
+		    && nm_platform_link_get_type (priv->platform, master_ifindex) != NM_LINK_TYPE_OPENVSWITCH) {
 			master = nm_manager_get_device_by_ifindex (self, master_ifindex);
 			if (!master) {
 				_LOG2D (LOGD_DEVICE, device, "assume: don't assume because "
@@ -2679,6 +2710,18 @@ recheck_assume_connection (NMManager *self,
 		GError *error = NULL;
 
 		subject = nm_auth_subject_new_internal ();
+
+		/* Note: the lifetime of the activation connection is always bound to the profiles visibility
+		 * via NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY.
+		 *
+		 * This only makes a difference, if the profile actually has "connection.permissions"
+		 * set to limit visibility (which is not the case for externally managed, generated profiles).
+		 *
+		 * If we assume a previously active connection whose lifetime was unbound, we now bind it
+		 * after restart. That is not correct, and can mean that the profile becomes subject to
+		 * deactivation after restart (if the user logs out).
+		 *
+		 * This should be improved, but it's unclear how. */
 		active = _new_active_connection (self,
 		                                 FALSE,
 		                                 sett_conn,
@@ -2689,6 +2732,7 @@ recheck_assume_connection (NMManager *self,
 		                                 subject,
 		                                 generated ? NM_ACTIVATION_TYPE_EXTERNAL : NM_ACTIVATION_TYPE_ASSUME,
 		                                 generated ? NM_ACTIVATION_REASON_EXTERNAL : NM_ACTIVATION_REASON_ASSUME,
+		                                 NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
 		                                 &error);
 
 		if (!active) {
@@ -2810,43 +2854,91 @@ device_realized (NMDevice *device,
 	_emit_device_added_removed (self, device, nm_device_is_real (device));
 }
 
-static void
-device_connectivity_changed (NMDevice *device,
-                             NMManager *self)
+static NMConnectivityState
+_get_best_connectivity (NMManager *self, int addr_family)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	NMConnectivityState best_state = NM_CONNECTIVITY_UNKNOWN;
-	NMConnectivityState state;
+	NMConnectivityState best_state;
 	NMDevice *dev;
+	gint64 best_metric;
+
+	if (addr_family == AF_UNSPEC) {
+		best_state = _get_best_connectivity (self, AF_INET);
+		if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) {
+			/* already FULL IPv4 connectivity. No need to check IPv6, it doesn't get
+			 * better. */
+			return best_state;
+		}
+		return NM_MAX_WITH_CMP (nm_connectivity_state_cmp,
+		                        best_state,
+		                        _get_best_connectivity (self, AF_INET6));
+	}
 
-	best_state = nm_device_get_connectivity_state (device);
-	if (best_state < NM_CONNECTIVITY_FULL) {
-		/* FIXME: is this really correct, to considere devices that don't have
-		 * (the best) default route for connectivity checking? */
-		c_list_for_each_entry (dev, &priv->devices_lst_head, devices_lst) {
-			state = nm_device_get_connectivity_state (dev);
-			if (nm_connectivity_state_cmp (state, best_state) <= 0)
-				continue;
+	nm_assert_addr_family (addr_family);
+
+	best_state = NM_CONNECTIVITY_UNKNOWN;
+	best_metric = G_MAXINT64;
+	c_list_for_each_entry (dev, &priv->devices_lst_head, devices_lst) {
+		const NMPObject *r;
+		NMConnectivityState state;
+		gint64 metric;
+
+		r = nm_device_get_best_default_route (dev, addr_family);
+		if (r) {
+			metric = nm_utils_ip_route_metric_normalize (addr_family,
+			                                             NMP_OBJECT_CAST_IP_ROUTE (r)->metric);
+		} else {
+			/* if all devices have no default-route, we still include the best
+			 * of all connectivity state of all the devices. */
+			metric = G_MAXINT64;
+		}
+
+		if (metric > best_metric) {
+			/* we already have a default route with better metric. The connectivity state
+			 * of this device is irreleavnt. */
+			continue;
+		}
+
+		state = nm_device_get_connectivity_state (dev, addr_family);
+		if (metric < best_metric) {
+			/* this device has a better default route. It wins. */
+			best_metric = metric;
 			best_state = state;
-			if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) {
-				/* it doesn't get better than this. */
-				break;
-			}
+		} else {
+			best_state = NM_MAX_WITH_CMP (nm_connectivity_state_cmp,
+			                              best_state,
+			                              state);
+		}
+
+		if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) {
+			/* it doesn't get better than FULL. We are done. */
+			break;
 		}
 	}
-	nm_assert (best_state <= NM_CONNECTIVITY_FULL);
-	nm_assert (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) <= 0);
 
-	if (best_state != priv->connectivity_state) {
-		priv->connectivity_state = best_state;
+	return best_state;
+}
 
-		_LOGD (LOGD_CORE, "connectivity checking indicates %s",
-		       nm_connectivity_state_to_string (priv->connectivity_state));
+static void
+device_connectivity_changed (NMDevice *device,
+                             GParamSpec *pspec,
+                             NMManager *self)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMConnectivityState best_state;
 
-		nm_manager_update_state (self);
-		_notify (self, PROP_CONNECTIVITY);
-		nm_dispatcher_call_connectivity (priv->connectivity_state, NULL, NULL, NULL);
-	}
+	best_state = _get_best_connectivity (self, AF_UNSPEC);
+	if (best_state == priv->connectivity_state)
+		return;
+
+	priv->connectivity_state = best_state;
+
+	_LOGD (LOGD_CORE, "connectivity checking indicates %s",
+	       nm_connectivity_state_to_string (priv->connectivity_state));
+
+	nm_manager_update_state (self);
+	_notify (self, PROP_CONNECTIVITY);
+	nm_dispatcher_call_connectivity (priv->connectivity_state, NULL, NULL, NULL);
 }
 
 static void
@@ -2957,7 +3049,10 @@ add_device (NMManager *self, NMDevice *device, GError **error)
 	                  G_CALLBACK (device_realized),
 	                  self);
 
-	g_signal_connect (device, NM_DEVICE_CONNECTIVITY_CHANGED,
+	g_signal_connect (device, "notify::" NM_DEVICE_IP4_CONNECTIVITY,
+	                  G_CALLBACK (device_connectivity_changed),
+	                  self);
+	g_signal_connect (device, "notify::" NM_DEVICE_IP6_CONNECTIVITY,
 	                  G_CALLBACK (device_connectivity_changed),
 	                  self);
 
@@ -3409,7 +3504,7 @@ nm_manager_get_best_device_for_connection (NMManager *self,
 		flags = NM_DEVICE_CHECK_CON_AVAILABLE_NONE;
 	else {
 		/* if the profile is multi-connect=single, we also consider devices which
-		 * are marked as unmanaged. And explicit user-request shows sufficent user
+		 * are marked as unmanaged. And explicit user-request shows sufficient user
 		 * intent to make the device managed.
 		 * That is also, because we expect that such profile is suitably tied
 		 * to the intended device. So when an unmanaged device matches, the user's
@@ -3872,11 +3967,16 @@ ensure_master_active_connection (NMManager *self,
                                  GError **error)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMActiveConnection *ac;
 	NMActiveConnection *master_ac = NULL;
 	NMDeviceState master_state;
+	gboolean bind_lifetime_to_profile_visibility;
+
+	g_return_val_if_fail (connection, NULL);
+	g_return_val_if_fail (master_connection || master_device, FALSE);
 
-	g_assert (connection);
-	g_assert (master_connection || master_device);
+	bind_lifetime_to_profile_visibility = NM_FLAGS_HAS (nm_device_get_activation_state_flags (device),
+	                                                    NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
 
 	/* If the master device isn't activated then we need to activate it using
 	 * compatible connection.  If it's already activating we can just proceed.
@@ -3901,8 +4001,16 @@ ensure_master_active_connection (NMManager *self,
 		if (   (master_state == NM_DEVICE_STATE_ACTIVATED)
 		    || nm_device_is_activating (master_device)) {
 			/* Device already using master_connection */
-			g_assert (device_connection);
-			return NM_ACTIVE_CONNECTION (nm_device_get_act_request (master_device));
+			ac = NM_ACTIVE_CONNECTION (nm_device_get_act_request (master_device));
+			g_return_val_if_fail (device_connection, ac);
+
+			if (!bind_lifetime_to_profile_visibility) {
+				/* unbind the lifetime. */
+				nm_active_connection_set_state_flags_clear (ac,
+				                                            NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
+			}
+
+			return ac;
 		}
 
 		/* If the device is disconnected, find a compatible connection and
@@ -3939,6 +4047,9 @@ ensure_master_active_connection (NMManager *self,
 					                                            subject,
 					                                            NM_ACTIVATION_TYPE_MANAGED,
 					                                            activation_reason,
+					                                              bind_lifetime_to_profile_visibility
+					                                            ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
+					                                            : NM_ACTIVATION_STATE_FLAG_NONE,
 					                                            error);
 					return master_ac;
 				}
@@ -3987,6 +4098,9 @@ ensure_master_active_connection (NMManager *self,
 			                                            subject,
 			                                            NM_ACTIVATION_TYPE_MANAGED,
 			                                            activation_reason,
+			                                              bind_lifetime_to_profile_visibility
+			                                            ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
+			                                            : NM_ACTIVATION_STATE_FLAG_NONE,
 			                                            error);
 			return master_ac;
 		}
@@ -4104,7 +4218,7 @@ should_connect_slaves (NMConnection *connection, NMDevice *device)
 		goto out;
 
 	val = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                   "connection.autoconnect-slaves",
+	                                                   NM_CON_DEFAULT ("connection.autoconnect-slaves"),
 	                                                   device,
 	                                                   0, 1, -1);
 
@@ -4148,6 +4262,7 @@ autoconnect_slaves (NMManager *self,
 	                           master_device)) {
 		gs_free SlaveConnectionInfo *slaves = NULL;
 		guint i, n_slaves = 0;
+		gboolean bind_lifetime_to_profile_visibility;
 
 		slaves = find_slaves (self, master_connection, master_device, &n_slaves);
 		if (n_slaves > 1) {
@@ -4162,6 +4277,10 @@ autoconnect_slaves (NMManager *self,
 			                   GINT_TO_POINTER (!nm_streq0 (value, "index")));
 		}
 
+		bind_lifetime_to_profile_visibility =    n_slaves > 0
+		                                      && NM_FLAGS_HAS (nm_device_get_activation_state_flags (master_device),
+		                                                       NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
+
 		for (i = 0; i < n_slaves; i++) {
 			SlaveConnectionInfo *slave = &slaves[i];
 			const char *uuid;
@@ -4216,6 +4335,9 @@ autoconnect_slaves (NMManager *self,
 			                                subject,
 			                                NM_ACTIVATION_TYPE_MANAGED,
 			                                NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES,
+			                                  bind_lifetime_to_profile_visibility
+			                                ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
+			                                : NM_ACTIVATION_STATE_FLAG_NONE,
 			                                &local_err);
 			if (local_err) {
 				_LOGW (LOGD_CORE, "Slave connection activation failed: %s", local_err->message);
@@ -4276,6 +4398,40 @@ unmanaged_to_disconnected (NMDevice *device)
 	}
 }
 
+static NMActivationStateFlags
+_activation_bind_lifetime_to_profile_visibility (NMAuthSubject *subject)
+{
+	if (   nm_auth_subject_is_internal (subject)
+	    || nm_auth_subject_get_unix_process_uid (subject) == 0) {
+		/* internal requests and requests from root are always unbound. */
+		return NM_ACTIVATION_STATE_FLAG_NONE;
+	}
+
+	/* if the activation was not done by internal decision nor root, there
+	 * are the following cases:
+	 *
+	 * - the connection has "connection.permissions" unset and the profile
+	 *   is not restricted to a user and commonly always visible. It does
+	 *   not hurt to bind the lifetime, because we expect the profile to be
+	 *   visible at the moment. If the profile changes (while still being active),
+	 *   we want to pick-up changes to the visibility and possibly disconnect.
+	 *
+	 * - the connection has "connection.permissions" set, and the current user
+	 *   is the owner:
+	 *
+	 *      - Usually, we would expect that the profile is visible at the moment,
+	 *        and of course we want to bind the lifetime. The moment the user
+	 *        logs out, the connection becomes invisible and disconnects.
+	 *
+	 *      - the profile at this time could already be invisible (e.g. if the
+	 *        user didn't create a proper session (sudo) and manually activates
+	 *        an invisible profile. In this case, we still want to bind the
+	 *        lifetime, and it will disconnect after the user logs in and logs
+	 *        out again. NMKeepAlive takes care of that.
+	 */
+	return NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY;
+}
+
 /* The parent connection is ready; we can proceed realizing the device and
  * progressing the device to disconencted state.
  */
@@ -4405,6 +4561,8 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 			                                            subject,
 			                                            NM_ACTIVATION_TYPE_MANAGED,
 			                                            nm_active_connection_get_activation_reason (active),
+			                                              nm_active_connection_get_state_flags (active)
+			                                            & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
 			                                            error);
 			if (!parent_ac) {
 				g_prefix_error (error, "%s failed to activate parent: ", nm_device_get_iface (device));
@@ -4530,7 +4688,9 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 			for (i = 0; i < n_all; i++) {
 				nm_device_disconnect_active_connection (  all_ac_arr
 				                                        ? all_ac_arr->pdata[i]
-				                                        : ac);
+				                                        : ac,
+				                                        NM_DEVICE_STATE_REASON_NEW_ACTIVATION,
+				                                        NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN);
 			}
 		}
 	}
@@ -4603,6 +4763,7 @@ _new_active_connection (NMManager *self,
                         NMAuthSubject *subject,
                         NMActivationType activation_type,
                         NMActivationReason activation_reason,
+                        NMActivationStateFlags initial_state_flags,
                         GError **error)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
@@ -4674,6 +4835,7 @@ _new_active_connection (NMManager *self,
 		                                                     parent_device,
 		                                                     nm_dbus_object_get_path (NM_DBUS_OBJECT (parent)),
 		                                                     activation_reason,
+		                                                     initial_state_flags,
 		                                                     subject);
 	}
 
@@ -4683,6 +4845,7 @@ _new_active_connection (NMManager *self,
 	                                                  subject,
 	                                                  activation_type,
 	                                                  activation_reason,
+	                                                  initial_state_flags,
 	                                                  device);
 }
 
@@ -4746,6 +4909,7 @@ fail:
  * @activation_type: whether to assume the connection. That is, take over gracefully,
  *   non-destructible.
  * @activation_reason: the reason for activation
+ * @initial_state_flags: the initial state flags for the activation.
  * @error: return location for an error
  *
  * Begins a new internally-initiated activation of @sett_conn on @device.
@@ -4767,6 +4931,7 @@ nm_manager_activate_connection (NMManager *self,
                                 NMAuthSubject *subject,
                                 NMActivationType activation_type,
                                 NMActivationReason activation_reason,
+                                NMActivationStateFlags initial_state_flags,
                                 GError **error)
 {
 	NMManagerPrivate *priv;
@@ -4820,6 +4985,7 @@ nm_manager_activate_connection (NMManager *self,
 	                                 subject,
 	                                 activation_type,
 	                                 activation_reason,
+	                                 initial_state_flags,
 	                                 error);
 	if (!active)
 		return NULL;
@@ -4840,7 +5006,7 @@ nm_manager_activate_connection (NMManager *self,
  *   is only a partial activation.
  * @connection: the partial #NMConnection to be activated (if @sett_conn is unspecified)
  * @device_path: the object path of the device to be activated, or NULL
- * @out_device: on successful reutrn, the #NMDevice to be activated with @connection
+ * @out_device: on successful return, the #NMDevice to be activated with @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.
@@ -5079,6 +5245,7 @@ impl_manager_activate_connection (NMDBusObject *obj,
 	                                 subject,
 	                                 NM_ACTIVATION_TYPE_MANAGED,
 	                                 NM_ACTIVATION_REASON_USER_REQUEST,
+	                                 _activation_bind_lifetime_to_profile_visibility (subject),
 	                                 &error);
 	if (!active)
 		goto error;
@@ -5116,35 +5283,54 @@ activation_add_done (NMSettings *settings,
 	NMManager *self;
 	gs_unref_object NMActiveConnection *active = NULL;
 	gs_free_error GError *local = NULL;
+	gpointer persist_ptr;
+	NMSettingsConnectionPersistMode persist;
+	gpointer async_op_type_ptr;
+	AsyncOpType async_op_type;
+	GVariant *result_floating;
 
-	nm_utils_user_data_unpack (user_data, &self, &active);
-
-	if (!error) {
-		nm_active_connection_set_settings_connection (active, new_connection);
-
-		if (_internal_activate_generic (self, active, &local)) {
-			nm_settings_connection_update (new_connection,
-			                               NULL,
-			                               NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK,
-			                               NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION | NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED,
-			                               "add-and-activate",
-			                               NULL);
-			g_dbus_method_invocation_return_value (
-			    context,
-			    g_variant_new ("(oo)",
-			                   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,
-			                            NULL,
-			                            nm_active_connection_get_subject (active),
-			                            NULL);
-			return;
-		}
+	nm_utils_user_data_unpack (user_data, &self, &active, &persist_ptr, &async_op_type_ptr);
+	persist = GPOINTER_TO_INT (persist_ptr);
+	async_op_type = GPOINTER_TO_INT (async_op_type_ptr);
+
+	if (error)
+		goto fail;
+
+	nm_active_connection_set_settings_connection (active, new_connection);
+
+	if (!_internal_activate_generic (self, active, &local)) {
 		error = local;
+		goto fail;
+	}
+
+	nm_settings_connection_update (new_connection,
+	                               NULL,
+	                               persist,
+	                               NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION | NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED,
+	                               "add-and-activate",
+	                               NULL);
+
+	if (async_op_type == ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE) {
+		result_floating = g_variant_new ("(oo)",
+		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)),
+		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (active)));
+	} else {
+		result_floating = g_variant_new ("(oo@a{sv})",
+		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)),
+		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (active)),
+		                                 g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
 	}
+	g_dbus_method_invocation_return_value (context, result_floating);
 
+	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE,
+	                            nm_active_connection_get_settings_connection (active),
+	                            TRUE,
+	                            NULL,
+	                            nm_active_connection_get_subject (active),
+	                            NULL);
+	return;
+
+fail:
 	nm_assert (error);
 
 	nm_active_connection_set_state_fail (active,
@@ -5163,9 +5349,11 @@ activation_add_done (NMSettings *settings,
 
 static void
 _add_and_activate_auth_done (NMManager *self,
+                             AsyncOpType async_op_type,
                              NMActiveConnection *active,
                              NMConnection *connection,
                              GDBusMethodInvocation *invocation,
+                             NMSettingsConnectionPersistMode persist,
                              gboolean success,
                              const char *error_desc)
 {
@@ -5198,7 +5386,9 @@ _add_and_activate_auth_done (NMManager *self,
 	                                 invocation,
 	                                 activation_add_done,
 	                                 nm_utils_user_data_pack (self,
-	                                                          g_object_ref (active)));
+	                                                          g_object_ref (active),
+	                                                          GINT_TO_POINTER (persist),
+	                                                          GINT_TO_POINTER (async_op_type)));
 }
 
 static void
@@ -5213,17 +5403,79 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 	NMManager *self = NM_MANAGER (obj);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	gs_unref_object NMConnection *incompl_conn = NULL;
-	NMActiveConnection *active = NULL;
+	gs_unref_object NMActiveConnection *active = NULL;
 	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
 	NMDevice *device = NULL;
 	gboolean is_vpn = FALSE;
 	gs_unref_variant GVariant *settings = NULL;
+	gs_unref_variant GVariant *options = NULL;
 	const char *device_path;
 	const char *specific_object_path;
 	gs_free NMConnection **conns = NULL;
+	NMSettingsConnectionPersistMode persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK;
+	gboolean bind_dbus_client = FALSE;
+	AsyncOpType async_op_type;
 
-	g_variant_get (parameters, "(@a{sa{sv}}&o&o)", &settings, &device_path, &specific_object_path);
+	if (nm_streq (method_info->parent.name, "AddAndActivateConnection2")) {
+		async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2;
+		g_variant_get (parameters, "(@a{sa{sv}}&o&o@a{sv})", &settings, &device_path, &specific_object_path, &options);
+	} else {
+		nm_assert (nm_streq (method_info->parent.name, "AddAndActivateConnection"));
+		async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE;
+		g_variant_get (parameters, "(@a{sa{sv}}&o&o)", &settings, &device_path, &specific_object_path);
+	}
+
+	if (options) {
+		GVariantIter iter;
+		const char *option_name;
+		GVariant *option_value;
+
+		g_variant_iter_init (&iter, options);
+		while (g_variant_iter_next (&iter, "{&sv}", &option_name, &option_value)) {
+			gs_unref_variant GVariant *option_value_free = NULL;
+			const char *s;
+
+			option_value_free = option_value;
+
+			if (   nm_streq (option_name, "persist")
+			    && g_variant_is_of_type (option_value, G_VARIANT_TYPE_STRING)) {
+				s = g_variant_get_string (option_value, NULL);
+
+				if (nm_streq (s, "volatile"))
+					persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_ONLY;
+				else if (nm_streq (s, "memory"))
+					persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
+				else if (nm_streq (s, "disk"))
+					persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK;
+				else {
+					error = g_error_new_literal (NM_MANAGER_ERROR,
+					                             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+					                             "Option \"persist\" must be one of \"volatile\", \"memory\" or \"disk\"");
+					goto error;
+				}
+			} else if (   nm_streq (option_name, "bind-activation")
+			           && g_variant_is_of_type (option_value, G_VARIANT_TYPE_STRING)) {
+				s = g_variant_get_string (option_value, NULL);
+
+				if (nm_streq (s, "dbus-client"))
+					bind_dbus_client = TRUE;
+				else if (nm_streq (s, "none"))
+					bind_dbus_client = FALSE;
+				else {
+					error = g_error_new_literal (NM_MANAGER_ERROR,
+					                             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+					                             "Option \"bind-activation\" must be one of \"dbus-client\" or \"none\"");
+					goto error;
+				}
+			} else {
+				error = g_error_new_literal (NM_MANAGER_ERROR,
+				                             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
+				                             "Unknown extra option passed");
+				goto error;
+			}
+		}
+	}
 
 	specific_object_path = nm_utils_dbus_normalize_object_path (specific_object_path);
 	device_path = nm_utils_dbus_normalize_object_path (device_path);
@@ -5291,17 +5543,28 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 	                                 subject,
 	                                 NM_ACTIVATION_TYPE_MANAGED,
 	                                 NM_ACTIVATION_REASON_USER_REQUEST,
+	                                 _activation_bind_lifetime_to_profile_visibility (subject),
 	                                 &error);
 	if (!active)
 		goto error;
 
+	if (bind_dbus_client) {
+		NMKeepAlive *keep_alive;
+
+		keep_alive = nm_active_connection_get_keep_alive (active);
+		nm_keep_alive_set_dbus_client_watch (keep_alive, dbus_connection, sender);
+		nm_keep_alive_arm (keep_alive);
+	}
+
 	nm_active_connection_authorize (active,
 	                                incompl_conn,
 	                                _async_op_complete_ac_auth_cb,
 	                                _async_op_data_new_ac_auth_add_and_activate (self,
+	                                                                             async_op_type,
 	                                                                             active,
 	                                                                             invocation,
-	                                                                             incompl_conn));
+	                                                                             incompl_conn,
+	                                                                             persist));
 
 	/* we passed the pointers on to _async_op_data_new_ac_auth_add_and_activate() */
 	g_steal_pointer (&incompl_conn);
@@ -5321,31 +5584,26 @@ nm_manager_deactivate_connection (NMManager *manager,
                                   NMDeviceStateReason reason,
                                   GError **error)
 {
-	gboolean success = FALSE;
-
 	if (NM_IS_VPN_CONNECTION (active)) {
 		NMActiveConnectionStateReason vpn_reason = NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED;
 
 		if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_CONNECTION_REMOVED)
 			vpn_reason = NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED;
 
-		if (nm_vpn_connection_deactivate (NM_VPN_CONNECTION (active), vpn_reason, FALSE))
-			success = TRUE;
-		else
+		if (!nm_vpn_connection_deactivate (NM_VPN_CONNECTION (active), vpn_reason, FALSE)) {
 			g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE,
 			                     "The VPN connection was not active.");
+			return FALSE;
+		}
 	} else {
-		g_assert (NM_IS_ACT_REQUEST (active));
-		nm_device_state_changed (nm_active_connection_get_device (active),
-		                         NM_DEVICE_STATE_DEACTIVATING,
-		                         reason);
-		success = TRUE;
+		nm_assert (NM_IS_ACT_REQUEST (active));
+		nm_device_disconnect_active_connection (active,
+		                                        reason,
+		                                        NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN);
 	}
 
-	if (success)
-		_notify (manager, PROP_ACTIVE_CONNECTIONS);
-
-	return success;
+	_notify (manager, PROP_ACTIVE_CONNECTIONS);
+	return TRUE;
 }
 
 static void
@@ -6171,6 +6429,12 @@ check_connectivity_auth_done_cb (NMAuthChain *chain,
 
 	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
 		if (nm_device_check_connectivity (device,
+		                                  AF_INET,
+		                                  device_connectivity_done,
+		                                  data))
+			data->remaining++;
+		if (nm_device_check_connectivity (device,
+		                                  AF_INET6,
 		                                  device_connectivity_done,
 		                                  data))
 			data->remaining++;
@@ -6573,6 +6837,7 @@ _dbus_set_property_auth_cb (NMAuthChain *chain,
 	gs_unref_object NMManager *self = handle_data->self;
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthCallResult result;
+	gs_free_error GError *local = NULL;
 	const char *error_name = NULL;
 	const char *error_message = NULL;
 	GValue gvalue;
@@ -6610,7 +6875,10 @@ _dbus_set_property_auth_cb (NMAuthChain *chain,
 	}
 
 	g_dbus_gvariant_to_gvalue (value, &gvalue);
-	g_object_set_property (G_OBJECT (obj), property_info->property_name, &gvalue);
+	if (!nm_g_object_set_property (G_OBJECT (obj), property_info->property_name, &gvalue, &local)) {
+		error_name = "org.freedesktop.DBus.Error.InvalidArgs";
+		error_message = local->message;
+	}
 	g_value_unset (&gvalue);
 
 out:
@@ -6921,10 +7189,10 @@ rfkill_change (NMManager *self, const char *desc, RfKillType rtype, gboolean ena
 	int fd;
 	struct rfkill_event event;
 	ssize_t len;
+	int errsv;
 
 	g_return_if_fail (rtype == RFKILL_TYPE_WLAN || rtype == RFKILL_TYPE_WWAN);
 
-	errno = 0;
 	fd = open ("/dev/rfkill", O_RDWR | O_CLOEXEC);
 	if (fd < 0) {
 		if (errno == EACCES)
@@ -6955,14 +7223,15 @@ rfkill_change (NMManager *self, const char *desc, RfKillType rtype, gboolean ena
 
 	len = write (fd, &event, sizeof (event));
 	if (len < 0) {
-		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change WiFi killswitch state: (%d) %s",
-		       desc, errno, g_strerror (errno));
+		errsv = errno;
+		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change Wi-Fi killswitch state: (%d) %s",
+		       desc, errsv, nm_strerror_native (errsv));
 	} else if (len == sizeof (event)) {
 		_LOGI (LOGD_RFKILL, "rfkill: %s hardware radio set %s",
 		       desc, enabled ? "enabled" : "disabled");
 	} else {
 		/* Failed to write full structure */
-		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change WiFi killswitch state", desc);
+		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change Wi-Fi killswitch state", desc);
 	}
 
 	nm_close (fd);
@@ -7159,7 +7428,7 @@ constructed (GObject *object)
 	                  G_CALLBACK (rfkill_manager_rfkill_changed_cb),
 	                  self);
 
-	/* Force kernel WiFi/WWAN rfkill state to follow NM saved WiFi/WWAN state
+	/* Force kernel Wi-Fi/WWAN rfkill state to follow NM saved Wi-Fi/WWAN state
 	 * in case the BIOS doesn't save rfkill state, and to be consistent with user
 	 * changes to the WirelessEnabled/WWANEnabled properties which toggle kernel
 	 * rfkill.
@@ -7192,7 +7461,7 @@ nm_manager_init (NMManager *self)
 	priv->radio_states[RFKILL_TYPE_WLAN].key = NM_CONFIG_STATE_PROPERTY_WIFI_ENABLED;
 	priv->radio_states[RFKILL_TYPE_WLAN].prop = NM_MANAGER_WIRELESS_ENABLED;
 	priv->radio_states[RFKILL_TYPE_WLAN].hw_prop = NM_MANAGER_WIRELESS_HARDWARE_ENABLED;
-	priv->radio_states[RFKILL_TYPE_WLAN].desc = "WiFi";
+	priv->radio_states[RFKILL_TYPE_WLAN].desc = "Wi-Fi";
 	priv->radio_states[RFKILL_TYPE_WLAN].rtype = RFKILL_TYPE_WLAN;
 
 	priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = TRUE;
@@ -7388,7 +7657,7 @@ set_property (GObject *object, guint prop_id,
 		                            g_value_get_boolean (value));
 		break;
 	case PROP_WIMAX_ENABLED:
-		/* WIMAX is depreacted. This does nothing. */
+		/* WIMAX is deprecated. This does nothing. */
 		break;
 	case PROP_CONNECTIVITY_CHECK_ENABLED:
 		nm_config_set_connectivity_check_enabled (priv->config,
@@ -7644,6 +7913,23 @@ static const NMDBusInterfaceInfoExtended interface_info_manager = {
 			),
 			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
 				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"AddAndActivateConnection2",
+					.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"),
+						NM_DEFINE_GDBUS_ARG_INFO ("options",         "a{sv}"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("path",              "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("result",            "a{sv}"),
+					),
+				),
+				.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"),
diff --git a/src/nm-manager.h b/src/nm-manager.h
index 11cba1a5..ecb4b017 100644
--- a/src/nm-manager.h
+++ b/src/nm-manager.h
@@ -97,6 +97,19 @@ const CList * nm_manager_get_active_connections        (NMManager *manager);
 	    }); \
 	    iter = c_list_entry (iter->active_connections_lst.next, NMActiveConnection, active_connections_lst))
 
+#define nm_manager_for_each_active_connection_safe(manager, iter, tmp_list, iter_safe) \
+	for (tmp_list = nm_manager_get_active_connections (manager), \
+	     iter_safe = tmp_list->next; \
+	     ({ \
+	        if (iter_safe != tmp_list) { \
+	            iter = c_list_entry (iter_safe, NMActiveConnection, active_connections_lst); \
+	            iter_safe = iter_safe->next; \
+	        } else \
+	            iter = NULL; \
+	        (iter != NULL); \
+	     }); \
+	    )
+
 NMSettingsConnection **nm_manager_get_activatable_connections (NMManager *manager,
                                                                gboolean for_auto_activation,
                                                                gboolean sort,
@@ -121,6 +134,19 @@ const CList *       nm_manager_get_devices             (NMManager *manager);
 	    }); \
 	    iter = c_list_entry (iter->devices_lst.next, NMDevice, devices_lst))
 
+#define nm_manager_for_each_device_safe(manager, iter, tmp_list, iter_safe) \
+	for (tmp_list = nm_manager_get_devices (manager), \
+	     iter_safe = tmp_list->next; \
+	     ({ \
+	        if (iter_safe != tmp_list) { \
+	            iter = c_list_entry (iter_safe, NMDevice, devices_lst); \
+	            iter_safe = iter_safe->next; \
+	        } else \
+	            iter = NULL; \
+	        (iter != NULL); \
+	     }); \
+	    )
+
 NMDevice *          nm_manager_get_device_by_ifindex   (NMManager *manager,
                                                         int ifindex);
 NMDevice *          nm_manager_get_device_by_path      (NMManager *manager,
@@ -149,6 +175,7 @@ NMActiveConnection *nm_manager_activate_connection     (NMManager *manager,
                                                         NMAuthSubject *subject,
                                                         NMActivationType activation_type,
                                                         NMActivationReason activation_reason,
+                                                        NMActivationStateFlags initial_state_flags,
                                                         GError **error);
 
 gboolean            nm_manager_deactivate_connection   (NMManager *manager,
@@ -174,4 +201,6 @@ void nm_manager_dbus_set_property_handle (NMDBusObject *obj,
                                           GVariant *value,
                                           gpointer user_data);
 
+NMMetered nm_manager_get_metered (NMManager *self);
+
 #endif /* __NETWORKMANAGER_MANAGER_H__ */
diff --git a/src/nm-pacrunner-manager.c b/src/nm-pacrunner-manager.c
index af43edbe..b9881f76 100644
--- a/src/nm-pacrunner-manager.c
+++ b/src/nm-pacrunner-manager.c
@@ -173,6 +173,7 @@ get_ip4_domains (GPtrArray *domains, NMIP4Config *ip4)
 	const NMPlatformIP4Address *address;
 	const NMPlatformIP4Route *routes;
 	guint i;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	/* Extract searches */
 	for (i = 0; i < nm_ip4_config_get_num_searches (ip4); i++)
@@ -186,7 +187,7 @@ get_ip4_domains (GPtrArray *domains, NMIP4Config *ip4)
 
 	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &address) {
 		cidr = g_strdup_printf ("%s/%u",
-		                        nm_utils_inet4_ntop (address->address, NULL),
+		                        nm_utils_inet4_ntop (address->address, sbuf),
 		                        address->plen);
 		g_ptr_array_add (domains, cidr);
 	}
@@ -195,7 +196,7 @@ get_ip4_domains (GPtrArray *domains, NMIP4Config *ip4)
 		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (routes))
 			continue;
 		cidr = g_strdup_printf ("%s/%u",
-		                        nm_utils_inet4_ntop (routes->network, NULL),
+		                        nm_utils_inet4_ntop (routes->network, sbuf),
 		                        routes->plen);
 		g_ptr_array_add (domains, cidr);
 	}
@@ -209,6 +210,7 @@ get_ip6_domains (GPtrArray *domains, NMIP6Config *ip6)
 	const NMPlatformIP6Address *address;
 	const NMPlatformIP6Route *routes;
 	guint i;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	/* Extract searches */
 	for (i = 0; i < nm_ip6_config_get_num_searches (ip6); i++)
@@ -221,7 +223,7 @@ get_ip6_domains (GPtrArray *domains, NMIP6Config *ip6)
 	/* Add addresses and routes in CIDR form */
 	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &address) {
 		cidr = g_strdup_printf ("%s/%u",
-		                        nm_utils_inet6_ntop (&address->address, NULL),
+		                        nm_utils_inet6_ntop (&address->address, sbuf),
 		                        address->plen);
 		g_ptr_array_add (domains, cidr);
 	}
@@ -230,7 +232,7 @@ get_ip6_domains (GPtrArray *domains, NMIP6Config *ip6)
 		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (routes))
 			continue;
 		cidr = g_strdup_printf ("%s/%u",
-		                        nm_utils_inet6_ntop (&routes->network, NULL),
+		                        nm_utils_inet6_ntop (&routes->network, sbuf),
 		                        routes->plen);
 		g_ptr_array_add (domains, cidr);
 	}
@@ -271,7 +273,7 @@ pacrunner_send_done (GObject *source, GAsyncResult *res, gpointer user_data)
 		g_variant_get (variant, "(&o)", &path);
 
 		if (c_list_is_empty (&config->lst)) {
-			_LOG2D (config, "sent (%s), but destory it right away", path);
+			_LOG2D (config, "sent (%s), but destroy it right away", path);
 			g_dbus_proxy_call (priv->pacrunner,
 			                   "DestroyProxyConfiguration",
 			                   g_variant_new ("(o)", path),
diff --git a/src/nm-policy.c b/src/nm-policy.c
index a5960105..540f4065 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -23,13 +23,12 @@
 
 #include "nm-policy.h"
 
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 #include <netdb.h>
 
 #include "NetworkManagerUtils.h"
 #include "nm-act-request.h"
+#include "nm-keep-alive.h"
 #include "devices/nm-device.h"
 #include "nm-setting-ip4-config.h"
 #include "nm-setting-connection.h"
@@ -150,6 +149,25 @@ static NMDevice *get_default_device (NMPolicy *self, int addr_family);
 
 /*****************************************************************************/
 
+static void
+_dns_manager_set_ip_config (NMDnsManager *dns_manager,
+                            NMIPConfig *ip_config,
+                            NMDnsIPConfigType ip_config_type,
+                            NMDevice *device)
+{
+	if (   NM_IN_SET (ip_config_type, NM_DNS_IP_CONFIG_TYPE_DEFAULT,
+	                                  NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE)
+	    && device
+	    && nm_device_get_route_metric_default (nm_device_get_device_type (device)) == NM_VPN_ROUTE_METRIC_DEFAULT) {
+		/* some device types are inherently VPN. */
+		ip_config_type = NM_DNS_IP_CONFIG_TYPE_VPN;
+	}
+
+	nm_dns_manager_set_ip_config (dns_manager, ip_config, ip_config_type);
+}
+
+/*****************************************************************************/
+
 typedef struct {
 	NMPlatformIP6Address prefix;
 	NMDevice *device;             /* The requesting ("uplink") device */
@@ -178,9 +196,10 @@ static void
 clear_ip6_prefix_delegation (gpointer data)
 {
 	IP6PrefixDelegation *delegation = data;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	_LOGD (LOGD_IP6, "ipv6-pd: undelegating prefix %s/%d",
-	       nm_utils_inet6_ntop (&delegation->prefix.address, NULL),
+	       nm_utils_inet6_ntop (&delegation->prefix.address, sbuf),
 	       delegation->prefix.plen);
 
 	g_hash_table_foreach (delegation->subnets, _clear_ip6_subnet, NULL);
@@ -214,13 +233,14 @@ ip6_subnet_from_delegation (IP6PrefixDelegation *delegation, NMDevice *device)
 {
 	NMPlatformIP6Address *subnet;
 	int ifindex = nm_device_get_ifindex (device);
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	subnet = g_hash_table_lookup (delegation->subnets, GINT_TO_POINTER (ifindex));
 	if (!subnet) {
 		/* Check for out-of-prefixes condition. */
 		if (delegation->next_subnet >= (1 << (64 - delegation->prefix.plen))) {
 			_LOGD (LOGD_IP6, "ipv6-pd: no more prefixes in %s/%d",
-			       nm_utils_inet6_ntop (&delegation->prefix.address, NULL),
+			       nm_utils_inet6_ntop (&delegation->prefix.address, sbuf),
 			       delegation->prefix.plen);
 			return FALSE;
 		}
@@ -248,7 +268,7 @@ ip6_subnet_from_delegation (IP6PrefixDelegation *delegation, NMDevice *device)
 	subnet->preferred = delegation->prefix.preferred;
 
 	_LOGD (LOGD_IP6, "ipv6-pd: %s allocated from a /%d prefix on %s",
-	       nm_utils_inet6_ntop (&subnet->address, NULL),
+	       nm_utils_inet6_ntop (&subnet->address, sbuf),
 	       delegation->prefix.plen,
 	       nm_device_get_iface (device));
 
@@ -319,9 +339,10 @@ device_ip6_prefix_delegated (NMDevice *device,
 	guint i;
 	const CList *tmp_list;
 	NMActiveConnection *ac;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	_LOGI (LOGD_IP6, "ipv6-pd: received a prefix %s/%d from %s",
-	       nm_utils_inet6_ntop (&prefix->address, NULL),
+	       nm_utils_inet6_ntop (&prefix->address, sbuf),
 	       prefix->plen,
 	       nm_device_get_iface (device));
 
@@ -504,15 +525,15 @@ settings_set_hostname_cb (const char *hostname,
 	NMPolicy *self = NM_POLICY (user_data);
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 	int ret = 0;
+	int errsv;
 
 	if (!result) {
 		_LOGT (LOGD_DNS, "set-hostname: hostname set via dbus failed, fallback to \"sethostname\"");
 		ret = sethostname (hostname, strlen (hostname));
 		if (ret != 0) {
-			int errsv = errno;
-
+			errsv = errno;
 			_LOGW (LOGD_DNS, "set-hostname: couldn't set the system hostname to '%s': (%d) %s",
-			       hostname, errsv, strerror (errsv));
+			       hostname, errsv, nm_strerror_native (errsv));
 			if (errsv == EPERM)
 				_LOGW (LOGD_DNS, "set-hostname: you should use hostnamed when systemd hardening is in effect!");
 		}
@@ -531,6 +552,7 @@ _get_hostname (NMPolicy *self)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 	char *hostname = NULL;
+	int errsv;
 
 	/* If there is an in-progress hostname change, return
 	 * the last hostname set as would be set soon...
@@ -549,10 +571,9 @@ _get_hostname (NMPolicy *self)
 	/* ...or retrieve it by yourself */
 	hostname = g_malloc (HOST_NAME_BUFSIZE);
 	if (gethostname (hostname, HOST_NAME_BUFSIZE -1) != 0) {
-		int errsv = errno;
-
+		errsv = errno;
 		_LOGT (LOGD_DNS, "get-hostname: couldn't get the system hostname: (%d) %s",
-		       errsv, g_strerror (errsv));
+		       errsv, nm_strerror_native (errsv));
 		g_free (hostname);
 		return NULL;
 	}
@@ -1088,19 +1109,21 @@ update_ip_dns (NMPolicy *self, int addr_family)
 	gpointer ip_config;
 	const char *ip_iface = NULL;
 	NMVpnConnection *vpn = NULL;
+	NMDevice *device = NULL;
 
 	nm_assert_addr_family (addr_family);
 
-	ip_config = get_best_ip_config (self, addr_family, &ip_iface, NULL, NULL, &vpn);
+	ip_config = get_best_ip_config (self, addr_family, &ip_iface, NULL, &device, &vpn);
 	if (ip_config) {
 		/* Tell the DNS manager this config is preferred by re-adding it with
 		 * a different IP config type.
 		 */
-		nm_dns_manager_set_ip_config (NM_POLICY_GET_PRIVATE (self)->dns_manager,
-		                              ip_config,
-		                              vpn
-		                                ? NM_DNS_IP_CONFIG_TYPE_VPN
-		                                : NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE);
+		_dns_manager_set_ip_config (NM_POLICY_GET_PRIVATE (self)->dns_manager,
+		                            ip_config,
+		                            vpn
+		                            ? NM_DNS_IP_CONFIG_TYPE_VPN
+		                            : NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE,
+		                            device);
 	}
 
 	if (addr_family == AF_INET6)
@@ -1283,6 +1306,7 @@ auto_activate_device (NMPolicy *self,
 	                                     subject,
 	                                     NM_ACTIVATION_TYPE_MANAGED,
 	                                     NM_ACTIVATION_REASON_AUTOCONNECT,
+	                                     NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
 	                                     &error);
 	if (!ac) {
 		_LOGI (LOGD_DEVICE, "connection '%s' auto-activation failed: %s",
@@ -1673,9 +1697,14 @@ activate_secondary_connections (NMPolicy *self,
 	GError *error = NULL;
 	guint32 i;
 	gboolean success = TRUE;
+	NMActivationStateFlags initial_state_flags;
 
 	s_con = nm_connection_get_setting_connection (connection);
-	nm_assert (s_con);
+	nm_assert (NM_IS_SETTING_CONNECTION (s_con));
+
+	/* we propagate the activation's state flags. */
+	initial_state_flags =   nm_device_get_activation_state_flags (device)
+	                      & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY;
 
 	for (i = 0; i < nm_setting_connection_get_num_secondaries (s_con); i++) {
 		NMSettingsConnection *sett_conn;
@@ -1699,7 +1728,6 @@ activate_secondary_connections (NMPolicy *self,
 		}
 
 		req = nm_device_get_act_request (device);
-		g_assert (req);
 
 		_LOGD (LOGD_DEVICE, "activating secondary connection '%s (%s)' for base connection '%s (%s)'",
 		       nm_settings_connection_get_id (sett_conn), sec_uuid,
@@ -1712,6 +1740,7 @@ activate_secondary_connections (NMPolicy *self,
 		                                     nm_active_connection_get_subject (NM_ACTIVE_CONNECTION (req)),
 		                                     NM_ACTIVATION_TYPE_MANAGED,
 		                                     nm_active_connection_get_activation_reason (NM_ACTIVE_CONNECTION (req)),
+		                                     initial_state_flags,
 		                                     &error);
 		if (ac)
 			secondary_ac_list = g_slist_append (secondary_ac_list, g_object_ref (ac));
@@ -1841,10 +1870,10 @@ device_state_changed (NMDevice *device,
 
 		ip4_config = nm_device_get_ip4_config (device);
 		if (ip4_config)
-			nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip4_config), NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+			_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip4_config), NM_DNS_IP_CONFIG_TYPE_DEFAULT, device);
 		ip6_config = nm_device_get_ip6_config (device);
 		if (ip6_config)
-			nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip6_config), NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+			_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip6_config), NM_DNS_IP_CONFIG_TYPE_DEFAULT, device);
 
 		update_routing_and_dns (self, FALSE);
 
@@ -1872,8 +1901,8 @@ device_state_changed (NMDevice *device,
 			if (blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE) {
 				_LOGD (LOGD_DEVICE, "blocking autoconnect of connection '%s': %s",
 				       nm_settings_connection_get_id (sett_conn),
-				       NM_UTILS_LOOKUP_STR (nm_device_state_reason_to_str,
-				                            nm_device_state_reason_check (reason)));
+				       NM_UTILS_LOOKUP_STR_A (nm_device_state_reason_to_str,
+				                              nm_device_state_reason_check (reason)));
 				nm_settings_connection_autoconnect_blocked_reason_set (sett_conn, blocked_reason, TRUE);
 			}
 		}
@@ -1966,12 +1995,12 @@ device_ip_config_changed (NMDevice *device,
 	/* 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.
+	 * Prevents unnecessary changes to DNS information.
 	 */
 	if (nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) {
 		if (old_config != new_config) {
 			if (new_config)
-				nm_dns_manager_set_ip_config (priv->dns_manager, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+				_dns_manager_set_ip_config (priv->dns_manager, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT, device);
 			if (old_config)
 				nm_dns_manager_set_ip_config (priv->dns_manager, old_config, NM_DNS_IP_CONFIG_TYPE_REMOVED);
 		}
@@ -2158,6 +2187,8 @@ vpn_connection_retry_after_failure (NMVpnConnection *vpn, NMPolicy *self)
 	                                     nm_active_connection_get_subject (ac),
 	                                     NM_ACTIVATION_TYPE_MANAGED,
 	                                     nm_active_connection_get_activation_reason (ac),
+	                                     (  nm_active_connection_get_state_flags (ac)
+	                                      & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY),
 	                                     &error)) {
 		_LOGW (LOGD_DEVICE, "VPN '%s' reconnect failed: %s",
 		       nm_settings_connection_get_id (connection),
@@ -2180,12 +2211,47 @@ active_connection_state_changed (NMActiveConnection *active,
 }
 
 static void
+active_connection_keep_alive_changed (NMKeepAlive *keep_alive,
+                                      GParamSpec *pspec,
+                                      NMPolicy *self)
+{
+	NMPolicyPrivate *priv;
+	NMActiveConnection *ac;
+	GError *error = NULL;
+
+	nm_assert (NM_IS_POLICY (self));
+	nm_assert (NM_IS_KEEP_ALIVE (keep_alive));
+	nm_assert (NM_IS_ACTIVE_CONNECTION (nm_keep_alive_get_owner (keep_alive)));
+
+	if (nm_keep_alive_is_alive (keep_alive))
+		return;
+
+	ac = nm_keep_alive_get_owner (keep_alive);
+
+	if (nm_active_connection_get_state (ac) > NM_ACTIVE_CONNECTION_STATE_ACTIVATED)
+		return;
+
+	priv = NM_POLICY_GET_PRIVATE (self);
+
+	if (!nm_manager_deactivate_connection (priv->manager,
+	                                       ac,
+	                                       NM_DEVICE_STATE_REASON_CONNECTION_REMOVED,
+	                                       &error)) {
+		_LOGW (LOGD_DEVICE, "connection '%s' is no longer kept alive, but error deactivating it: %s",
+		       nm_active_connection_get_settings_connection_id (ac),
+		       error->message);
+		g_clear_error (&error);
+	}
+}
+
+static void
 active_connection_added (NMManager *manager,
                          NMActiveConnection *active,
                          gpointer user_data)
 {
 	NMPolicyPrivate *priv = user_data;
 	NMPolicy *self = _PRIV_TO_SELF (priv);
+	NMKeepAlive *keep_alive;
 
 	if (NM_IS_VPN_CONNECTION (active)) {
 		g_signal_connect (active, NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED,
@@ -2196,9 +2262,18 @@ active_connection_added (NMManager *manager,
 		                  self);
 	}
 
+	keep_alive = nm_active_connection_get_keep_alive (active);
+
+	nm_keep_alive_arm (keep_alive);
+
 	g_signal_connect (active, "notify::" NM_ACTIVE_CONNECTION_STATE,
 	                  G_CALLBACK (active_connection_state_changed),
 	                  self);
+	g_signal_connect (keep_alive,
+	                  "notify::" NM_KEEP_ALIVE_ALIVE,
+	                  G_CALLBACK (active_connection_keep_alive_changed),
+	                  self);
+	active_connection_keep_alive_changed (keep_alive, NULL, self);
 }
 
 static void
@@ -2218,6 +2293,9 @@ active_connection_removed (NMManager *manager,
 	g_signal_handlers_disconnect_by_func (active,
 	                                      active_connection_state_changed,
 	                                      self);
+	g_signal_handlers_disconnect_by_func (nm_active_connection_get_keep_alive (active),
+	                                      active_connection_keep_alive_changed,
+	                                      self);
 }
 
 /*****************************************************************************/
@@ -2355,12 +2433,12 @@ _deactivate_if_active (NMPolicy *self, NMSettingsConnection *connection)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 	NMActiveConnection *ac;
-	const CList *tmp_list;
+	const CList *tmp_list, *tmp_safe;
 	GError *error = NULL;
 
 	nm_assert (NM_IS_SETTINGS_CONNECTION (connection));
 
-	nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
+	nm_manager_for_each_active_connection_safe (priv->manager, ac, tmp_list, tmp_safe) {
 
 		if (   nm_active_connection_get_settings_connection (ac) == connection
 		    && (nm_active_connection_get_state (ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) {
@@ -2401,8 +2479,7 @@ connection_flags_changed (NMSettings *settings,
 	                  NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) {
 		if (!nm_settings_connection_autoconnect_is_blocked (connection))
 			schedule_activate_all (self);
-	} else
-		_deactivate_if_active (self, connection);
+	}
 }
 
 static void
diff --git a/src/nm-rfkill-manager.c b/src/nm-rfkill-manager.c
index a7bc694a..534ad369 100644
--- a/src/nm-rfkill-manager.c
+++ b/src/nm-rfkill-manager.c
@@ -22,7 +22,6 @@
 
 #include "nm-rfkill-manager.h"
 
-#include <string.h>
 #include <libudev.h>
 
 #include "nm-utils/nm-udev-utils.h"
@@ -82,7 +81,7 @@ static const char *
 rfkill_type_to_desc (RfKillType rtype)
 {
 	if (rtype == 0)
-		return "WiFi";
+		return "Wi-Fi";
 	else if (rtype == 1)
 		return "WWAN";
 	else if (rtype == 2)
diff --git a/src/nm-session-monitor.c b/src/nm-session-monitor.c
index b67c537c..e8c25fd6 100644
--- a/src/nm-session-monitor.c
+++ b/src/nm-session-monitor.c
@@ -24,8 +24,6 @@
 #include "nm-session-monitor.h"
 
 #include <pwd.h>
-#include <errno.h>
-#include <string.h>
 #include <sys/stat.h>
 
 #if SESSION_TRACKING_SYSTEMD && SESSION_TRACKING_ELOGIND
@@ -206,13 +204,15 @@ static gboolean
 ck_update_cache (NMSessionMonitor *monitor)
 {
 	struct stat statbuf;
+	int errsv;
 
 	if (!monitor->ck.cache)
 		return FALSE;
 
 	/* Check the database file */
 	if (stat (CKDB_PATH, &statbuf) != 0) {
-		_LOGE ("failed to check ConsoleKit timestamp: %s", strerror (errno));
+		errsv = errno;
+		_LOGE ("failed to check ConsoleKit timestamp: %s", nm_strerror_native (errsv));
 		return FALSE;
 	}
 	if (statbuf.st_mtime == monitor->ck.timestamp)
diff --git a/src/nm-sleep-monitor.c b/src/nm-sleep-monitor.c
index 54d75773..7e0ebe6c 100644
--- a/src/nm-sleep-monitor.c
+++ b/src/nm-sleep-monitor.c
@@ -21,8 +21,6 @@
 
 #include "nm-sleep-monitor.h"
 
-#include <errno.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <gio/gunixfdlist.h>
 
diff --git a/src/nm-types.h b/src/nm-types.h
index a0a7f620..b6b49028 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -37,7 +37,6 @@ typedef struct _NMAuthSubject        NMAuthSubject;
 typedef struct _NMDBusManager        NMDBusManager;
 typedef struct _NMConfig             NMConfig;
 typedef struct _NMConfigData         NMConfigData;
-typedef struct _NMAcdManager         NMAcdManager;
 typedef struct _NMConnectivity       NMConnectivity;
 typedef struct _NMDevice             NMDevice;
 typedef struct _NMDhcp4Config        NMDhcp4Config;
@@ -52,6 +51,7 @@ typedef struct _NMPolicy             NMPolicy;
 typedef struct _NMRfkillManager      NMRfkillManager;
 typedef struct _NMPacrunnerManager   NMPacrunnerManager;
 typedef struct _NMSessionMonitor     NMSessionMonitor;
+typedef struct _NMKeepAlive          NMKeepAlive;
 typedef struct _NMSleepMonitor       NMSleepMonitor;
 typedef struct _NMLldpListener       NMLldpListener;
 typedef struct _NMConfigDeviceStateData NMConfigDeviceStateData;
@@ -152,6 +152,7 @@ typedef enum {
 	NM_LINK_TYPE_WIMAX,
 	NM_LINK_TYPE_WPAN,
 	NM_LINK_TYPE_6LOWPAN,
+	NM_LINK_TYPE_WIFI_P2P,
 
 	/* Software types */
 	NM_LINK_TYPE_BNEP = 0x10000,   /* Bluetooth Ethernet emulation */
diff --git a/src/org.freedesktop.NetworkManager.conf b/src/org.freedesktop.NetworkManager.conf
index fa74b280..720c090e 100644
--- a/src/org.freedesktop.NetworkManager.conf
+++ b/src/org.freedesktop.NetworkManager.conf
@@ -84,6 +84,8 @@
                        send_interface="org.freedesktop.NetworkManager.WiMax.Nsp"/>
                 <allow send_destination="org.freedesktop.NetworkManager"
                        send_interface="org.freedesktop.NetworkManager.AccessPoint"/>
+                <allow send_destination="org.freedesktop.NetworkManager"
+                       send_interface="org.freedesktop.NetworkManager.WifiP2PPeer"/>
 
                 <!-- Devices (read-only, no security required) -->
                 <allow send_destination="org.freedesktop.NetworkManager"
@@ -93,6 +95,8 @@
                 <allow send_destination="org.freedesktop.NetworkManager"
                        send_interface="org.freedesktop.NetworkManager.Device.Wireless"/>
                 <allow send_destination="org.freedesktop.NetworkManager"
+                       send_interface="org.freedesktop.NetworkManager.Device.WifiP2P"/>
+                <allow send_destination="org.freedesktop.NetworkManager"
                        send_interface="org.freedesktop.NetworkManager.Device"/>
 
                 <!-- Core stuff (read-only properties, no methods) -->
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index ef69b391..30466159 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -22,7 +22,6 @@
 
 #include "nm-fake-platform.h"
 
-#include <errno.h>
 #include <unistd.h>
 #include <netinet/icmp6.h>
 #include <netinet/in.h>
@@ -195,7 +194,7 @@ link_add_prepare (NMPlatform *platform,
 {
 	gboolean connected;
 
-	/* we must clear the driver, because platform cache want's to set it */
+	/* we must clear the driver, because platform cache wants to set it */
 	g_assert (obj_tmp->link.driver == g_intern_string (obj_tmp->link.driver));
 	obj_tmp->link.driver = NULL;
 
@@ -283,7 +282,7 @@ link_add_pre (NMPlatform *platform,
 	return device;
 }
 
-static gboolean
+static int
 link_add (NMPlatform *platform,
           const char *name,
           NMLinkType type,
@@ -335,7 +334,7 @@ link_add (NMPlatform *platform,
 	if (veth_peer)
 		link_changed (platform, device_veth, cache_op_veth, NULL);
 
-	return TRUE;
+	return 0;
 }
 
 static NMFakePlatformLink *
@@ -563,7 +562,7 @@ link_set_noarp (NMPlatform *platform, int ifindex)
 	return TRUE;
 }
 
-static NMPlatformError
+static int
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t len)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
@@ -572,10 +571,10 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t
 	if (   len == 0
 	    || len > NM_UTILS_HWADDR_LEN_MAX
 	    || !addr)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+		g_return_val_if_reached (-NME_BUG);
 
 	if (!device)
-		return NM_PLATFORM_ERROR_EXISTS;
+		return -NME_PL_EXISTS;
 
 	obj_tmp = nmp_object_clone (device->obj, FALSE);
 	obj_tmp->link.addr.len = len;
@@ -583,10 +582,10 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t
 	memcpy (obj_tmp->link.addr.data, addr, len);
 
 	link_set_obj (platform, device, obj_tmp);
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return 0;
 }
 
-static NMPlatformError
+static int
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
@@ -594,13 +593,13 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 
 	if (!device) {
 		_LOGE ("failure changing link: netlink error (No such device)");
-		return NM_PLATFORM_ERROR_EXISTS;
+		return -NME_PL_EXISTS;
 	}
 
 	obj_tmp = nmp_object_clone (device->obj, FALSE);
 	obj_tmp->link.mtu = mtu;
 	link_set_obj (platform, device, obj_tmp);
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return 0;
 }
 
 static const char *
@@ -1187,7 +1186,7 @@ object_delete (NMPlatform *platform, const NMPObject *obj)
 	return ipx_route_delete (platform, AF_UNSPEC, -1, obj);
 }
 
-static NMPlatformError
+static int
 ip_route_add (NMPlatform *platform,
               NMPNlmFlags flags,
               int addr_family,
@@ -1267,14 +1266,16 @@ ip_route_add (NMPlatform *platform,
 			}
 		}
 		if (!has_route_to_gw) {
+			char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
 			if (addr_family == AF_INET) {
 				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable",
-				             r->ifindex, nm_utils_inet4_ntop (r4->network, NULL), r->plen, r->metric);
+				             r->ifindex, nm_utils_inet4_ntop (r4->network, sbuf), r->plen, r->metric);
 			} else {
 				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip6-route '%d: %s/%d %d': Network Unreachable",
-				             r->ifindex, nm_utils_inet6_ntop (&r6->network, NULL), r->plen, r->metric);
+				             r->ifindex, nm_utils_inet6_ntop (&r6->network, sbuf), r->plen, r->metric);
 			}
-			return NM_PLATFORM_ERROR_UNSPECIFIED;
+			return -NME_UNSPEC;
 		}
 	}
 
@@ -1336,7 +1337,7 @@ ip_route_add (NMPlatform *platform,
 		}
 	}
 
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return 0;
 }
 
 /*****************************************************************************/
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index c0224fff..2f5c75b0 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -21,30 +21,30 @@
 
 #include "nm-linux-platform.h"
 
-#include <poll.h>
+#include <arpa/inet.h>
+#include <dlfcn.h>
 #include <endian.h>
-#include <errno.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#include <sys/ioctl.h>
 #include <fcntl.h>
-#include <dlfcn.h>
-#include <arpa/inet.h>
-#include <netinet/icmp6.h>
-#include <netinet/in.h>
+#include <libudev.h>
 #include <linux/ip.h>
 #include <linux/if_arp.h>
 #include <linux/if_link.h>
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
 #include <linux/ip6_tunnel.h>
-#include <libudev.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
 
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-setting-vlan.h"
 
 #include "nm-utils/nm-secret-utils.h"
+#include "nm-utils/nm-c-list.h"
 #include "nm-netlink.h"
 #include "nm-core-utils.h"
 #include "nmp-object.h"
@@ -186,6 +186,12 @@ G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
 #define WG_CMD_GET_DEVICE 0
 #define WG_CMD_SET_DEVICE 1
 
+#define WGDEVICE_F_REPLACE_PEERS               ((guint32) (1U << 0))
+
+#define WGPEER_F_REMOVE_ME                     ((guint32) (1U << 0))
+#define WGPEER_F_REPLACE_ALLOWEDIPS            ((guint32) (1U << 1))
+
+
 #define WGDEVICE_A_UNSPEC                      0
 #define WGDEVICE_A_IFINDEX                     1
 #define WGDEVICE_A_IFNAME                      2
@@ -315,13 +321,27 @@ typedef enum {
 } DelayedActionType;
 
 #define FOR_EACH_DELAYED_ACTION(iflags, flags_all) \
-	for ((iflags) = (DelayedActionType) 0x1LL; (iflags) <= DELAYED_ACTION_TYPE_MAX; (iflags) <<= 1) \
-		if (NM_FLAGS_ANY (flags_all, iflags))
+	for ((iflags) = (DelayedActionType) 0x1LL; \
+	     ({ \
+	         gboolean _good = FALSE; \
+	         \
+	         nm_assert (nm_utils_is_power_of_two (iflags)); \
+	         \
+	         while ((iflags) <= DELAYED_ACTION_TYPE_MAX) { \
+	             if (NM_FLAGS_ANY ((flags_all), (iflags))) { \
+	                 _good = TRUE; \
+	                 break; \
+	             } \
+	             (iflags) <<= 1; \
+	         } \
+	         _good; \
+	     }); \
+	     (iflags) <<= 1)
 
 typedef enum {
 	/* Negative values are errors from kernel. Add dummy member to
 	 * make enum signed. */
-	_WAIT_FOR_NL_RESPONSE_RESULT_SYSTEM_ERROR = -1,
+	_WAIT_FOR_NL_RESPONSE_RESULT_SYSTEM_ERROR = G_MININT,
 
 	WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN = 0,
 	WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK,
@@ -369,8 +389,8 @@ typedef struct {
 
 	bool pruning[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
 
-	bool sysctl_get_warned;
 	GHashTable *sysctl_get_prev_values;
+	CList sysctl_list;
 
 	NMUdevClient *udev_client;
 
@@ -447,14 +467,14 @@ G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
         if (nm_logging_enabled (__level, __domain)) { \
             int __errsv = (errsv); \
             \
-            /* The %m format specifier (GNU extension) would alread allow you to specify the error
+            /* The %m format specifier (GNU extension) would already allow you to specify the error
              * message conveniently (and nm_log would get that right too). But we don't want to depend
              * on that, so instead append the message at the end.
              * Currently users are expected not to use %m in the format string. */ \
             _LOG_print (__level, __domain, __errsv, self, \
                         _NM_UTILS_MACRO_FIRST (__VA_ARGS__) ": %s (%d)" \
                         _NM_UTILS_MACRO_REST (__VA_ARGS__), \
-                        g_strerror (__errsv), __errsv); \
+                        nm_strerror_native (__errsv), __errsv); \
         } \
     } G_STMT_END
 
@@ -474,14 +494,14 @@ static struct nl_sock *_genl_sock (NMLinuxPlatform *platform);
 
 /*****************************************************************************/
 
-static NMPlatformError
-wait_for_nl_response_to_plerr (WaitForNlResponseResult seq_result)
+static int
+wait_for_nl_response_to_nmerr (WaitForNlResponseResult seq_result)
 {
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
-		return NM_PLATFORM_ERROR_SUCCESS;
+		return 0;
 	if (seq_result < 0)
-		return (NMPlatformError) seq_result;
-	return NM_PLATFORM_ERROR_NETLINK;
+		return (int) seq_result;
+	return -NME_PL_NETLINK;
 }
 
 static const char *
@@ -505,7 +525,7 @@ wait_for_nl_response_to_string (WaitForNlResponseResult 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)),
+			                        nm_strerror_native (-((int) seq_result)),
 			                        errmsg ? " - " : "",
 			                        errmsg ?: "");
 		}
@@ -570,7 +590,7 @@ _support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
 
 	/* IFA_FLAGS is set for IPv4 and IPv6 addresses. It was added first to IPv6,
 	 * but if we encounter an IPv4 address with IFA_FLAGS, we surely have support. */
-	if (NM_IN_SET (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family, AF_INET, AF_INET6))
+	if (!NM_IN_SET (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family, AF_INET, AF_INET6))
 		return;
 
 	/* see if the nl_msg contains the IFA_FLAGS attribute. If it does,
@@ -1093,7 +1113,7 @@ _linktype_get_type (NMPlatform *platform,
  ******************************************************************/
 
 #define NLMSG_TAIL(nmsg) \
-    ((struct rtattr *) (((char *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+    ((struct rtattr *) (((char *) (nmsg)) + NLMSG_ALIGN ((nmsg)->nlmsg_len)))
 
 /* copied from iproute2's addattr_l(). */
 static gboolean
@@ -1147,31 +1167,29 @@ _parse_af_inet6 (NMPlatform *platform,
                  guint8 *out_addr_gen_mode_inv,
                  gboolean *out_addr_gen_mode_valid)
 {
-	static const struct nla_policy policy[IFLA_INET6_MAX+1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_INET6_FLAGS]              = { .type = NLA_U32 },
 		[IFLA_INET6_CACHEINFO]          = { .minlen = nm_offsetofend (struct ifla_cacheinfo, retrans_time) },
 		[IFLA_INET6_CONF]               = { .minlen = 4 },
 		[IFLA_INET6_STATS]              = { .minlen = 8 },
 		[IFLA_INET6_ICMP6STATS]         = { .minlen = 8 },
-		[IFLA_INET6_TOKEN]              = { .minlen = sizeof(struct in6_addr) },
+		[IFLA_INET6_TOKEN]              = { .minlen = sizeof (struct in6_addr) },
 		[IFLA_INET6_ADDR_GEN_MODE]      = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[IFLA_INET6_MAX+1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	struct in6_addr i6_token;
 	gboolean token_valid = FALSE;
 	gboolean addr_gen_mode_valid = FALSE;
 	guint8 i6_addr_gen_mode_inv = 0;
 
-	err = nla_parse_nested (tb, IFLA_INET6_MAX, attr, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, attr, policy) < 0)
 		return FALSE;
 
-	if (tb[IFLA_INET6_CONF] && nla_len(tb[IFLA_INET6_CONF]) % 4)
+	if (tb[IFLA_INET6_CONF] && nla_len (tb[IFLA_INET6_CONF]) % 4)
 		return FALSE;
-	if (tb[IFLA_INET6_STATS] && nla_len(tb[IFLA_INET6_STATS]) % 8)
+	if (tb[IFLA_INET6_STATS] && nla_len (tb[IFLA_INET6_STATS]) % 8)
 		return FALSE;
-	if (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) % 8)
+	if (tb[IFLA_INET6_ICMP6STATS] && nla_len (tb[IFLA_INET6_ICMP6STATS]) % 8)
 		return FALSE;
 
 	if (_check_addr_or_return_val (tb, IFLA_INET6_TOKEN, sizeof (struct in6_addr), FALSE)) {
@@ -1211,7 +1229,7 @@ _parse_af_inet6 (NMPlatform *platform,
 static NMPObject *
 _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_GRE_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_GRE_LINK]     = { .type = NLA_U32 },
 		[IFLA_GRE_IFLAGS]   = { .type = NLA_U16 },
 		[IFLA_GRE_OFLAGS]   = { .type = NLA_U16 },
@@ -1223,13 +1241,13 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 		[IFLA_GRE_TOS]      = { .type = NLA_U8 },
 		[IFLA_GRE_PMTUDISC] = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[IFLA_GRE_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkGre *props;
 	gboolean is_tap;
 
-	if (!info_data || !kind)
+	if (   !info_data
+	    || !kind)
 		return NULL;
 
 	if (nm_streq (kind, "gretap"))
@@ -1239,8 +1257,7 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 	else
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (is_tap ? NMP_OBJECT_TYPE_LNK_GRETAP : NMP_OBJECT_TYPE_LNK_GRE, NULL);
@@ -1280,25 +1297,25 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_infiniband (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_IPOIB_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_IPOIB_PKEY]   = { .type = NLA_U16 },
 		[IFLA_IPOIB_MODE]   = { .type = NLA_U16 },
 		[IFLA_IPOIB_UMCAST] = { .type = NLA_U16 },
 	};
-	struct nlattr *tb[IFLA_IPOIB_MAX + 1];
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPlatformLnkInfiniband *info;
 	NMPObject *obj;
-	int err;
 	const char *mode;
 
-	if (!info_data || g_strcmp0 (kind, "ipoib"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "ipoib"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_IPOIB_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
-	if (!tb[IFLA_IPOIB_PKEY] || !tb[IFLA_IPOIB_MODE])
+	if (   !tb[IFLA_IPOIB_PKEY]
+	    || !tb[IFLA_IPOIB_MODE])
 		return NULL;
 
 	switch (nla_get_u16 (tb[IFLA_IPOIB_MODE])) {
@@ -1326,29 +1343,26 @@ _parse_lnk_infiniband (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_IPTUN_LINK]        = { .type = NLA_U32 },
-		[IFLA_IPTUN_LOCAL]       = { .type = NLA_UNSPEC,
-		                             .minlen = sizeof (struct in6_addr)},
-		[IFLA_IPTUN_REMOTE]      = { .type = NLA_UNSPEC,
-		                             .minlen = sizeof (struct in6_addr)},
+		[IFLA_IPTUN_LOCAL]       = { .minlen = sizeof (struct in6_addr)},
+		[IFLA_IPTUN_REMOTE]      = { .minlen = sizeof (struct in6_addr)},
 		[IFLA_IPTUN_TTL]         = { .type = NLA_U8 },
 		[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;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkIp6Tnl *props;
 	guint32 flowinfo;
 
-	if (!info_data || g_strcmp0 (kind, "ip6tnl"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "ip6tnl"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_IP6TNL, NULL);
@@ -1357,9 +1371,9 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 	if (tb[IFLA_IPTUN_LINK])
 		props->parent_ifindex = nla_get_u32 (tb[IFLA_IPTUN_LINK]);
 	if (tb[IFLA_IPTUN_LOCAL])
-		memcpy (&props->local, nla_data (tb[IFLA_IPTUN_LOCAL]), sizeof (props->local));
+		props->local = *nla_data_as (struct in6_addr, tb[IFLA_IPTUN_LOCAL]);
 	if (tb[IFLA_IPTUN_REMOTE])
-		memcpy (&props->remote, nla_data (tb[IFLA_IPTUN_REMOTE]), sizeof (props->remote));
+		props->remote = *nla_data_as (struct in6_addr, tb[IFLA_IPTUN_REMOTE]);
 	if (tb[IFLA_IPTUN_TTL])
 		props->ttl = nla_get_u8 (tb[IFLA_IPTUN_TTL]);
 	if (tb[IFLA_IPTUN_ENCAP_LIMIT])
@@ -1380,23 +1394,22 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_ip6gre (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_GRE_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_GRE_LINK]        = { .type = NLA_U32 },
 		[IFLA_GRE_IFLAGS]      = { .type = NLA_U16 },
 		[IFLA_GRE_OFLAGS]      = { .type = NLA_U16 },
 		[IFLA_GRE_IKEY]        = { .type = NLA_U32 },
 		[IFLA_GRE_OKEY]        = { .type = NLA_U32 },
 		[IFLA_GRE_LOCAL]       = { .type = NLA_UNSPEC,
-		                             .minlen = sizeof (struct in6_addr)},
+		                           .minlen = sizeof (struct in6_addr)},
 		[IFLA_GRE_REMOTE]      = { .type = NLA_UNSPEC,
-		                             .minlen = sizeof (struct in6_addr)},
+		                           .minlen = sizeof (struct in6_addr)},
 		[IFLA_GRE_TTL]         = { .type = NLA_U8 },
 		[IFLA_GRE_ENCAP_LIMIT] = { .type = NLA_U8 },
 		[IFLA_GRE_FLOWINFO]    = { .type = NLA_U32 },
 		[IFLA_GRE_FLAGS]       = { .type = NLA_U32 },
 	};
-	struct nlattr *tb[IFLA_GRE_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkIp6Tnl *props;
 	guint32 flowinfo;
@@ -1412,8 +1425,7 @@ _parse_lnk_ip6gre (const char *kind, struct nlattr *info_data)
 	else
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (is_tap ? NMP_OBJECT_TYPE_LNK_IP6GRETAP : NMP_OBJECT_TYPE_LNK_IP6GRE, NULL);
@@ -1432,9 +1444,9 @@ _parse_lnk_ip6gre (const char *kind, struct nlattr *info_data)
 	if (tb[IFLA_GRE_OKEY])
 		props->output_key = ntohl (nla_get_u32 (tb[IFLA_GRE_OKEY]));
 	if (tb[IFLA_GRE_LOCAL])
-		memcpy (&props->local, nla_data (tb[IFLA_GRE_LOCAL]), sizeof (props->local));
+		props->local = *nla_data_as (struct in6_addr, tb[IFLA_GRE_LOCAL]);
 	if (tb[IFLA_GRE_REMOTE])
-		memcpy (&props->remote, nla_data (tb[IFLA_GRE_REMOTE]), sizeof (props->remote));
+		props->remote = *nla_data_as (struct in6_addr, tb[IFLA_GRE_REMOTE]);
 	if (tb[IFLA_GRE_TTL])
 		props->ttl = nla_get_u8 (tb[IFLA_GRE_TTL]);
 	if (tb[IFLA_GRE_ENCAP_LIMIT])
@@ -1455,7 +1467,7 @@ _parse_lnk_ip6gre (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_ipip (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_IPTUN_LINK]     = { .type = NLA_U32 },
 		[IFLA_IPTUN_LOCAL]    = { .type = NLA_U32 },
 		[IFLA_IPTUN_REMOTE]   = { .type = NLA_U32 },
@@ -1463,16 +1475,15 @@ _parse_lnk_ipip (const char *kind, struct nlattr *info_data)
 		[IFLA_IPTUN_TOS]      = { .type = NLA_U8 },
 		[IFLA_IPTUN_PMTUDISC] = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkIpIp *props;
 
-	if (!info_data || g_strcmp0 (kind, "ipip"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "ipip"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_IPIP, NULL);
@@ -1493,28 +1504,27 @@ _parse_lnk_ipip (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_macvlan (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_MACVLAN_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_MACVLAN_MODE]  = { .type = NLA_U32 },
 		[IFLA_MACVLAN_FLAGS] = { .type = NLA_U16 },
 	};
 	NMPlatformLnkMacvlan *props;
-	struct nlattr *tb[IFLA_MACVLAN_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	gboolean tap;
 
-	if (!info_data)
+	if (   !info_data
+	    || !kind)
 		return NULL;
 
-	if (!g_strcmp0 (kind, "macvlan"))
+	if (nm_streq (kind, "macvlan"))
 		tap = FALSE;
-	else if (!g_strcmp0 (kind, "macvtap"))
+	else if (nm_streq (kind, "macvtap"))
 		tap = TRUE;
 	else
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_MACVLAN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	if (!tb[IFLA_MACVLAN_MODE])
@@ -1536,7 +1546,7 @@ _parse_lnk_macvlan (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_macsec (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[__IFLA_MACSEC_MAX] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_MACSEC_SCI]            = { .type = NLA_U64 },
 		[IFLA_MACSEC_ICV_LEN]        = { .type = NLA_U8 },
 		[IFLA_MACSEC_CIPHER_SUITE]   = { .type = NLA_U64 },
@@ -1550,33 +1560,32 @@ _parse_lnk_macsec (const char *kind, struct nlattr *info_data)
 		[IFLA_MACSEC_REPLAY_PROTECT] = { .type = NLA_U8 },
 		[IFLA_MACSEC_VALIDATION]     = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[__IFLA_MACSEC_MAX];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkMacsec *props;
 
-	if (!info_data || !nm_streq0 (kind, "macsec"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "macsec"))
 		return NULL;
 
-	err = nla_parse_nested (tb, __IFLA_MACSEC_MAX - 1, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_MACSEC, NULL);
 	props = &obj->lnk_macsec;
 
-	props->sci = tb[IFLA_MACSEC_SCI] ? be64toh (nla_get_u64 (tb[IFLA_MACSEC_SCI])) : 0;
-	props->icv_length = tb[IFLA_MACSEC_ICV_LEN] ? nla_get_u8 (tb[IFLA_MACSEC_ICV_LEN]) : 0;
-	props->cipher_suite = tb [IFLA_MACSEC_CIPHER_SUITE] ? nla_get_u64 (tb[IFLA_MACSEC_CIPHER_SUITE]) : 0;
-	props->window = tb [IFLA_MACSEC_WINDOW] ? nla_get_u32 (tb[IFLA_MACSEC_WINDOW]) : 0;
-	props->encoding_sa = tb[IFLA_MACSEC_ENCODING_SA] ? !!nla_get_u8 (tb[IFLA_MACSEC_ENCODING_SA]) : 0;
-	props->encrypt = tb[IFLA_MACSEC_ENCRYPT] ? !!nla_get_u8 (tb[IFLA_MACSEC_ENCRYPT]) : 0;
-	props->protect = tb[IFLA_MACSEC_PROTECT] ? !!nla_get_u8 (tb[IFLA_MACSEC_PROTECT]) : 0;
-	props->include_sci = tb[IFLA_MACSEC_INC_SCI] ? !!nla_get_u8 (tb[IFLA_MACSEC_INC_SCI]) : 0;
-	props->es = tb[IFLA_MACSEC_ES] ? !!nla_get_u8 (tb[IFLA_MACSEC_ES]) : 0;
-	props->scb = tb[IFLA_MACSEC_SCB] ? !!nla_get_u8 (tb[IFLA_MACSEC_SCB]) : 0;
-	props->replay_protect = tb[IFLA_MACSEC_REPLAY_PROTECT] ? !!nla_get_u8 (tb[IFLA_MACSEC_REPLAY_PROTECT]) : 0;
-	props->validation = tb[IFLA_MACSEC_VALIDATION] ? nla_get_u8 (tb[IFLA_MACSEC_VALIDATION]) : 0;
+	if (tb[IFLA_MACSEC_SCI])            { props->sci            =   nla_get_be64 (tb[IFLA_MACSEC_SCI]);            }
+	if (tb[IFLA_MACSEC_ICV_LEN])        { props->icv_length     =   nla_get_u8   (tb[IFLA_MACSEC_ICV_LEN]);        }
+	if (tb[IFLA_MACSEC_CIPHER_SUITE])   { props->cipher_suite   =   nla_get_u64  (tb[IFLA_MACSEC_CIPHER_SUITE]);   }
+	if (tb[IFLA_MACSEC_WINDOW])         { props->window         =   nla_get_u32  (tb[IFLA_MACSEC_WINDOW]);         }
+	if (tb[IFLA_MACSEC_ENCODING_SA])    { props->encoding_sa    = !!nla_get_u8   (tb[IFLA_MACSEC_ENCODING_SA]);    }
+	if (tb[IFLA_MACSEC_ENCRYPT])        { props->encrypt        = !!nla_get_u8   (tb[IFLA_MACSEC_ENCRYPT]);        }
+	if (tb[IFLA_MACSEC_PROTECT])        { props->protect        = !!nla_get_u8   (tb[IFLA_MACSEC_PROTECT]);        }
+	if (tb[IFLA_MACSEC_INC_SCI])        { props->include_sci    = !!nla_get_u8   (tb[IFLA_MACSEC_INC_SCI]);        }
+	if (tb[IFLA_MACSEC_ES])             { props->es             = !!nla_get_u8   (tb[IFLA_MACSEC_ES]);             }
+	if (tb[IFLA_MACSEC_SCB])            { props->scb            = !!nla_get_u8   (tb[IFLA_MACSEC_SCB]);            }
+	if (tb[IFLA_MACSEC_REPLAY_PROTECT]) { props->replay_protect = !!nla_get_u8   (tb[IFLA_MACSEC_REPLAY_PROTECT]); }
+	if (tb[IFLA_MACSEC_VALIDATION])     { props->validation     =   nla_get_u8   (tb[IFLA_MACSEC_VALIDATION]);     }
 
 	return obj;
 }
@@ -1586,7 +1595,7 @@ _parse_lnk_macsec (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_sit (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_IPTUN_LINK]     = { .type = NLA_U32 },
 		[IFLA_IPTUN_LOCAL]    = { .type = NLA_U32 },
 		[IFLA_IPTUN_REMOTE]   = { .type = NLA_U32 },
@@ -1596,16 +1605,15 @@ _parse_lnk_sit (const char *kind, struct nlattr *info_data)
 		[IFLA_IPTUN_FLAGS]    = { .type = NLA_U16 },
 		[IFLA_IPTUN_PROTO]    = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkSit *props;
 
-	if (!info_data || g_strcmp0 (kind, "sit"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "sit"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_SIT, NULL);
@@ -1628,7 +1636,7 @@ _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] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_TUN_OWNER]               = { .type = NLA_U32 },
 		[IFLA_TUN_GROUP]               = { .type = NLA_U32 },
 		[IFLA_TUN_TYPE]                = { .type = NLA_U8 },
@@ -1639,22 +1647,19 @@ _parse_lnk_tun (const char *kind, struct nlattr *info_data)
 		[IFLA_TUN_NUM_QUEUES]          = { .type = NLA_U32 },
 		[IFLA_TUN_NUM_DISABLED_QUEUES] = { .type = NLA_U32 },
 	};
-	struct nlattr *tb[IFLA_TUN_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkTun *props;
 
-	if (!info_data || !nm_streq0 (kind, "tun"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "tun"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_TUN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
-	if (!tb[IFLA_TUN_TYPE]) {
-		/* we require at least a type. */
+	if (!tb[IFLA_TUN_TYPE])
 		return NULL;
-	}
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_TUN, NULL);
 	props = &obj->lnk_tun;
@@ -1701,7 +1706,7 @@ _vlan_qos_mapping_from_nla (struct nlattr *nlattr,
 
 	array = g_ptr_array_new ();
 	nla_for_each_nested (nla, nlattr, remaining) {
-		if (nla_len (nla) < sizeof(NMVlanQosMapping))
+		if (nla_len (nla) < sizeof (NMVlanQosMapping))
 			return FALSE;
 		g_ptr_array_add (array, nla_data (nla));
 	}
@@ -1742,22 +1747,22 @@ _vlan_qos_mapping_from_nla (struct nlattr *nlattr,
 static NMPObject *
 _parse_lnk_vlan (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_VLAN_MAX+1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_VLAN_ID]          = { .type = NLA_U16 },
 		[IFLA_VLAN_FLAGS]       = { .minlen = nm_offsetofend (struct ifla_vlan_flags, flags) },
 		[IFLA_VLAN_INGRESS_QOS] = { .type = NLA_NESTED },
 		[IFLA_VLAN_EGRESS_QOS]  = { .type = NLA_NESTED },
 		[IFLA_VLAN_PROTOCOL]    = { .type = NLA_U16 },
 	};
-	struct nlattr *tb[IFLA_VLAN_MAX+1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	nm_auto_nmpobj NMPObject *obj = NULL;
 	NMPObject *obj_result;
 
-	if (!info_data || g_strcmp0 (kind, "vlan"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "vlan"))
 		return NULL;
 
-	if ((err = nla_parse_nested (tb, IFLA_VLAN_MAX, info_data, policy)) < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	if (!tb[IFLA_VLAN_ID])
@@ -1769,7 +1774,7 @@ _parse_lnk_vlan (const char *kind, struct nlattr *info_data)
 	if (tb[IFLA_VLAN_FLAGS]) {
 		struct ifla_vlan_flags flags;
 
-		nla_memcpy (&flags, tb[IFLA_VLAN_FLAGS], sizeof(flags));
+		nla_memcpy (&flags, tb[IFLA_VLAN_FLAGS], sizeof (flags));
 
 		obj->lnk_vlan.flags = flags.flags;
 	}
@@ -1827,7 +1832,7 @@ struct nm_ifla_vxlan_port_range {
 static NMPObject *
 _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_VXLAN_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_VXLAN_ID]         = { .type = NLA_U32 },
 		[IFLA_VXLAN_GROUP]      = { .type = NLA_U32 },
 		[IFLA_VXLAN_GROUP6]     = { .type = NLA_UNSPEC,
@@ -1850,16 +1855,14 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 		[IFLA_VXLAN_PORT]       = { .type = NLA_U16 },
 	};
 	NMPlatformLnkVxlan *props;
-	struct nlattr *tb[IFLA_VXLAN_MAX + 1];
-	struct nm_ifla_vxlan_port_range *range;
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 
-	if (!info_data || g_strcmp0 (kind, "vxlan"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "vxlan"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_VXLAN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
@@ -1874,10 +1877,10 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 		props->group = nla_get_u32 (tb[IFLA_VXLAN_GROUP]);
 	if (tb[IFLA_VXLAN_LOCAL])
 		props->local = nla_get_u32 (tb[IFLA_VXLAN_LOCAL]);
-	if (tb[IFLA_VXLAN_GROUP6])
-		memcpy (&props->group6, nla_data (tb[IFLA_VXLAN_GROUP6]), sizeof (props->group6));
 	if (tb[IFLA_VXLAN_LOCAL6])
-		memcpy (&props->local6, nla_data (tb[IFLA_VXLAN_LOCAL6]), sizeof (props->local6));
+		props->local6 = *nla_data_as (struct in6_addr, tb[IFLA_VXLAN_LOCAL6]);
+	if (tb[IFLA_VXLAN_GROUP6])
+		props->group6 = *nla_data_as (struct in6_addr, tb[IFLA_VXLAN_GROUP6]);
 
 	if (tb[IFLA_VXLAN_AGEING])
 		props->ageing = nla_get_u32 (tb[IFLA_VXLAN_AGEING]);
@@ -1892,7 +1895,9 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 		props->dst_port = ntohs (nla_get_u16 (tb[IFLA_VXLAN_PORT]));
 
 	if (tb[IFLA_VXLAN_PORT_RANGE]) {
-		range = nla_data (tb[IFLA_VXLAN_PORT_RANGE]);
+		struct nm_ifla_vxlan_port_range *range;
+
+		range = nla_data_as (struct nm_ifla_vxlan_port_range, tb[IFLA_VXLAN_PORT_RANGE]);
 		props->src_port_min = ntohs (range->low);
 		props->src_port_max = ntohs (range->high);
 	}
@@ -1917,16 +1922,16 @@ static gboolean
 _wireguard_update_from_allowed_ips_nla (NMPWireGuardAllowedIP *allowed_ip,
                                         struct nlattr *nlattr)
 {
-	static const struct nla_policy policy[WGALLOWEDIP_A_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[WGALLOWEDIP_A_FAMILY]    = { .type = NLA_U16 },
 		[WGALLOWEDIP_A_IPADDR]    = { .minlen = sizeof (struct in_addr) },
 		[WGALLOWEDIP_A_CIDR_MASK] = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[WGALLOWEDIP_A_MAX + 1];
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	int family;
 	int addr_len;
 
-	if (nla_parse_nested (tb, WGALLOWEDIP_A_MAX, nlattr, policy) < 0)
+	if (nla_parse_nested_arr (tb, nlattr, policy) < 0)
 		return FALSE;
 
 	if (!tb[WGALLOWEDIP_A_FAMILY])
@@ -1942,9 +1947,10 @@ _wireguard_update_from_allowed_ips_nla (NMPWireGuardAllowedIP *allowed_ip,
 
 	_check_addr_or_return_val (tb, WGALLOWEDIP_A_IPADDR, addr_len, FALSE);
 
-	memset (allowed_ip, 0, sizeof (NMPWireGuardAllowedIP));
+	*allowed_ip = (NMPWireGuardAllowedIP) {
+		.family = family,
+	};
 
-	allowed_ip->family = family;
 	nm_assert ((int) allowed_ip->family == family);
 
 	if (tb[WGALLOWEDIP_A_IPADDR])
@@ -1965,7 +1971,7 @@ _wireguard_update_from_peers_nla (CList *peers,
                                   GArray **p_allowed_ips,
                                   struct nlattr *peer_attr)
 {
-	static const struct nla_policy policy[WGPEER_A_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[WGPEER_A_PUBLIC_KEY]                    = { .minlen = NMP_WIREGUARD_PUBLIC_KEY_LEN },
 		[WGPEER_A_PRESHARED_KEY]                 = { },
 		[WGPEER_A_FLAGS]                         = { .type = NLA_U32 },
@@ -1976,10 +1982,10 @@ _wireguard_update_from_peers_nla (CList *peers,
 		[WGPEER_A_TX_BYTES]                      = { .type = NLA_U64 },
 		[WGPEER_A_ALLOWEDIPS]                    = { .type = NLA_NESTED },
 	};
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	WireGuardPeerConstruct *peer_c;
-	struct nlattr *tb[WGPEER_A_MAX + 1];
 
-	if (nla_parse_nested (tb, WGPEER_A_MAX, peer_attr, policy) < 0)
+	if (nla_parse_nested_arr (tb, peer_attr, policy) < 0)
 		return FALSE;
 
 	if (!tb[WGPEER_A_PUBLIC_KEY])
@@ -2006,34 +2012,17 @@ _wireguard_update_from_peers_nla (CList *peers,
 			nm_explicit_bzero (nla_data (tb[WGPEER_A_PRESHARED_KEY]),
 			                   nla_len (tb[WGPEER_A_PRESHARED_KEY]));
 		}
-		if (tb[WGPEER_A_ENDPOINT]) {
-			const struct sockaddr *addr = nla_data (tb[WGPEER_A_ENDPOINT]);
-			unsigned short family;
-
-			G_STATIC_ASSERT (sizeof (addr->sa_family) == sizeof (family));
-			memcpy (&family, &addr->sa_family, sizeof (addr->sa_family));
-
-			if (   family == AF_INET
-			    && nla_len (tb[WGPEER_A_ENDPOINT]) == sizeof (struct sockaddr_in)) {
-				const struct sockaddr_in *addr4 = (const struct sockaddr_in *) addr;
-
-				peer_c->data.endpoint_family = AF_INET;
-				peer_c->data.endpoint_port = unaligned_read_be16 (&addr4->sin_port);
-				peer_c->data.endpoint_addr.addr4 = unaligned_read_ne32 (&addr4->sin_addr.s_addr);
-				memcpy (&peer_c->data.endpoint_addr.addr4, &addr4->sin_addr.s_addr, 4);
-			} else if (   family == AF_INET6
-			           && nla_len (tb[WGPEER_A_ENDPOINT]) == sizeof (struct sockaddr_in6)) {
-				const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *) addr;
-
-				peer_c->data.endpoint_family = AF_INET6;
-				peer_c->data.endpoint_port = unaligned_read_be16 (&addr6->sin6_port);
-				memcpy (&peer_c->data.endpoint_addr.addr6, &addr6->sin6_addr, 16);
-			}
-		}
+
+		nm_sock_addr_union_cpy_untrusted (&peer_c->data.endpoint,
+		                                  tb[WGPEER_A_ENDPOINT] ? nla_data (tb[WGPEER_A_ENDPOINT]) : NULL,
+		                                  tb[WGPEER_A_ENDPOINT] ? nla_len  (tb[WGPEER_A_ENDPOINT]) : 0);
+
 		if (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL])
 			peer_c->data.persistent_keepalive_interval = nla_get_u16 (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
-		if (tb[WGPEER_A_LAST_HANDSHAKE_TIME])
-			nla_memcpy (&peer_c->data.last_handshake_time, tb[WGPEER_A_LAST_HANDSHAKE_TIME], sizeof (peer_c->data.last_handshake_time));
+		if (tb[WGPEER_A_LAST_HANDSHAKE_TIME]) {
+			if (nla_len (tb[WGPEER_A_LAST_HANDSHAKE_TIME]) >= sizeof (peer_c->data.last_handshake_time))
+				nla_memcpy (&peer_c->data.last_handshake_time, tb[WGPEER_A_LAST_HANDSHAKE_TIME], sizeof (peer_c->data.last_handshake_time));
+		}
 		if (tb[WGPEER_A_RX_BYTES])
 			peer_c->data.rx_bytes = nla_get_u64 (tb[WGPEER_A_RX_BYTES]);
 		if (tb[WGPEER_A_TX_BYTES])
@@ -2081,7 +2070,7 @@ typedef struct {
 static int
 _wireguard_get_device_cb (struct nl_msg *msg, void *arg)
 {
-	static const struct nla_policy policy[WGDEVICE_A_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[WGDEVICE_A_IFINDEX]     = { .type = NLA_U32 },
 		[WGDEVICE_A_IFNAME]      = { .type = NLA_NUL_STRING, .maxlen = IFNAMSIZ },
 		[WGDEVICE_A_PRIVATE_KEY] = { },
@@ -2091,12 +2080,10 @@ _wireguard_get_device_cb (struct nl_msg *msg, void *arg)
 		[WGDEVICE_A_FWMARK]      = { .type = NLA_U32 },
 		[WGDEVICE_A_PEERS]       = { .type = NLA_NESTED },
 	};
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	WireGuardParseData *parse_data = arg;
-	struct nlattr *tb[WGDEVICE_A_MAX + 1];
-	int nlerr;
 
-	nlerr = genlmsg_parse (nlmsg_hdr (msg), 0, tb, WGDEVICE_A_MAX, policy);
-	if (nlerr < 0)
+	if (genlmsg_parse_arr (nlmsg_hdr (msg), 0, tb, policy) < 0)
 		return NL_SKIP;
 
 	if (tb[WGDEVICE_A_IFINDEX]) {
@@ -2157,9 +2144,9 @@ _wireguard_get_device_cb (struct nl_msg *msg, void *arg)
 
 static const NMPObject *
 _wireguard_read_info (NMPlatform *platform /* used only as logging context */,
-                     struct nl_sock *genl,
-                     int wireguard_family_id,
-                     int ifindex)
+                      struct nl_sock *genl,
+                      int wireguard_family_id,
+                      int ifindex)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	NMPObject *obj = NULL;
@@ -2175,6 +2162,8 @@ _wireguard_read_info (NMPlatform *platform /* used only as logging context */,
 	nm_assert (wireguard_family_id >= 0);
 	nm_assert (ifindex > 0);
 
+	_LOGT ("wireguard: fetching information for ifindex %d (genl-id %d)...", ifindex, wireguard_family_id);
+
 	msg = nlmsg_alloc ();
 
 	if (!genlmsg_put (msg,
@@ -2227,7 +2216,7 @@ _wireguard_read_info (NMPlatform *platform /* used only as logging context */,
 	 * there. The realloc/resize of the GArray is fine there. However,
 	 * while we build the GArray, we don't yet have the final pointers.
 	 * Hence, while constructing, we track the indexes with peer->_construct_idx_*
-	 * fields. These indexes must be convered to actual pointers blow.
+	 * fields. These indexes must be converted to actual pointers blow.
 	 *
 	 * This is all done during parsing. In the final NMPObjectLnkWireGuard we
 	 * don't want the CList anymore and repackage the NMPObject tightly. The
@@ -2283,13 +2272,383 @@ nla_put_failure:
 	g_return_val_if_reached (NULL);
 }
 
+static int
+_wireguard_get_family_id (NMPlatform *platform, int ifindex_try)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	int wireguard_family_id = -1;
+
+	if (ifindex_try > 0) {
+		const NMPlatformLink *plink;
+
+		if (nm_platform_link_get_lnk_wireguard (platform, ifindex_try, &plink))
+			wireguard_family_id = NMP_OBJECT_UP_CAST (plink)->_link.wireguard_family_id;
+	}
+	if (wireguard_family_id < 0)
+		wireguard_family_id = genl_ctrl_resolve (priv->genl, "wireguard");
+	return wireguard_family_id;
+}
+
+static const NMPObject *
+_wireguard_refresh_link (NMPlatform *platform,
+                         int wireguard_family_id,
+                         int ifindex)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	nm_auto_nmpobj const NMPObject *lnk_new = NULL;
+	NMPCacheOpsType cache_op;
+	const NMPObject *plink = NULL;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+
+	nm_assert (wireguard_family_id >= 0);
+	nm_assert (ifindex > 0);
+
+	nm_platform_process_events (platform);
+
+	plink = nm_platform_link_get_obj (platform, ifindex, TRUE);
+
+	if (   !plink
+	    || plink->link.type != NM_LINK_TYPE_WIREGUARD) {
+		nm_platform_link_refresh (platform, ifindex);
+		plink = nm_platform_link_get_obj (platform, ifindex, TRUE);
+		if (   !plink
+		    || plink->link.type != NM_LINK_TYPE_WIREGUARD)
+			return NULL;
+		if (NMP_OBJECT_GET_TYPE (plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD)
+			lnk_new = nmp_object_ref (plink->_link.netlink.lnk);
+	} else {
+		lnk_new = _wireguard_read_info (platform,
+		                                priv->genl,
+		                                wireguard_family_id,
+		                                ifindex);
+		if (!lnk_new) {
+			if (NMP_OBJECT_GET_TYPE (plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD)
+				lnk_new = nmp_object_ref (plink->_link.netlink.lnk);
+		} else if (nmp_object_equal (plink->_link.netlink.lnk, lnk_new)) {
+			nmp_object_unref (lnk_new);
+			lnk_new = nmp_object_ref (plink->_link.netlink.lnk);
+		}
+	}
+
+	if (   plink->_link.wireguard_family_id == wireguard_family_id
+	    && plink->_link.netlink.lnk == lnk_new)
+		return plink;
+
+	/* we use nmp_cache_update_netlink() to re-inject the new object into the cache.
+	 * For that, we need to clone it, and tweak it so that it's suitable. It's a bit
+	 * of a hack, in particular that we need to clear driver and udev-device. */
+	obj = nmp_object_clone (plink, FALSE);
+	obj->_link.wireguard_family_id = wireguard_family_id;
+	nmp_object_unref (obj->_link.netlink.lnk);
+	obj->_link.netlink.lnk = g_steal_pointer (&lnk_new);
+	obj->link.driver = NULL;
+	nm_clear_pointer (&obj->_link.udev.device, udev_device_unref);
+
+	cache_op = nmp_cache_update_netlink (nm_platform_get_cache (platform),
+	                                     obj,
+	                                     FALSE,
+	                                     &obj_old,
+	                                     &obj_new);
+	nm_assert (NM_IN_SET (cache_op, NMP_CACHE_OPS_UPDATED));
+	if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+		cache_on_change (platform, cache_op, obj_old, obj_new);
+		nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
+	}
+
+	nm_assert (   !obj_new
+	           || (   NMP_OBJECT_GET_TYPE (obj_new) == NMP_OBJECT_TYPE_LINK
+	               && obj_new->link.type == NM_LINK_TYPE_WIREGUARD
+	               && (   !obj_new->_link.netlink.lnk
+	                   || NMP_OBJECT_GET_TYPE (obj_new->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD)));
+	return obj_new;
+}
+
+static int
+_wireguard_create_change_nlmsgs (NMPlatform *platform,
+                                 int ifindex,
+                                 int wireguard_family_id,
+                                 const NMPlatformLnkWireGuard *lnk_wireguard,
+                                 const NMPWireGuardPeer *peers,
+                                 const NMPlatformWireGuardChangePeerFlags *peer_flags,
+                                 guint peers_len,
+                                 NMPlatformWireGuardChangeFlags change_flags,
+                                 GPtrArray **out_msgs)
+{
+	gs_unref_ptrarray GPtrArray *msgs = NULL;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	const guint IDX_NIL = G_MAXUINT;
+	guint idx_peer_curr;
+	guint idx_allowed_ips_curr;
+	struct nlattr *nest_peers;
+	struct nlattr *nest_curr_peer;
+	struct nlattr *nest_allowed_ips;
+	struct nlattr *nest_curr_allowed_ip;
+	NMPlatformWireGuardChangePeerFlags p_flags = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_DEFAULT;
+
+#define _nla_nest_end(msg, nest_start) \
+	G_STMT_START { \
+		if (nla_nest_end ((msg), (nest_start)) < 0) \
+			g_return_val_if_reached (-NME_BUG); \
+	} G_STMT_END
+
+	/* Adapted from LGPL-2.1+ code [1].
+	 *
+	 * [1] https://git.zx2c4.com/WireGuard/tree/contrib/examples/embeddable-wg-library/wireguard.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n1073 */
+
+	idx_peer_curr = IDX_NIL;
+	idx_allowed_ips_curr = IDX_NIL;
+
+	/* TODO: for the moment, we always reset all peers and allowed-ips (WGDEVICE_F_REPLACE_PEERS, WGPEER_F_REPLACE_ALLOWEDIPS).
+	 * The platform API should be extended to also support partial updates. In particular, configuring the same configuration
+	 * multiple times, should not clear and re-add all settings, but rather sync the existing settings with the desired configuration. */
+
+again:
+
+	msg = nlmsg_alloc ();
+	if (!genlmsg_put (msg,
+	                  NL_AUTO_PORT,
+	                  NL_AUTO_SEQ,
+	                  wireguard_family_id,
+	                  0,
+	                  NLM_F_REQUEST,
+	                  WG_CMD_SET_DEVICE,
+	                  1))
+		g_return_val_if_reached (-NME_BUG);
+
+	NLA_PUT_U32 (msg, WGDEVICE_A_IFINDEX, (guint32) ifindex);
+
+	if (idx_peer_curr == IDX_NIL) {
+		guint32 flags;
+
+		if (NM_FLAGS_HAS (change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY))
+			NLA_PUT (msg, WGDEVICE_A_PRIVATE_KEY, sizeof (lnk_wireguard->private_key), lnk_wireguard->private_key);
+		if (NM_FLAGS_HAS (change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT))
+			NLA_PUT_U16 (msg, WGDEVICE_A_LISTEN_PORT, lnk_wireguard->listen_port);
+		if (NM_FLAGS_HAS (change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK))
+			NLA_PUT_U32 (msg, WGDEVICE_A_FWMARK, lnk_wireguard->fwmark);
+
+		flags = 0;
+		if (NM_FLAGS_HAS (change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS))
+			flags |= WGDEVICE_F_REPLACE_PEERS;
+		NLA_PUT_U32 (msg, WGDEVICE_A_FLAGS, flags);
+	}
+
+	if (peers_len == 0)
+		goto send;
+
+	nest_curr_peer = NULL;
+	nest_allowed_ips = NULL;
+	nest_curr_allowed_ip = NULL;
+
+	nest_peers = nla_nest_start (msg, WGDEVICE_A_PEERS);
+	if (!nest_peers)
+		g_return_val_if_reached (-NME_BUG);
+
+	if (idx_peer_curr == IDX_NIL)
+		idx_peer_curr = 0;
+	for (; idx_peer_curr < peers_len; idx_peer_curr++) {
+		const NMPWireGuardPeer *p = &peers[idx_peer_curr];
+
+		if (peer_flags) {
+			p_flags = peer_flags[idx_peer_curr];
+			if (!NM_FLAGS_ANY (p_flags,   NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME
+			                            | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY
+			                            | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL
+			                            | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT
+			                            | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS
+			                            | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS)) {
+				/* no flags set. We take that as indication to skip configuring the peer
+				 * entirely. */
+				nm_assert (p_flags == NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE);
+				continue;
+			}
+		}
+
+		nest_curr_peer = nla_nest_start (msg, 0);
+		if (!nest_curr_peer)
+			goto toobig_peers;
+
+		if (nla_put (msg, WGPEER_A_PUBLIC_KEY, NMP_WIREGUARD_PUBLIC_KEY_LEN, p->public_key) < 0)
+			goto toobig_peers;
+
+		if (NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME)) {
+			/* all other p_flags are silently ignored. */
+			if (nla_put_uint32 (msg, WGPEER_A_FLAGS, WGPEER_F_REMOVE_ME) < 0)
+				goto toobig_peers;
+		} else {
+
+			if (idx_allowed_ips_curr == IDX_NIL) {
+				if (   NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY)
+				    && nla_put (msg, WGPEER_A_PRESHARED_KEY, sizeof (p->preshared_key), p->preshared_key) < 0)
+					goto toobig_peers;
+
+				if (   NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL)
+				    && nla_put_uint16 (msg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, p->persistent_keepalive_interval) < 0)
+					goto toobig_peers;
+
+				if (   NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS)
+				    && nla_put_uint32 (msg, WGPEER_A_FLAGS, WGPEER_F_REPLACE_ALLOWEDIPS) < 0)
+					goto toobig_peers;
+
+				if (NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT)) {
+					if (NM_IN_SET (p->endpoint.sa.sa_family, AF_INET, AF_INET6)) {
+						if (nla_put (msg,
+						             WGPEER_A_ENDPOINT,
+						               p->endpoint.sa.sa_family == AF_INET
+						             ? sizeof (p->endpoint.in)
+						             : sizeof (p->endpoint.in6),
+						             &p->endpoint) < 0)
+							goto toobig_peers;
+					} else {
+						/* I think there is no way to clear an endpoint, though there shold be. */
+						nm_assert (p->endpoint.sa.sa_family == AF_UNSPEC);
+					}
+				}
+			}
+
+			if (   NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS)
+			    && p->allowed_ips_len > 0) {
+				if (idx_allowed_ips_curr == IDX_NIL)
+					idx_allowed_ips_curr = 0;
+
+				nest_allowed_ips = nla_nest_start (msg, WGPEER_A_ALLOWEDIPS);
+				if (!nest_allowed_ips)
+					goto toobig_allowedips;
+
+				for (; idx_allowed_ips_curr < p->allowed_ips_len; idx_allowed_ips_curr++) {
+					const NMPWireGuardAllowedIP *aip = &p->allowed_ips[idx_allowed_ips_curr];
+
+					nest_curr_allowed_ip = nla_nest_start (msg, 0);
+					if (!nest_curr_allowed_ip)
+						goto toobig_allowedips;
+
+					g_return_val_if_fail (NM_IN_SET (aip->family, AF_INET, AF_INET6), -NME_BUG);
+
+					if (nla_put_uint16 (msg, WGALLOWEDIP_A_FAMILY, aip->family) < 0)
+						goto toobig_allowedips;
+					if (nla_put (msg,
+					             WGALLOWEDIP_A_IPADDR,
+					             nm_utils_addr_family_to_size (aip->family),
+					             &aip->addr) < 0)
+						goto toobig_allowedips;
+					if (nla_put_uint8 (msg, WGALLOWEDIP_A_CIDR_MASK, aip->mask) < 0)
+						goto toobig_allowedips;
+
+					_nla_nest_end (msg, nest_curr_allowed_ip);
+					nest_curr_allowed_ip = NULL;
+				}
+				idx_allowed_ips_curr = IDX_NIL;
+
+				_nla_nest_end (msg, nest_allowed_ips);
+				nest_allowed_ips = NULL;
+			}
+		}
+
+		_nla_nest_end (msg, nest_curr_peer);
+		nest_curr_peer = NULL;
+	}
+
+	_nla_nest_end (msg, nest_peers);
+	goto send;
+
+toobig_allowedips:
+	if (nest_curr_allowed_ip)
+		nla_nest_cancel (msg, nest_curr_allowed_ip);
+	if (nest_allowed_ips)
+		nla_nest_cancel (msg, nest_allowed_ips);
+	_nla_nest_end (msg, nest_curr_peer);
+	_nla_nest_end (msg, nest_peers);
+	goto send;
+
+toobig_peers:
+	if (nest_curr_peer)
+		nla_nest_cancel (msg, nest_curr_peer);
+	_nla_nest_end (msg, nest_peers);
+	goto send;
+
+send:
+	if (!msgs)
+		msgs = g_ptr_array_new_with_free_func ((GDestroyNotify) nlmsg_free);
+	g_ptr_array_add (msgs, g_steal_pointer (&msg));
+
+	if (   idx_peer_curr != IDX_NIL
+	    && idx_peer_curr < peers_len)
+		goto again;
+
+	NM_SET_OUT (out_msgs, g_steal_pointer (&msgs));
+	return 0;
+
+nla_put_failure:
+	g_return_val_if_reached (-NME_BUG);
+
+#undef _nla_nest_end
+}
+
+static int
+link_wireguard_change (NMPlatform *platform,
+                       int ifindex,
+                       const NMPlatformLnkWireGuard *lnk_wireguard,
+                       const NMPWireGuardPeer *peers,
+                       const NMPlatformWireGuardChangePeerFlags *peer_flags,
+                       guint peers_len,
+                       NMPlatformWireGuardChangeFlags change_flags)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	gs_unref_ptrarray GPtrArray *msgs = NULL;
+	int wireguard_family_id;
+	guint i;
+	int r;
+
+	wireguard_family_id = _wireguard_get_family_id (platform, ifindex);
+	if (wireguard_family_id < 0)
+		return -NME_PL_NO_FIRMWARE;
+
+	r = _wireguard_create_change_nlmsgs (platform,
+	                                     ifindex,
+	                                     wireguard_family_id,
+	                                     lnk_wireguard,
+	                                     peers,
+	                                     peer_flags,
+	                                     peers_len,
+	                                     change_flags,
+	                                     &msgs);
+	if (r < 0) {
+		_LOGW ("wireguard: set-device, cannot construct netlink message: %s", nm_strerror (r));
+		return r;
+	}
+
+	for (i = 0; i < msgs->len; i++) {
+		r = nl_send_auto (priv->genl, msgs->pdata[i]);
+		if (r < 0) {
+			_LOGW ("wireguard: set-device, send netlink message #%u failed: %s", i, nm_strerror (r));
+			return r;
+		}
+
+		do {
+			r = nl_recvmsgs (priv->genl, NULL);
+		} while (r == -EAGAIN);
+		if (r < 0) {
+			_LOGW ("wireguard: set-device, message #%u was rejected: %s", i, nm_strerror (r));
+			return r;
+		}
+
+		_LOGT ("wireguard: set-device, message #%u sent and confirmed", i);
+	}
+
+	_wireguard_refresh_link (platform, wireguard_family_id, ifindex);
+
+	return 0;
+}
+
 /*****************************************************************************/
 
 /* Copied and heavily modified from libnl3's link_msg_parser(). */
 static NMPObject *
 _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr *nlh, gboolean id_only)
 {
-	static const struct nla_policy policy[IFLA_MAX+1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_IFNAME]           = { .type = NLA_STRING,
 		                            .maxlen = IFNAMSIZ },
 		[IFLA_MTU]              = { .type = NLA_U32 },
@@ -2316,18 +2675,12 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 		[IFLA_PHYS_PORT_ID]     = { .type = NLA_UNSPEC },
 		[IFLA_NET_NS_PID]       = { .type = NLA_U32 },
 		[IFLA_NET_NS_FD]        = { .type = NLA_U32 },
-	};
-	static const struct nla_policy policy_link_info[IFLA_INFO_MAX+1] = {
-		[IFLA_INFO_KIND]        = { .type = NLA_STRING },
-		[IFLA_INFO_DATA]        = { .type = NLA_NESTED },
-		[IFLA_INFO_XSTATS]      = { .type = NLA_NESTED },
+		[IFLA_LINK_NETNSID]     = { },
 	};
 	const struct ifinfomsg *ifi;
-	struct nlattr *tb[IFLA_MAX+1];
-	struct nlattr *li[IFLA_INFO_MAX+1];
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	struct nlattr *nl_info_data = NULL;
 	const char *nl_info_kind = NULL;
-	int err;
 	nm_auto_nmpobj NMPObject *obj = NULL;
 	gboolean completed_from_cache_val = FALSE;
 	gboolean *completed_from_cache = cache ? &completed_from_cache_val : NULL;
@@ -2341,6 +2694,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 
 	if (!nlmsg_valid_hdr (nlh, sizeof (*ifi)))
 		return NULL;
+
 	ifi = nlmsg_data (nlh);
 
 	if (ifi->ifi_family != AF_UNSPEC)
@@ -2353,13 +2707,12 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	if (id_only)
 		return g_steal_pointer (&obj);
 
-	err = nlmsg_parse (nlh, sizeof (*ifi), tb, IFLA_MAX, policy);
-	if (err < 0)
+	if (nlmsg_parse_arr (nlh, sizeof (*ifi), tb, policy) < 0)
 		return NULL;
 
 	if (!tb[IFLA_IFNAME])
 		return NULL;
-	nla_strlcpy(obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ);
+	nla_strlcpy (obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ);
 	if (!obj->link.name[0])
 		return NULL;
 
@@ -2381,8 +2734,14 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	obj->link.mtu = nla_get_u32 (tb[IFLA_MTU]);
 
 	if (tb[IFLA_LINKINFO]) {
-		err = nla_parse_nested (li, IFLA_INFO_MAX, tb[IFLA_LINKINFO], policy_link_info);
-		if (err < 0)
+		static const struct nla_policy policy_link_info[] = {
+			[IFLA_INFO_KIND]        = { .type = NLA_STRING },
+			[IFLA_INFO_DATA]        = { .type = NLA_NESTED },
+			[IFLA_INFO_XSTATS]      = { .type = NLA_NESTED },
+		};
+		struct nlattr *li[G_N_ELEMENTS (policy_link_info)];
+
+		if (nla_parse_nested_arr (li, tb[IFLA_LINKINFO], policy_link_info) < 0)
 			return NULL;
 
 		if (li[IFLA_INFO_KIND])
@@ -2392,18 +2751,12 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	}
 
 	if (tb[IFLA_STATS64]) {
-		/* tb[IFLA_STATS64] is only guaranteed to be 32bit-aligned,
-		 * so in general we can't access the rtnl_link_stats64 struct
-		 * members directly on 64bit architectures. */
-		char *stats = nla_data (tb[IFLA_STATS64]);
-
-#define READ_STAT64(member) \
-	unaligned_read_ne64 (stats + offsetof (struct rtnl_link_stats64, member))
+		const char *stats = nla_data (tb[IFLA_STATS64]);
 
-		obj->link.rx_packets = READ_STAT64 (rx_packets);
-		obj->link.rx_bytes   = READ_STAT64 (rx_bytes);
-		obj->link.tx_packets = READ_STAT64 (tx_packets);
-		obj->link.tx_bytes   = READ_STAT64 (tx_bytes);
+		obj->link.rx_packets = unaligned_read_ne64 (&stats[G_STRUCT_OFFSET (struct rtnl_link_stats64, rx_packets)]);
+		obj->link.rx_bytes   = unaligned_read_ne64 (&stats[G_STRUCT_OFFSET (struct rtnl_link_stats64, rx_bytes)]);
+		obj->link.tx_packets = unaligned_read_ne64 (&stats[G_STRUCT_OFFSET (struct rtnl_link_stats64, tx_packets)]);
+		obj->link.tx_bytes   = unaligned_read_ne64 (&stats[G_STRUCT_OFFSET (struct rtnl_link_stats64, tx_bytes)]);
 	}
 
 	obj->link.n_ifi_flags = ifi->ifi_flags;
@@ -2631,14 +2984,14 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 static NMPObject *
 _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 {
-	static const struct nla_policy policy[IFA_MAX+1] = {
+	static const struct nla_policy policy[] = {
 		[IFA_LABEL]     = { .type = NLA_STRING,
-		                     .maxlen = IFNAMSIZ },
+		                    .maxlen = IFNAMSIZ },
 		[IFA_CACHEINFO] = { .minlen = nm_offsetofend (struct ifa_cacheinfo, tstamp) },
+		[IFA_FLAGS]     = { },
 	};
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	const struct ifaddrmsg *ifa;
-	struct nlattr *tb[IFA_MAX+1];
-	int err;
 	gboolean is_v4;
 	nm_auto_nmpobj NMPObject *obj = NULL;
 	int addr_len;
@@ -2646,14 +2999,15 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 
 	if (!nlmsg_valid_hdr (nlh, sizeof (*ifa)))
 		return NULL;
-	ifa = nlmsg_data(nlh);
+
+	ifa = nlmsg_data (nlh);
 
 	if (!NM_IN_SET (ifa->ifa_family, AF_INET, AF_INET6))
 		return NULL;
+
 	is_v4 = ifa->ifa_family == AF_INET;
 
-	err = nlmsg_parse (nlh, sizeof(*ifa), tb, IFA_MAX, policy);
-	if (err < 0)
+	if (nlmsg_parse_arr (nlh, sizeof (*ifa), tb, policy) < 0)
 		return NULL;
 
 	addr_len = is_v4
@@ -2722,8 +3076,9 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 	timestamp = 0;
 	/* IPv6 only */
 	if (tb[IFA_CACHEINFO]) {
-		const struct ifa_cacheinfo *ca = nla_data(tb[IFA_CACHEINFO]);
+		const struct ifa_cacheinfo *ca;
 
+		ca = nla_data_as (struct ifa_cacheinfo, tb[IFA_CACHEINFO]);
 		lifetime = ca->ifa_valid;
 		preferred = ca->ifa_prefered;
 		timestamp = ca->tstamp;
@@ -2742,7 +3097,7 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 static NMPObject *
 _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 {
-	static const struct nla_policy policy[RTA_MAX+1] = {
+	static const struct nla_policy policy[] = {
 		[RTA_TABLE]     = { .type = NLA_U32 },
 		[RTA_IIF]       = { .type = NLA_U32 },
 		[RTA_OIF]       = { .type = NLA_U32 },
@@ -2754,8 +3109,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 		[RTA_MULTIPATH] = { .type = NLA_NESTED },
 	};
 	const struct rtmsg *rtm;
-	struct nlattr *tb[RTA_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	gboolean is_v4;
 	nm_auto_nmpobj NMPObject *obj = NULL;
 	int addr_len;
@@ -2763,13 +3117,21 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 		gboolean is_present;
 		int ifindex;
 		NMIPAddr gateway;
-	} nh;
+	} nh = {
+		.is_present = FALSE,
+	};
 	guint32 mss;
-	guint32 window = 0, cwnd = 0, initcwnd = 0, initrwnd = 0, mtu = 0, lock = 0;
+	guint32 window = 0;
+	guint32 cwnd = 0;
+	guint32 initcwnd = 0;
+	guint32 initrwnd = 0;
+	guint32 mtu = 0;
+	guint32 lock = 0;
 
 	if (!nlmsg_valid_hdr (nlh, sizeof (*rtm)))
 		return NULL;
-	rtm = nlmsg_data(nlh);
+
+	rtm = nlmsg_data (nlh);
 
 	/*****************************************************************
 	 * only handle ~normal~ routes.
@@ -2781,8 +3143,10 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	if (rtm->rtm_type != RTN_UNICAST)
 		return NULL;
 
-	err = nlmsg_parse (nlh, sizeof (struct rtmsg), tb, RTA_MAX, policy);
-	if (err < 0)
+	if (nlmsg_parse_arr (nlh,
+	                     sizeof (struct rtmsg),
+	                     tb,
+	                     policy) < 0)
 		return NULL;
 
 	/*****************************************************************/
@@ -2799,39 +3163,49 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	 * parse nexthops. Only handle routes with one nh.
 	 *****************************************************************/
 
-	memset (&nh, 0, sizeof (nh));
-
 	if (tb[RTA_MULTIPATH]) {
-		struct rtnexthop *rtnh = nla_data (tb[RTA_MULTIPATH]);
-		size_t tlen = nla_len(tb[RTA_MULTIPATH]);
+		size_t tlen = nla_len (tb[RTA_MULTIPATH]);
+		struct rtnexthop *rtnh;
+
+		if (tlen < sizeof (*rtnh))
+			goto rta_multipath_done;
+
+		rtnh = nla_data_as (struct rtnexthop, tb[RTA_MULTIPATH]);
 
-		while (tlen >= sizeof(*rtnh) && tlen >= rtnh->rtnh_len) {
+		if (tlen < rtnh->rtnh_len)
+			goto rta_multipath_done;
+
+		while (TRUE) {
 
 			if (nh.is_present) {
 				/* we don't support multipath routes. */
 				return NULL;
 			}
-			nh.is_present = TRUE;
 
+			nh.is_present = TRUE;
 			nh.ifindex = rtnh->rtnh_ifindex;
 
-			if (rtnh->rtnh_len > sizeof(*rtnh)) {
-				struct nlattr *ntb[RTA_MAX + 1];
+			if (rtnh->rtnh_len > sizeof (*rtnh)) {
+				struct nlattr *ntb[G_N_ELEMENTS (policy)];
 
-				err = nla_parse (ntb, RTA_MAX, (struct nlattr *)
-				                 RTNH_DATA(rtnh),
-				                 rtnh->rtnh_len - sizeof (*rtnh),
-				                 policy);
-				if (err < 0)
+				if (nla_parse_arr (ntb,
+				                   (struct nlattr *) RTNH_DATA (rtnh),
+				                   rtnh->rtnh_len - sizeof (*rtnh),
+				                   policy) < 0)
 					return NULL;
 
 				if (_check_addr_or_return_null (ntb, RTA_GATEWAY, addr_len))
 					memcpy (&nh.gateway, nla_data (ntb[RTA_GATEWAY]), addr_len);
 			}
 
-			tlen -= RTNH_ALIGN(rtnh->rtnh_len);
-			rtnh = RTNH_NEXT(rtnh);
+			if (tlen < RTNH_ALIGN (rtnh->rtnh_len) + sizeof (*rtnh))
+				goto rta_multipath_done;
+
+			tlen -= RTNH_ALIGN (rtnh->rtnh_len);
+			rtnh = RTNH_NEXT (rtnh);
 		}
+rta_multipath_done:
+		;
 	}
 
 	if (   tb[RTA_OIF]
@@ -2865,8 +3239,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 	mss = 0;
 	if (tb[RTA_METRICS]) {
-		struct nlattr *mtb[RTAX_MAX + 1];
-		static const struct nla_policy rtax_policy[RTAX_MAX + 1] = {
+		static const struct nla_policy rtax_policy[] = {
 			[RTAX_LOCK]        = { .type = NLA_U32 },
 			[RTAX_ADVMSS]      = { .type = NLA_U32 },
 			[RTAX_WINDOW]      = { .type = NLA_U32 },
@@ -2875,9 +3248,9 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 			[RTAX_INITRWND]    = { .type = NLA_U32 },
 			[RTAX_MTU]         = { .type = NLA_U32 },
 		};
+		struct nlattr *mtb[G_N_ELEMENTS (rtax_policy)];
 
-		err = nla_parse_nested (mtb, RTAX_MAX, tb[RTA_METRICS], rtax_policy);
-		if (err < 0)
+		if (nla_parse_nested_arr (mtb, tb[RTA_METRICS], rtax_policy) < 0)
 			return NULL;
 
 		if (mtb[RTAX_LOCK])
@@ -2912,7 +3285,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	obj->ip_route.plen = rtm->rtm_dst_len;
 
 	if (tb[RTA_PRIORITY])
-		obj->ip_route.metric = nla_get_u32(tb[RTA_PRIORITY]);
+		obj->ip_route.metric = nla_get_u32 (tb[RTA_PRIORITY]);
 
 	if (is_v4)
 		obj->ip4_route.gateway = nh.gateway.addr4;
@@ -2969,25 +3342,24 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 static NMPObject *
 _new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
 {
-	NMPObject *obj = NULL;
-	const struct tcmsg *tcm;
-	struct nlattr *tb[TCA_MAX + 1];
-	int err;
-	static const struct nla_policy policy[TCA_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[TCA_KIND] = { .type = NLA_STRING },
 	};
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
+	const struct tcmsg *tcm;
+	NMPObject *obj;
 
-	if (!nlmsg_valid_hdr (nlh, sizeof (*tcm)))
-		return NULL;
-	tcm = nlmsg_data (nlh);
-
-	err = nlmsg_parse (nlh, sizeof (*tcm), tb, TCA_MAX, policy);
-	if (err < 0)
+	if (nlmsg_parse_arr (nlh,
+	                     sizeof (*tcm),
+	                     tb,
+	                     policy) < 0)
 		return NULL;
 
 	if (!tb[TCA_KIND])
 		return NULL;
 
+	tcm = nlmsg_data (nlh);
+
 	obj = nmp_object_new (NMP_OBJECT_TYPE_QDISC, NULL);
 
 	obj->qdisc.kind = g_intern_string (nla_get_string (tb[TCA_KIND]));
@@ -3003,25 +3375,21 @@ _new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
 static NMPObject *
 _new_from_nl_tfilter (struct nlmsghdr *nlh, gboolean id_only)
 {
-	NMPObject *obj = NULL;
-	const struct tcmsg *tcm;
-	struct nlattr *tb[TCA_MAX + 1];
-	int err;
-	static const struct nla_policy policy[TCA_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[TCA_KIND] = { .type = NLA_STRING },
 	};
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
+	NMPObject *obj = NULL;
+	const struct tcmsg *tcm;
 
-	if (!nlmsg_valid_hdr (nlh, sizeof (*tcm)))
-		return NULL;
-	tcm = nlmsg_data (nlh);
-
-	err = nlmsg_parse (nlh, sizeof (*tcm), tb, TCA_MAX, policy);
-	if (err < 0)
+	if (nlmsg_parse_arr (nlh, sizeof (*tcm), tb, policy) < 0)
 		return NULL;
 
 	if (!tb[TCA_KIND])
 		return NULL;
 
+	tcm = nlmsg_data (nlh);
+
 	obj = nmp_object_new (NMP_OBJECT_TYPE_TFILTER, NULL);
 
 	obj->tfilter.kind = g_intern_string (nla_get_string (tb[TCA_KIND]));
@@ -3143,14 +3511,14 @@ _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
 	NLA_PUT_STRING (msg, IFLA_INFO_KIND, kind);
 
 	if (veth_peer) {
-		struct ifinfomsg ifi = { };
+		const struct ifinfomsg ifi = { };
 		struct nlattr *data, *info_peer;
 
 		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
 			goto nla_put_failure;
 		if (!(info_peer = nla_nest_start (msg, 1 /*VETH_INFO_PEER*/)))
 			goto nla_put_failure;
-		if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0)
+		if (nlmsg_append_struct (msg, &ifi) < 0)
 			goto nla_put_failure;
 		NLA_PUT_STRING (msg, IFLA_IFNAME, veth_peer);
 		nla_nest_end (msg, info_peer);
@@ -3254,7 +3622,7 @@ _nl_msg_new_link_set_linkinfo_vlan (struct nl_msg *msg,
 		for (i = 0; i < egress_qos_len; i++) {
 			if (VLAN_XGRESS_PRIO_VALID (egress_qos[i].to)) {
 				if (!qos) {
-					if (!(qos = nla_nest_start(msg, IFLA_VLAN_EGRESS_QOS)))
+					if (!(qos = nla_nest_start (msg, IFLA_VLAN_EGRESS_QOS)))
 						goto nla_put_failure;
 				}
 				NLA_PUT (msg, i, sizeof (egress_qos[i]), &egress_qos[i]);
@@ -3262,7 +3630,7 @@ _nl_msg_new_link_set_linkinfo_vlan (struct nl_msg *msg,
 		}
 
 		if (qos)
-			nla_nest_end(msg, qos);
+			nla_nest_end (msg, qos);
 	}
 
 	nla_nest_end (msg, data);
@@ -3281,8 +3649,8 @@ _nl_msg_new_link (int nlmsg_type,
                   unsigned flags_mask,
                   unsigned flags_set)
 {
-	struct nl_msg *msg;
-	struct ifinfomsg ifi = {
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	const struct ifinfomsg ifi = {
 		.ifi_change = flags_mask,
 		.ifi_flags = flags_set,
 		.ifi_index = ifindex,
@@ -3292,15 +3660,15 @@ _nl_msg_new_link (int nlmsg_type,
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
 
-	if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0)
+	if (nlmsg_append_struct (msg, &ifi) < 0)
 		goto nla_put_failure;
 
 	if (ifname)
 		NLA_PUT_STRING (msg, IFLA_IFNAME, ifname);
 
-	return msg;
+	return g_steal_pointer (&msg);
+
 nla_put_failure:
-	nlmsg_free (msg);
 	g_return_val_if_reached (NULL);
 }
 
@@ -3319,7 +3687,7 @@ _nl_msg_new_address (int nlmsg_type,
                      guint32 preferred,
                      const char *label)
 {
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct ifaddrmsg am = {
 		.ifa_family = family,
 		.ifa_index = ifindex,
@@ -3346,7 +3714,7 @@ _nl_msg_new_address (int nlmsg_type,
 
 	addr_len = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr);
 
-	if (nlmsg_append (msg, &am, sizeof (am), NLMSG_ALIGNTO) < 0)
+	if (nlmsg_append_struct (msg, &am) < 0)
 		goto nla_put_failure;
 
 	if (address)
@@ -3377,7 +3745,7 @@ _nl_msg_new_address (int nlmsg_type,
 			.ifa_prefered = preferred,
 		};
 
-		NLA_PUT (msg, IFA_CACHEINFO, sizeof(ca), &ca);
+		NLA_PUT (msg, IFA_CACHEINFO, sizeof (ca), &ca);
 	}
 
 	if (flags & ~((guint32) 0xFF)) {
@@ -3390,10 +3758,9 @@ _nl_msg_new_address (int nlmsg_type,
 		NLA_PUT_U32 (msg, IFA_FLAGS, flags);
 	}
 
-	return msg;
+	return g_steal_pointer (&msg);
 
 nla_put_failure:
-	nlmsg_free (msg);
 	g_return_val_if_reached (NULL);
 }
 
@@ -3413,12 +3780,12 @@ _nl_msg_new_route (int nlmsg_type,
                    guint16 nlmsgflags,
                    const NMPObject *obj)
 {
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
 	gboolean is_v4 = klass->addr_family == AF_INET;
 	const guint32 lock = ip_route_get_lock_flag (NMP_OBJECT_CAST_IP_ROUTE (obj));
 	const guint32 table = nm_platform_route_table_uncoerce (NMP_OBJECT_CAST_IP_ROUTE (obj)->table_coerced, TRUE);
-	struct rtmsg rtmsg = {
+	const struct rtmsg rtmsg = {
 		.rtm_family = klass->addr_family,
 		.rtm_tos = is_v4
 		           ? obj->ip4_route.tos
@@ -3445,7 +3812,7 @@ _nl_msg_new_route (int nlmsg_type,
 
 	msg = nlmsg_alloc_simple (nlmsg_type, (int) nlmsgflags);
 
-	if (nlmsg_append (msg, &rtmsg, sizeof (rtmsg), NLMSG_ALIGNTO) < 0)
+	if (nlmsg_append_struct (msg, &rtmsg) < 0)
 		goto nla_put_failure;
 
 	addr_len = is_v4
@@ -3503,7 +3870,7 @@ _nl_msg_new_route (int nlmsg_type,
 		if (lock)
 			NLA_PUT_U32 (msg, RTAX_LOCK, lock);
 
-		nla_nest_end(msg, metrics);
+		nla_nest_end (msg, metrics);
 	}
 
 	/* We currently don't have need for multi-hop routes... */
@@ -3519,10 +3886,9 @@ _nl_msg_new_route (int nlmsg_type,
 	    && obj->ip6_route.rt_pref != NM_ICMPV6_ROUTER_PREF_MEDIUM)
 		NLA_PUT_U8 (msg, RTA_PREF, obj->ip6_route.rt_pref);
 
-	return msg;
+	return g_steal_pointer (&msg);
 
 nla_put_failure:
-	nlmsg_free (msg);
 	g_return_val_if_reached (NULL);
 }
 
@@ -3531,8 +3897,8 @@ _nl_msg_new_qdisc (int nlmsg_type,
                    int nlmsg_flags,
                    const NMPlatformQdisc *qdisc)
 {
-	struct nl_msg *msg;
-	struct tcmsg tcm = {
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	const struct tcmsg tcm = {
 		.tcm_family = qdisc->addr_family,
 		.tcm_ifindex = qdisc->ifindex,
 		.tcm_handle = qdisc->handle,
@@ -3542,14 +3908,14 @@ _nl_msg_new_qdisc (int nlmsg_type,
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
 
-	if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0)
+	if (nlmsg_append_struct (msg, &tcm) < 0)
 		goto nla_put_failure;
 
 	NLA_PUT_STRING (msg, TCA_KIND, qdisc->kind);
 
-	return msg;
+	return g_steal_pointer (&msg);
+
 nla_put_failure:
-	nlmsg_free (msg);
 	g_return_val_if_reached (NULL);
 }
 
@@ -3603,10 +3969,10 @@ _nl_msg_new_tfilter (int nlmsg_type,
                      int nlmsg_flags,
                      const NMPlatformTfilter *tfilter)
 {
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct nlattr *tc_options;
 	struct nlattr *act_tab;
-	struct tcmsg tcm = {
+	const struct tcmsg tcm = {
 		.tcm_family = tfilter->addr_family,
 		.tcm_ifindex = tfilter->ifindex,
 		.tcm_handle = tfilter->handle,
@@ -3616,7 +3982,7 @@ _nl_msg_new_tfilter (int nlmsg_type,
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
 
-	if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0)
+	if (nlmsg_append_struct (msg, &tcm) < 0)
 		goto nla_put_failure;
 
 	NLA_PUT_STRING (msg, TCA_KIND, tfilter->kind);
@@ -3634,9 +4000,9 @@ _nl_msg_new_tfilter (int nlmsg_type,
 
 	nla_nest_end (msg, act_tab);
 
-	return msg;
+	return g_steal_pointer (&msg);
+
 nla_put_failure:
-	nlmsg_free (msg);
 	g_return_val_if_reached (NULL);
 }
 
@@ -3731,10 +4097,10 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 			errsv = errno;
 			if (errsv == ENOENT) {
 				_LOGD ("sysctl: failed to open '%s': (%d) %s",
-				       pathid, errsv, strerror (errsv));
+				       pathid, errsv, nm_strerror_native (errsv));
 			} else {
 				_LOGE ("sysctl: failed to open '%s': (%d) %s",
-				       pathid, errsv, strerror (errsv));
+				       pathid, errsv, nm_strerror_native (errsv));
 			}
 			errno = errsv;
 			return FALSE;
@@ -3745,10 +4111,10 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 			errsv = errno;
 			if (errsv == ENOENT) {
 				_LOGD ("sysctl: failed to openat '%s': (%d) %s",
-				       pathid, errsv, strerror (errsv));
+				       pathid, errsv, nm_strerror_native (errsv));
 			} else {
 				_LOGE ("sysctl: failed to openat '%s': (%d) %s",
-				       pathid, errsv, strerror (errsv));
+				       pathid, errsv, nm_strerror_native (errsv));
 			}
 			errno = errsv;
 			return FALSE;
@@ -3798,7 +4164,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 		}
 
 		_NMLOG (level, "sysctl: failed to set '%s' to '%s': (%d) %s",
-		        path, value, errsv, strerror (errsv));
+		        path, value, errsv, nm_strerror_native (errsv));
 	} else if (nwrote < len - 1) {
 		_LOGE ("sysctl: failed to set '%s' to '%s' after three attempts",
 		       path, value);
@@ -3825,8 +4191,8 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 
 static GSList *sysctl_clear_cache_list;
 
-static void
-_nm_logging_clear_platform_logging_cache_impl (void)
+void
+_nm_logging_clear_platform_logging_cache (void)
 {
 	while (sysctl_clear_cache_list) {
 		NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (sysctl_clear_cache_list->data);
@@ -3835,42 +4201,71 @@ _nm_logging_clear_platform_logging_cache_impl (void)
 
 		g_hash_table_destroy (priv->sysctl_get_prev_values);
 		priv->sysctl_get_prev_values = NULL;
-		priv->sysctl_get_warned = FALSE;
 	}
 }
 
+typedef struct {
+	const char *path;
+	CList lst;
+	char *value;
+	char path_data[];
+} SysctlCacheEntry;
+
+static void
+sysctl_cache_entry_free (SysctlCacheEntry *entry)
+{
+	c_list_unlink_stale (&entry->lst);
+	g_free (entry->value);
+	g_free (entry);
+}
+
 static void
 _log_dbg_sysctl_get_impl (NMPlatform *platform, const char *pathid, const char *contents)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	const char *prev_value = NULL;
+	SysctlCacheEntry *entry = NULL;
 
 	if (!priv->sysctl_get_prev_values) {
-		_nm_logging_clear_platform_logging_cache = _nm_logging_clear_platform_logging_cache_impl;
 		sysctl_clear_cache_list = g_slist_prepend (sysctl_clear_cache_list, platform);
-		priv->sysctl_get_prev_values = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
+		c_list_init (&priv->sysctl_list);
+		priv->sysctl_get_prev_values = g_hash_table_new_full (nm_pstr_hash,
+		                                                      nm_pstr_equal,
+		                                                      (GDestroyNotify) sysctl_cache_entry_free,
+		                                                      NULL);
 	} else
-		prev_value = g_hash_table_lookup (priv->sysctl_get_prev_values, pathid);
+		entry = g_hash_table_lookup (priv->sysctl_get_prev_values, &pathid);
 
-	if (prev_value) {
-		if (strcmp (prev_value, contents) != 0) {
+	if (entry) {
+		if (!nm_streq (entry->value, contents)) {
 			gs_free char *contents_escaped = g_strescape (contents, NULL);
-			gs_free char *prev_value_escaped = g_strescape (prev_value, NULL);
+			gs_free char *prev_value_escaped = g_strescape (entry->value, NULL);
 
 			_LOGD ("sysctl: reading '%s': '%s' (changed from '%s' on last read)", pathid, contents_escaped, prev_value_escaped);
-			g_hash_table_insert (priv->sysctl_get_prev_values, g_strdup (pathid), g_strdup (contents));
+			g_free (entry->value);
+			entry->value = g_strdup (contents);
 		}
+		nm_c_list_move_front (&priv->sysctl_list, &entry->lst);
 	} else {
 		gs_free char *contents_escaped = g_strescape (contents, NULL);
+		SysctlCacheEntry *old;
+		size_t len;
+
+		len = strlen (pathid);
+		entry = g_malloc (sizeof (SysctlCacheEntry) + len + 1);
+		entry->value = g_strdup (contents);
+		entry->path = entry->path_data;
+		memcpy (entry->path_data, pathid, len + 1);
+
+		/* Remove oldest entry when the cache becomes too big */
+		if (g_hash_table_size (priv->sysctl_get_prev_values) > 1000) {
+			old = c_list_last_entry (&priv->sysctl_list, SysctlCacheEntry, lst);
+			g_hash_table_remove (priv->sysctl_get_prev_values, old);
+		}
 
 		_LOGD ("sysctl: reading '%s': '%s'", pathid, contents_escaped);
-		g_hash_table_insert (priv->sysctl_get_prev_values, g_strdup (pathid), g_strdup (contents));
 
-		if (   !priv->sysctl_get_warned
-		    && g_hash_table_size (priv->sysctl_get_prev_values) > 50000) {
-			_LOGW ("sysctl: the internal cache for debug-logging of sysctl values grew pretty large. You can clear it by disabling debug-logging: `nmcli general logging level KEEP domains PLATFORM:INFO`.");
-			priv->sysctl_get_warned = TRUE;
-		}
+		g_hash_table_add (priv->sysctl_get_prev_values, entry);
+		c_list_link_front (&priv->sysctl_list, &entry->lst);
 	}
 }
 
@@ -4254,9 +4649,8 @@ delayed_action_handle_one (NMPlatform *platform)
 		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_ALL;
 
 		if (_LOGt_ENABLED ()) {
-			FOR_EACH_DELAYED_ACTION (iflags, flags) {
+			FOR_EACH_DELAYED_ACTION (iflags, flags)
 				_LOGt_delayed_action (iflags, NULL, "handle");
-			}
 		}
 
 		delayed_action_handle_REFRESH_ALL (platform, flags);
@@ -4340,9 +4734,8 @@ delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gp
 	priv->delayed_action.flags |= action_type;
 
 	if (_LOGt_ENABLED ()) {
-		FOR_EACH_DELAYED_ACTION (iflags, action_type) {
+		FOR_EACH_DELAYED_ACTION (iflags, action_type)
 			_LOGt_delayed_action (iflags, user_data, "schedule");
-		}
 	}
 }
 
@@ -4672,7 +5065,7 @@ _nl_send_nlmsghdr (NMPlatform *platform,
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	guint32 seq;
-	int nle;
+	int errsv;
 
 	nm_assert (nlhdr);
 
@@ -4689,7 +5082,7 @@ _nl_send_nlmsghdr (NMPlatform *platform,
 		};
 		struct msghdr msg = {
 			.msg_name = &nladdr,
-			.msg_namelen = sizeof(nladdr),
+			.msg_namelen = sizeof (nladdr),
 			.msg_iov = &iov,
 			.msg_iovlen = 1,
 		};
@@ -4701,13 +5094,13 @@ _nl_send_nlmsghdr (NMPlatform *platform,
 
 		try_count = 0;
 again:
-		nle = sendmsg (nl_socket_get_fd (priv->nlh), &msg, 0);
-		if (nle < 0) {
-			nle = errno;
-			if (nle == EINTR && try_count++ < 100)
+		errsv = sendmsg (nl_socket_get_fd (priv->nlh), &msg, 0);
+		if (errsv < 0) {
+			errsv = errno;
+			if (errsv == EINTR && try_count++ < 100)
 				goto again;
-			_LOGD ("netlink: nl-send-nlmsghdr: failed sending message: %s (%d)", g_strerror (nle), nle);
-			return -nle;
+			_LOGD ("netlink: nl-send-nlmsghdr: failed sending message: %s (%d)", nm_strerror_native (errsv), errsv);
+			return -nm_errno_from_native (errsv);
 		}
 	}
 
@@ -4745,7 +5138,7 @@ _nl_send_nlmsg (NMPlatform *platform,
 
 	nle = nl_send_auto (priv->nlh, nlmsg);
 	if (nle < 0) {
-		_LOGD ("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nl_geterror (nle), nle);
+		_LOGD ("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nm_strerror (nle), nle);
 		return nle;
 	}
 
@@ -4791,7 +5184,7 @@ do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const cha
 		if (nle < 0) {
 			_LOGE ("do-request-link: %d %s: failed sending netlink request \"%s\" (%d)",
 			       ifindex, name ?: "",
-			       nl_geterror (nle), -nle);
+			       nm_strerror (nle), -nle);
 			return;
 		}
 	}
@@ -4804,6 +5197,59 @@ do_request_link (NMPlatform *platform, int ifindex, const char *name)
 	delayed_action_handle_all (platform, FALSE);
 }
 
+static struct nl_msg *
+_nl_msg_new_dump (NMPObjectType obj_type,
+                  int preferred_addr_family)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	const NMPClass *klass;
+
+	klass = nmp_class_from_type (obj_type);
+
+	nm_assert (klass);
+	nm_assert (klass->rtm_gettype > 0);
+
+	nlmsg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP);
+
+	if (klass->addr_family != AF_UNSPEC) {
+		/* if the class specifies a particular address family, then it is preferred. */
+		nm_assert (NM_IN_SET (preferred_addr_family, AF_UNSPEC, klass->addr_family));
+		preferred_addr_family = klass->addr_family;
+	}
+
+	switch (klass->obj_type) {
+	case NMP_OBJECT_TYPE_QDISC:
+	case NMP_OBJECT_TYPE_TFILTER:
+		{
+			const struct tcmsg tcmsg = {
+				.tcm_family = preferred_addr_family,
+			};
+
+			if (nlmsg_append_struct (nlmsg, &tcmsg) < 0)
+				g_return_val_if_reached (NULL);
+		}
+		break;
+	case NMP_OBJECT_TYPE_LINK:
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+	case NMP_OBJECT_TYPE_IP6_ROUTE:
+		{
+			const struct rtgenmsg gmsg = {
+				.rtgen_family = preferred_addr_family,
+			};
+
+			if (nlmsg_append_struct (nlmsg, &gmsg) < 0)
+				g_return_val_if_reached (NULL);
+		}
+		break;
+	default:
+		g_return_val_if_reached (NULL);
+	}
+
+	return g_steal_pointer (&nlmsg);
+}
+
 static void
 do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType action_type)
 {
@@ -4814,16 +5260,18 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 	action_type &= DELAYED_ACTION_TYPE_REFRESH_ALL;
 
 	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
+		NMPLookup lookup;
+
 		priv->pruning[delayed_action_refresh_all_to_idx (iflags)] = TRUE;
+		nmp_lookup_init_obj_type (&lookup,
+		                          delayed_action_refresh_to_object_type (iflags));
 		nmp_cache_dirty_set_all (nm_platform_get_cache (platform),
-		                         delayed_action_refresh_to_object_type (iflags));
+		                         &lookup);
 	}
 
 	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
 		NMPObjectType obj_type = delayed_action_refresh_to_object_type (iflags);
-		const NMPClass *klass = nmp_class_from_type (obj_type);
 		nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-		int nle;
 		int *out_refresh_all_in_progress;
 
 		out_refresh_all_in_progress = &priv->delayed_action.refresh_all_in_progress[delayed_action_refresh_all_to_idx (iflags)];
@@ -4841,31 +5289,23 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 
 		event_handler_read_netlink (platform, FALSE);
 
-		/* reimplement
-		 *   nl_rtgen_request (sk, klass->rtm_gettype, klass->addr_family, NLM_F_DUMP);
-		 * because we need the sequence number.
-		 */
-		nlmsg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP);
+		nlmsg = _nl_msg_new_dump (obj_type, AF_UNSPEC);
+		if (!nlmsg)
+			goto next_after_fail;
 
-		if (   klass->obj_type == NMP_OBJECT_TYPE_QDISC
-		    || klass->obj_type == NMP_OBJECT_TYPE_TFILTER) {
-			struct tcmsg tcmsg = {
-				.tcm_family = AF_UNSPEC,
-			};
-			nle = nlmsg_append (nlmsg, &tcmsg, sizeof (tcmsg), NLMSG_ALIGNTO);
-		} else {
-			struct rtgenmsg gmsg = {
-				.rtgen_family = klass->addr_family,
-			};
-			nle = nlmsg_append (nlmsg, &gmsg, sizeof (gmsg), NLMSG_ALIGNTO);
-		}
-		if (nle < 0)
-			continue;
+		if (_nl_send_nlmsg (platform,
+		                    nlmsg,
+		                    NULL,
+		                    NULL,
+		                    DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS,
+		                    out_refresh_all_in_progress) < 0)
+			goto next_after_fail;
 
-		if (_nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progress) < 0) {
-			nm_assert (*out_refresh_all_in_progress > 0);
-			*out_refresh_all_in_progress -= 1;
-		}
+		continue;
+
+next_after_fail:
+		nm_assert (*out_refresh_all_in_progress > 0);
+		*out_refresh_all_in_progress -= 1;
 	}
 }
 
@@ -4952,9 +5392,9 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	NMPCacheOpsType cache_op;
 	struct nlmsghdr *msghdr;
 	char buf_nlmsghdr[400];
-	gboolean id_only = FALSE;
+	gboolean is_del = FALSE;
+	gboolean is_dump = FALSE;
 	NMPCache *cache = nm_platform_get_cache (platform);
-	gboolean is_dump;
 
 	msghdr = nlmsg_hdr (msg);
 
@@ -4965,37 +5405,38 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	if (!handle_events)
 		return;
 
-	if (NM_IN_SET (msghdr->nlmsg_type, RTM_DELLINK, RTM_DELADDR, RTM_DELROUTE)) {
+	if (NM_IN_SET (msghdr->nlmsg_type, RTM_DELLINK,
+	                                   RTM_DELADDR,
+	                                   RTM_DELROUTE,
+	                                   RTM_DELQDISC,
+	                                   RTM_DELTFILTER)) {
 		/* The event notifies about a deleted object. We don't need to initialize all
 		 * fields of the object. */
-		id_only = TRUE;
+		is_del = TRUE;
 	}
 
-	obj = nmp_object_new_from_nl (platform, cache, msg, id_only);
+	obj = nmp_object_new_from_nl (platform, cache, msg, is_del);
 	if (!obj) {
 		_LOGT ("event-notification: %s: ignore",
 		       nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 		return;
 	}
 
-	switch (msghdr->nlmsg_type) {
-	case RTM_NEWADDR:
-	case RTM_NEWLINK:
-	case RTM_NEWROUTE:
-	case RTM_NEWQDISC:
-	case RTM_NEWTFILTER:
+	if (   !is_del
+	    && NM_IN_SET (msghdr->nlmsg_type, RTM_NEWADDR,
+	                                      RTM_NEWLINK,
+	                                      RTM_NEWROUTE,
+	                                      RTM_NEWQDISC,
+	                                      RTM_NEWTFILTER)) {
 		is_dump = delayed_action_refresh_all_in_progress (platform,
 		                                                  delayed_action_refresh_from_object_type (NMP_OBJECT_GET_TYPE (obj)));
-		break;
-	default:
-		is_dump = FALSE;
 	}
 
 	_LOGT ("event-notification: %s%s: %s",
 	       nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)),
 	       is_dump ? ", in-dump" : "",
 	       nmp_object_to_string (obj,
-	                             id_only ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC,
+	                             is_del ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC,
 	                             NULL, 0));
 
 	{
@@ -5121,7 +5562,7 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 
 /*****************************************************************************/
 
-static gboolean
+static int
 do_add_link_with_lookup (NMPlatform *platform,
                          NMLinkType link_type,
                          const char *name,
@@ -5142,9 +5583,9 @@ do_add_link_with_lookup (NMPlatform *platform,
 		_LOGE ("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
 		       name,
 		       nm_link_type_to_string (link_type),
-		       nl_geterror (nle), -nle);
+		       nm_strerror (nle), -nle);
 		NM_SET_OUT (out_link, NULL);
-		return FALSE;
+		return nle;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
@@ -5164,10 +5605,10 @@ do_add_link_with_lookup (NMPlatform *platform,
 		*out_link = NMP_OBJECT_CAST_LINK (obj);
 	}
 
-	return seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+	return wait_for_nl_response_to_nmerr (seq_result);
 }
 
-static NMPlatformError
+static int
 do_add_addrroute (NMPlatform *platform,
                   const NMPObject *obj_id,
                   struct nl_msg *nlmsg,
@@ -5189,8 +5630,8 @@ do_add_addrroute (NMPlatform *platform,
 		_LOGE ("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)",
 		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 		       nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-		       nl_geterror (nle), -nle);
-		return NM_PLATFORM_ERROR_NETLINK;
+		       nm_strerror (nle), -nle);
+		return -NME_PL_NETLINK;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
@@ -5219,7 +5660,7 @@ do_add_addrroute (NMPlatform *platform,
 			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
 	}
 
-	return wait_for_nl_response_to_plerr (seq_result);
+	return wait_for_nl_response_to_nmerr (seq_result);
 }
 
 static gboolean
@@ -5239,7 +5680,7 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 		_LOGE ("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)",
 		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 		       nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-		       nl_geterror (nle), -nle);
+		       nm_strerror (nle), -nle);
 		return FALSE;
 	}
 
@@ -5287,7 +5728,7 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 	return success;
 }
 
-static NMPlatformError
+static int
 do_change_link (NMPlatform *platform,
                 ChangeLinkType change_link_type,
                 int ifindex,
@@ -5299,7 +5740,7 @@ do_change_link (NMPlatform *platform,
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	gs_free char *errmsg = NULL;
 	char s_buf[256];
-	NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS;
+	int result = 0;
 	NMLogLevel log_level = LOGL_DEBUG;
 	const char *log_result = "failure";
 	const char *log_detail = "";
@@ -5317,7 +5758,7 @@ retry:
 	if (nle < 0) {
 		log_level = LOGL_ERR;
 		log_detail_free = g_strdup_printf (", failure sending netlink request: %s (%d)",
-		                                   nl_geterror (nle), -nle);
+		                                   nm_strerror (nle), -nle);
 		log_detail = log_detail_free;
 		goto out;
 	}
@@ -5342,11 +5783,11 @@ retry:
 		/* */
 	} else if (NM_IN_SET (-((int) seq_result), ESRCH, ENOENT)) {
 		log_detail = ", firmware not found";
-		result = NM_PLATFORM_ERROR_NO_FIRMWARE;
+		result = -NME_PL_NO_FIRMWARE;
 	} else if (   NM_IN_SET (-((int) seq_result), ERANGE)
 	           && change_link_type == CHANGE_LINK_TYPE_SET_MTU) {
 		log_detail = ", setting MTU to requested size is not possible";
-		result = NM_PLATFORM_ERROR_CANT_SET_MTU;
+		result = -NME_PL_CANT_SET_MTU;
 	} else if (   NM_IN_SET (-((int) seq_result), ENFILE)
 	           && change_link_type == CHANGE_LINK_TYPE_SET_ADDRESS
 	           && (obj_cache = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex))
@@ -5356,16 +5797,16 @@ retry:
 		 * If the MAC address is as expected, assume success? */
 		log_result = "success";
 		log_detail = " (assume success changing address)";
-		result = NM_PLATFORM_ERROR_SUCCESS;
+		result = 0;
 	} else if (NM_IN_SET (-((int) seq_result), ENODEV)) {
 		log_level = LOGL_DEBUG;
-		result = NM_PLATFORM_ERROR_NOT_FOUND;
+		result = -NME_PL_NOT_FOUND;
 	} else if (-((int) seq_result) == EAFNOSUPPORT) {
 		log_level = LOGL_DEBUG;
-		result = NM_PLATFORM_ERROR_OPNOTSUPP;
+		result = -NME_PL_OPNOTSUPP;
 	} else {
 		log_level = LOGL_WARN;
-		result = NM_PLATFORM_ERROR_UNSPECIFIED;
+		result = -NME_UNSPEC;
 	}
 
 out:
@@ -5378,7 +5819,7 @@ out:
 	return result;
 }
 
-static gboolean
+static int
 link_add (NMPlatform *platform,
           const char *name,
           NMLinkType type,
@@ -5408,17 +5849,17 @@ link_add (NMPlatform *platform,
 	                          0,
 	                          0);
 	if (!nlmsg)
-		return FALSE;
+		return -NME_UNSPEC;
 
 	if (address && address_len)
 		NLA_PUT (nlmsg, IFLA_ADDRESS, address_len, address);
 
 	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type, veth_peer))
-		return FALSE;
+		return -NME_UNSPEC;
 
 	return do_add_link_with_lookup (platform, type, name, nlmsg, out_link);
 nla_put_failure:
-	g_return_val_if_reached (FALSE);
+	g_return_val_if_reached (-NME_BUG);
 }
 
 static gboolean
@@ -5473,13 +5914,13 @@ link_set_netns (NMPlatform *platform,
 		return FALSE;
 
 	NLA_PUT (nlmsg, IFLA_NET_NS_FD, 4, &netns_fd);
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static NMPlatformError
+static int
 link_change_flags (NMPlatform *platform,
                    int ifindex,
                    unsigned flags_mask,
@@ -5502,37 +5943,36 @@ link_change_flags (NMPlatform *platform,
 	                          flags_mask,
 	                          flags_set);
 	if (!nlmsg)
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
+		return -NME_UNSPEC;
 	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
 }
 
 static gboolean
 link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 {
-	NMPlatformError plerr;
+	int r;
 
-	plerr = link_change_flags (platform, ifindex, IFF_UP, IFF_UP);
-	if (out_no_firmware)
-		*out_no_firmware = plerr == NM_PLATFORM_ERROR_NO_FIRMWARE;
-	return plerr == NM_PLATFORM_ERROR_SUCCESS;
+	r = link_change_flags (platform, ifindex, IFF_UP, IFF_UP);
+	NM_SET_OUT (out_no_firmware, (r == -NME_PL_NO_FIRMWARE));
+	return r >= 0;
 }
 
 static gboolean
 link_set_down (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_UP, 0) == NM_PLATFORM_ERROR_SUCCESS;
+	return (link_change_flags (platform, ifindex, IFF_UP, 0) >= 0);
 }
 
 static gboolean
 link_set_arp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, 0) == NM_PLATFORM_ERROR_SUCCESS;
+	return (link_change_flags (platform, ifindex, IFF_NOARP, 0) >= 0);
 }
 
 static gboolean
 link_set_noarp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, IFF_NOARP) == NM_PLATFORM_ERROR_SUCCESS;
+	return (link_change_flags (platform, ifindex, IFF_NOARP, IFF_NOARP) >= 0);
 }
 
 static const char *
@@ -5547,7 +5987,7 @@ link_get_udi (NMPlatform *platform, int ifindex)
 	return udev_device_get_syspath (obj->_link.udev.device);
 }
 
-static NMPlatformError
+static int
 link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -5559,7 +5999,7 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
 
 	if (!_support_user_ipv6ll_get ()) {
 		_LOGD ("link: change %d: user-ipv6ll: not supported", ifindex);
-		return NM_PLATFORM_ERROR_OPNOTSUPP;
+		return -NME_PL_OPNOTSUPP;
 	}
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
@@ -5570,7 +6010,7 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
 	                          0);
 	if (   !nlmsg
 	    || !_nl_msg_new_link_set_afspec (nlmsg, mode, NULL))
-		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+		g_return_val_if_reached (-NME_BUG);
 
 	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
 }
@@ -5579,15 +6019,16 @@ static gboolean
 link_set_token (NMPlatform *platform, int ifindex, NMUtilsIPv6IfaceId iid)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	_LOGD ("link: change %d: token: set IPv6 address generation token to %s",
-	       ifindex, nm_utils_inet6_interface_identifier_to_token (iid, NULL));
+	       ifindex, nm_utils_inet6_interface_identifier_to_token (iid, sbuf));
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK, 0, ifindex, NULL, 0, 0);
 	if (!nlmsg || !_nl_msg_new_link_set_afspec (nlmsg, -1, &iid))
 		g_return_val_if_reached (FALSE);
 
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 }
 
 static gboolean
@@ -5647,7 +6088,7 @@ link_supports_sriov (NMPlatform *platform, int ifindex)
 	return total > 0;
 }
 
-static NMPlatformError
+static int
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size_t length)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -5659,7 +6100,7 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 	};
 
 	if (!address || !length)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+		g_return_val_if_reached (-NME_BUG);
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
@@ -5668,16 +6109,16 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 	                          0,
 	                          0);
 	if (!nlmsg)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+		g_return_val_if_reached (-NME_BUG);
 
 	NLA_PUT (nlmsg, IFLA_ADDRESS, length, address);
 
 	return do_change_link (platform, CHANGE_LINK_TYPE_SET_ADDRESS, ifindex, nlmsg, &d);
 nla_put_failure:
-	g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+	g_return_val_if_reached (-NME_BUG);
 }
 
-static NMPlatformError
+static int
 link_set_name (NMPlatform *platform, int ifindex, const char *name)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -5689,11 +6130,11 @@ link_set_name (NMPlatform *platform, int ifindex, const char *name)
 	                          0,
 	                          0);
 	if (!nlmsg)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+		g_return_val_if_reached (-NME_BUG);
 
 	NLA_PUT (nlmsg, IFLA_IFNAME, strlen (name) + 1, name);
 
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -5712,7 +6153,7 @@ link_get_permanent_address (NMPlatform *platform,
 	return nmp_utils_ethtool_get_permanent_address (ifindex, buf, length);
 }
 
-static NMPlatformError
+static int
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -5746,6 +6187,7 @@ link_set_sriov_params (NMPlatform *platform,
 	gint64 current_num;
 	char ifname[IFNAMSIZ];
 	char buf[64];
+	int errsv;
 
 	if (!nm_platform_netns_push (platform, &netns))
 		return FALSE;
@@ -5793,7 +6235,8 @@ link_set_sriov_params (NMPlatform *platform,
 		                                                       ifname,
 		                                                      "device/sriov_numvfs"),
 		                             "0")) {
-			_LOGW ("link: couldn't reset SR-IOV num_vfs: %s", strerror (errno));
+			errsv = errno;
+			_LOGW ("link: couldn't reset SR-IOV num_vfs: %s", nm_strerror_native (errsv));
 			return FALSE;
 		}
 	}
@@ -5808,7 +6251,8 @@ link_set_sriov_params (NMPlatform *platform,
 	                                                          ifname,
 	                                                          "device/sriov_drivers_autoprobe"),
 	                                nm_sprintf_buf (buf, "%d", (int) autoprobe))) {
-		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", (int) autoprobe, strerror (errno));
+		errsv = errno;
+		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", (int) autoprobe, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -5817,7 +6261,8 @@ link_set_sriov_params (NMPlatform *platform,
 	                                                       ifname,
 	                                                       "device/sriov_numvfs"),
 	                             nm_sprintf_buf (buf, "%u", num_vfs))) {
-		_LOGW ("link: couldn't set SR-IOV num_vfs to %d: %s", num_vfs, strerror (errno));
+		errsv = errno;
+		_LOGW ("link: couldn't set SR-IOV num_vfs to %d: %s", num_vfs, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -5838,7 +6283,7 @@ link_set_sriov_vfs (NMPlatform *platform, int ifindex, const NMPlatformVF *const
 	                          0,
 	                          0);
 	if (!nlmsg)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+		g_return_val_if_reached (-NME_BUG);
 
 	if (!(list = nla_nest_start (nlmsg, IFLA_VFINFO_LIST)))
 		goto nla_put_failure;
@@ -5914,7 +6359,7 @@ link_set_sriov_vfs (NMPlatform *platform, int ifindex, const NMPlatformVF *const
 	}
 	nla_nest_end (nlmsg, list);
 
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -5982,7 +6427,7 @@ vlan_add (NMPlatform *platform,
 	                                         0))
 		return FALSE;
 
-	return do_add_link_with_lookup (platform, NM_LINK_TYPE_VLAN, name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_VLAN, name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6029,9 +6474,9 @@ link_gre_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform,
-	                                props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE,
-	                                name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform,
+	                                 props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE,
+	                                 name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6087,7 +6532,7 @@ link_ip6tnl_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform, NM_LINK_TYPE_IP6TNL, name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_IP6TNL, name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6147,9 +6592,9 @@ link_ip6gre_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform,
-	                                props->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE,
-	                                name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform,
+	                                 props->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE,
+	                                 name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6192,7 +6637,7 @@ link_ipip_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform, NM_LINK_TYPE_IPIP, name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_IPIP, name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6247,9 +6692,9 @@ link_macsec_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform,
-	                               NM_LINK_TYPE_MACSEC,
-	                                name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform,
+	                                 NM_LINK_TYPE_MACSEC,
+	                                 name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6290,9 +6735,9 @@ link_macvlan_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform,
-	                                props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN,
-	                                name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform,
+	                                 props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN,
+	                                 name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6335,7 +6780,7 @@ link_sit_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform, NM_LINK_TYPE_SIT, name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_SIT, name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6461,7 +6906,7 @@ link_vxlan_add (NMPlatform *platform,
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform, NM_LINK_TYPE_VXLAN, name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_VXLAN, name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6493,9 +6938,9 @@ link_6lowpan_add (NMPlatform *platform,
 
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform,
-	                                NM_LINK_TYPE_6LOWPAN,
-	                                name, nlmsg, out_link);
+	return (do_add_link_with_lookup (platform,
+	                                 NM_LINK_TYPE_6LOWPAN,
+	                                 name, nlmsg, out_link) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6642,7 +7087,7 @@ link_vlan_change (NMPlatform *platform,
 	                                            new_n_egress_map))
 		g_return_val_if_reached (FALSE);
 
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 }
 
 static gboolean
@@ -6662,7 +7107,7 @@ link_enslave (NMPlatform *platform, int master, int slave)
 
 	NLA_PUT_U32 (nlmsg, IFLA_MASTER, master);
 
-	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -6948,6 +7393,13 @@ wpan_set_short_addr (NMPlatform *platform, int ifindex, guint16 short_addr)
 	return nm_wpan_utils_set_short_addr (wpan_data, short_addr);
 }
 
+static gboolean
+wpan_set_channel (NMPlatform *platform, int ifindex, guint8 page, guint8 channel)
+{
+	WPAN_GET_WPAN_DATA (wpan_data, platform, ifindex, FALSE);
+	return nm_wpan_utils_set_channel (wpan_data, page, channel);
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -7023,7 +7475,7 @@ ip4_address_add (NMPlatform *platform,
 	                             label);
 
 	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_addr);
-	return do_add_addrroute (platform, &obj_id, nlmsg, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_add_addrroute (platform, &obj_id, nlmsg, FALSE) >= 0);
 }
 
 static gboolean
@@ -7053,7 +7505,7 @@ ip6_address_add (NMPlatform *platform,
 	                             NULL);
 
 	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr);
-	return do_add_addrroute (platform, &obj_id, nlmsg, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
+	return (do_add_addrroute (platform, &obj_id, nlmsg, FALSE) >= 0);
 }
 
 static gboolean
@@ -7108,7 +7560,7 @@ ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, gui
 
 /*****************************************************************************/
 
-static NMPlatformError
+static int
 ip_route_add (NMPlatform *platform,
               NMPNlmFlags flags,
               int addr_family,
@@ -7132,7 +7584,7 @@ ip_route_add (NMPlatform *platform,
 
 	nlmsg = _nl_msg_new_route (RTM_NEWROUTE, flags & NMP_NLM_FLAG_FMASK, &obj);
 	if (!nlmsg)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+		g_return_val_if_reached (-NME_BUG);
 	return do_add_addrroute (platform,
 	                         &obj,
 	                         nlmsg,
@@ -7171,7 +7623,7 @@ object_delete (NMPlatform *platform,
 
 /*****************************************************************************/
 
-static NMPlatformError
+static int
 ip_route_get (NMPlatform *platform,
               int addr_family,
               gconstpointer address,
@@ -7220,8 +7672,8 @@ ip_route_get (NMPlatform *platform,
 		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);
-			return NM_PLATFORM_ERROR_UNSPECIFIED;
+			       nm_strerror_native (-nle), -nle);
+			return -NME_UNSPEC;
 		}
 
 		delayed_action_handle_all (platform, FALSE);
@@ -7233,24 +7685,24 @@ ip_route_get (NMPlatform *platform,
 
 	if (seq_result < 0) {
 		/* negative seq_result is an errno from kernel. Map it to negative
-		 * NMPlatformError (which are also errno). */
-		return (NMPlatformError) seq_result;
+		 * int (which are also errno). */
+		return (int) seq_result;
 	}
 
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
 		if (route) {
 			NM_SET_OUT (out_route, g_steal_pointer (&route));
-			return NM_PLATFORM_ERROR_SUCCESS;
+			return 0;
 		}
 		seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN;
 	}
 
-	return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return -NME_UNSPEC;
 }
 
 /*****************************************************************************/
 
-static NMPlatformError
+static int
 qdisc_add (NMPlatform *platform,
            NMPNlmFlags flags,
            const NMPlatformQdisc *qdisc)
@@ -7268,8 +7720,8 @@ qdisc_add (NMPlatform *platform,
 	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);
-		return NM_PLATFORM_ERROR_NETLINK;
+		      nm_strerror (nle), -nle);
+		return -NME_PL_NETLINK;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
@@ -7283,14 +7735,14 @@ qdisc_add (NMPlatform *platform,
 	        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;
+		return 0;
 
-	return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return -NME_UNSPEC;
 }
 
 /*****************************************************************************/
 
-static NMPlatformError
+static int
 tfilter_add (NMPlatform *platform,
              NMPNlmFlags flags,
              const NMPlatformTfilter *tfilter)
@@ -7308,8 +7760,8 @@ tfilter_add (NMPlatform *platform,
 	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);
-		return NM_PLATFORM_ERROR_NETLINK;
+		      nm_strerror (nle), -nle);
+		return -NME_PL_NETLINK;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
@@ -7323,9 +7775,9 @@ tfilter_add (NMPlatform *platform,
 	        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;
+		return 0;
 
-	return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return -NME_UNSPEC;
 }
 
 /*****************************************************************************/
@@ -7358,17 +7810,17 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 	struct nlmsghdr *hdr;
 	WaitForNlResponseResult seq_result;
 	struct sockaddr_nl nla = {0};
-	nm_auto_free struct ucred *creds = NULL;
+	struct ucred creds;
+	gboolean creds_has;
 	nm_auto_free unsigned char *buf = NULL;
 
 continue_reading:
 	g_clear_pointer (&buf, free);
-	g_clear_pointer (&creds, free);
-	n = nl_recv (sk, &nla, &buf, &creds);
+	n = nl_recv (sk, &nla, &buf, &creds, &creds_has);
 
 	if (n <= 0) {
 
-		if (n == -NLE_MSG_TRUNC) {
+		if (n == -NME_NL_MSG_TRUNC) {
 			int buf_size;
 
 			/* the message receive buffer was too small. We lost one message, which
@@ -7401,11 +7853,11 @@ continue_reading:
 		nlmsg_set_proto (msg, NETLINK_ROUTE);
 		nlmsg_set_src (msg, &nla);
 
-		if (!creds || creds->pid) {
-			if (creds)
-				_LOGT ("netlink: recvmsg: received non-kernel message (pid %d)", creds->pid);
-			else
+		if (!creds_has || creds.pid) {
+			if (!creds_has)
 				_LOGT ("netlink: recvmsg: received message without credentials");
+			else
+				_LOGT ("netlink: recvmsg: received non-kernel message (pid %d)", creds.pid);
 			err = 0;
 			goto stop;
 		}
@@ -7413,8 +7865,7 @@ continue_reading:
 		_LOGt ("netlink: recvmsg: new message %s",
 		       nl_nlmsghdr_to_str (hdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 
-		if (creds)
-			nlmsg_set_creds (msg, creds);
+		nlmsg_set_creds (msg, &creds);
 
 		if (hdr->nlmsg_flags & NLM_F_MULTI)
 			multipart = TRUE;
@@ -7451,7 +7902,7 @@ continue_reading:
 			/* 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) */
-			err = -NLE_MSG_OVERFLOW;
+			err = -NME_NL_MSG_OVERFLOW;
 			abort_parsing = TRUE;
 		} else if (hdr->nlmsg_type == NLMSG_ERROR) {
 			/* Message carries a nlmsgerr */
@@ -7462,23 +7913,25 @@ continue_reading:
 				 * is to stop parsing. The user may overrule
 				 * this action by returning NL_SKIP or
 				 * NL_PROCEED (dangerous) */
-				err = -NLE_MSG_TRUNC;
+				err = -NME_NL_MSG_TRUNC;
 				abort_parsing = TRUE;
 			} else if (e->error) {
-				int errsv = e->error > 0 ? e->error : -e->error;
+				int errsv = nm_errno_native (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] = {
+					static const struct nla_policy policy[] = {
 						[NLMSGERR_ATTR_MSG]     = { .type = NLA_STRING },
 						[NLMSGERR_ATTR_OFFS]    = { .type = NLA_U32 },
 					};
-					struct nlattr *tb[NLMSGERR_ATTR_MAX + 1];
+					struct nlattr *tb[G_N_ELEMENTS (policy)];
 					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 (nla_parse_arr (tb,
+					                   tlvs,
+					                   hdr->nlmsg_len - sizeof (*e) - e->msg.nlmsg_len,
+					                   policy) >= 0) {
 						if (tb[NLMSGERR_ATTR_MSG])
 							extack_msg = nla_get_string (tb[NLMSGERR_ATTR_MSG]);
 					}
@@ -7486,11 +7939,11 @@ continue_reading:
 
 				/* Error message reported back from kernel. */
 				_LOGD ("netlink: recvmsg: error message from kernel: %s (%d)%s%s%s for request %d",
-				       strerror (errsv),
+				       nm_strerror_native (errsv),
 				       errsv,
 				       NM_PRINT_FMT_QUOTED (extack_msg, " \"", extack_msg, "\"", ""),
 				       nlmsg_hdr (msg)->nlmsg_seq);
-				seq_result = -errsv;
+				seq_result = -NM_ERRNO_NATIVE (errsv);
 			} else
 				seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
 		} else
@@ -7539,7 +7992,7 @@ stop:
 	}
 
 	if (interrupted)
-		return -NLE_DUMP_INTR;
+		return -NME_NL_DUMP_INTR;
 	return err;
 }
 
@@ -7576,16 +8029,16 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 				switch (nle) {
 				case -EAGAIN:
 					goto after_read;
-				case -NLE_DUMP_INTR:
-					_LOGD ("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
+				case -NME_NL_DUMP_INTR:
+					_LOGD ("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", nm_strerror (nle), nle);
 					break;
-				case -NLE_MSG_TRUNC:
+				case -NME_NL_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 -NME_NL_MSG_TRUNC: _reason = "message truncated";       break;
 					            case -ENOBUFS:       _reason = "too many netlink events"; break;
 					            }
 					            _reason;
@@ -7605,7 +8058,7 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 					                         NULL);
 					break;
 				default:
-					_LOGE ("netlink: read: failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
+					_LOGE ("netlink: read: failed to retrieve incoming events: %s (%d)", nm_strerror (nle), nle);
 					break;
 				}
 			}
@@ -7649,7 +8102,7 @@ after_read:
 			int errsv = errno;
 
 			if (errsv != EINTR) {
-				_LOGE ("netlink: read: poll failed with %s", strerror (errsv));
+				_LOGE ("netlink: read: poll failed with %s", nm_strerror_native (errsv));
 				delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL);
 				return any;
 			}
@@ -7833,7 +8286,7 @@ constructed (GObject *_object)
 	nle = nl_connect (priv->genl, NETLINK_GENERIC);
 	if (nle) {
 		_LOGE ("unable to connect the generic netlink socket \"%s\" (%d)",
-		       nl_geterror (nle), -nle);
+		       nm_strerror (nle), -nle);
 		nl_socket_free (priv->genl);
 		priv->genl = NULL;
 	}
@@ -7859,7 +8312,7 @@ constructed (GObject *_object)
 		_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. */
+	 * If we later encounter NME_NL_MSG_TRUNC, we will adjust the buffer size. */
 	nl_socket_disable_msg_peek (priv->nlh);
 	nle = nl_socket_set_msg_buf_size (priv->nlh, 32 * 1024);
 	g_assert (!nle);
@@ -8042,6 +8495,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 
 	platform_class->vlan_add = vlan_add;
 	platform_class->link_vlan_change = link_vlan_change;
+	platform_class->link_wireguard_change = link_wireguard_change;
 	platform_class->link_vxlan_add = link_vxlan_add;
 
 	platform_class->infiniband_partition_add = infiniband_partition_add;
@@ -8068,6 +8522,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->wpan_set_pan_id = wpan_set_pan_id;
 	platform_class->wpan_get_short_addr = wpan_get_short_addr;
 	platform_class->wpan_set_short_addr = wpan_set_short_addr;
+	platform_class->wpan_set_channel = wpan_set_channel;
 
 	platform_class->link_gre_add = link_gre_add;
 	platform_class->link_ip6tnl_add = link_ip6tnl_add;
diff --git a/src/platform/nm-netlink.c b/src/platform/nm-netlink.c
index 3e2ad911..71506a2c 100644
--- a/src/platform/nm-netlink.c
+++ b/src/platform/nm-netlink.c
@@ -42,16 +42,14 @@
 #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;
+	bool                    nm_creds_has:1;
 };
 
 struct nl_sock {
@@ -67,38 +65,6 @@ struct nl_sock {
 
 /*****************************************************************************/
 
-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 nlerr)
-{
-	const char *s;
-
-	nlerr = nl_errno (nlerr);
-
-	if (nlerr >= _NLE_BASE) {
-		s = _geterror (nlerr);
-		if (s)
-			return s;
-	}
-	return g_strerror (nlerr);
-}
-
-/*****************************************************************************/
-
 NM_UTILS_ENUM2STR_DEFINE (nl_nlmsgtype2str, int,
 	NM_UTILS_ENUM2STR (NLMSG_NOOP,    "NOOP"),
 	NM_UTILS_ENUM2STR (NLMSG_ERROR,   "ERROR"),
@@ -136,21 +102,30 @@ nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize 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;
+	case RTM_GETLINK:    s = "RTM_GETLINK";     break;
+	case RTM_NEWLINK:    s = "RTM_NEWLINK";     break;
+	case RTM_DELLINK:    s = "RTM_DELLINK";     break;
+	case RTM_SETLINK:    s = "RTM_SETLINK";     break;
+	case RTM_GETADDR:    s = "RTM_GETADDR";     break;
+	case RTM_NEWADDR:    s = "RTM_NEWADDR";     break;
+	case RTM_DELADDR:    s = "RTM_DELADDR";     break;
+	case RTM_GETROUTE:   s = "RTM_GETROUTE";    break;
+	case RTM_NEWROUTE:   s = "RTM_NEWROUTE";    break;
+	case RTM_DELROUTE:   s = "RTM_DELROUTE";    break;
+	case RTM_GETRULE:    s = "RTM_GETRULE";     break;
+	case RTM_NEWRULE:    s = "RTM_NEWRULE";     break;
+	case RTM_DELRULE:    s = "RTM_DELRULE";     break;
+	case RTM_GETQDISC:   s = "RTM_GETQDISC";    break;
+	case RTM_NEWQDISC:   s = "RTM_NEWQDISC";    break;
+	case RTM_DELQDISC:   s = "RTM_DELQDISC";    break;
+	case RTM_GETTFILTER: s = "RTM_GETTFILTER";  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)
@@ -239,6 +214,8 @@ nlmsg_reserve (struct nl_msg *n, size_t len, int pad)
 	size_t nlmsg_len = n->nm_nlh->nlmsg_len;
 	size_t tlen;
 
+	nm_assert (pad >= 0);
+
 	if (len > n->nm_size)
 		return NULL;
 
@@ -285,20 +262,6 @@ nla_reserve (struct nl_msg *msg, int attrtype, int attrlen)
 
 /*****************************************************************************/
 
-static int
-get_default_page_size (void)
-{
-	static int val = 0;
-	int v;
-
-	if (G_UNLIKELY (val == 0)) {
-		v = getpagesize ();
-		g_assert (v > 0);
-		val = v;
-	}
-	return val;
-}
-
 struct nl_msg *
 nlmsg_alloc_size (size_t len)
 {
@@ -307,11 +270,12 @@ nlmsg_alloc_size (size_t len)
 	if (len < sizeof (struct nlmsghdr))
 		len = sizeof (struct nlmsghdr);
 
-	nm = g_slice_new0 (struct nl_msg);
-
-	nm->nm_protocol = -1;
-	nm->nm_size = len;
-	nm->nm_nlh = g_malloc0 (len);
+	nm = g_slice_new (struct nl_msg);
+	*nm = (struct nl_msg) {
+		.nm_protocol = -1,
+		.nm_size = len,
+		.nm_nlh = g_malloc0 (len),
+	};
 	nm->nm_nlh->nlmsg_len = nlmsg_total_size (0);
 	return nm;
 }
@@ -328,7 +292,7 @@ nlmsg_alloc_size (size_t len)
 struct nl_msg *
 nlmsg_alloc (void)
 {
-	return nlmsg_alloc_size (get_default_page_size ());
+	return nlmsg_alloc_size (nm_utils_getpagesize ());
 }
 
 struct nl_msg *
@@ -366,10 +330,18 @@ void nlmsg_free (struct nl_msg *msg)
 /*****************************************************************************/
 
 int
-nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad)
+nlmsg_append (struct nl_msg *n,
+              const void *data,
+              size_t len,
+              int pad)
 {
 	void *tmp;
 
+	nm_assert (n);
+	nm_assert (data);
+	nm_assert (len > 0);
+	nm_assert (pad >= 0);
+
 	tmp = nlmsg_reserve (n, len, pad);
 	if (tmp == NULL)
 		return -ENOMEM;
@@ -385,7 +357,7 @@ 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 -NME_NL_MSG_TOOSHORT;
 
 	return nla_parse (tb, maxtype, nlmsg_attrdata (nlh, hdrlen),
 	                  nlmsg_attrlen (nlh, hdrlen), policy);
@@ -413,48 +385,77 @@ nlmsg_put (struct nl_msg *n, uint32_t pid, uint32_t seq,
 	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--;
+nla_strlcpy (char *dst,
+             const struct nlattr *nla,
+             size_t dstsize)
+{
+	const char *src;
+	size_t srclen;
+	size_t len;
+
+	/* - Always writes @dstsize bytes to @dst
+	 * - Copies the first non-NUL characters to @dst.
+	 *   Any characters after the first NUL bytes in @nla are ignored.
+	 * - If the string @nla is longer than @dstsize, the string
+	 *   gets truncated. @dst will always be NUL terminated. */
+
+	if (G_UNLIKELY (dstsize <= 1)) {
+		if (dstsize == 1)
+			dst[0] = '\0';
+		if (   nla
+		    && (srclen = nla_len (nla)) > 0)
+			return strnlen (nla_data (nla), srclen);
+		return 0;
+	}
 
-	if (dstsize > 0) {
-		size_t len = (srclen >= dstsize) ? dstsize - 1 : srclen;
+	nm_assert (dst);
 
-		memset (dst, 0, dstsize);
-		memcpy (dst, src, len);
+	if (nla) {
+		srclen = nla_len (nla);
+		if (srclen > 0) {
+			src = nla_data (nla);
+			srclen = strnlen (src, srclen);
+			if (srclen > 0) {
+				len = NM_MIN (dstsize - 1, srclen);
+				memcpy (dst, src, len);
+				memset (&dst[len], 0, dstsize - len);
+				return srclen;
+			}
+		}
 	}
 
-	return srclen;
+	memset (dst, 0, dstsize);
+	return 0;
 }
 
-int
-nla_memcpy (void *dest, const struct nlattr *src, int count)
+size_t
+nla_memcpy (void *dst, const struct nlattr *nla, size_t dstsize)
 {
-	int minlen;
+	size_t len;
+	int srclen;
 
-	if (!src)
+	if (!nla)
 		return 0;
 
-	minlen = NM_MIN (count, (int) nla_len (src));
-	memcpy (dest, nla_data (src), minlen);
+	srclen = nla_len (nla);
 
-	return minlen;
+	if (srclen <= 0) {
+		nm_assert (srclen == 0);
+		return 0;
+	}
+
+	len = NM_MIN ((size_t) srclen, dstsize);
+	if (len > 0) {
+		/* there is a crucial difference between nla_strlcpy() and nla_memcpy().
+		 * The former always write @dstsize bytes (akin to strncpy()), here, we only
+		 * write the bytes that we actually have (leaving the remainder undefined). */
+		memcpy (dst,
+		        nla_data (nla),
+		        len);
+	}
+
+	return srclen;
 }
 
 int
@@ -465,7 +466,7 @@ nla_put (struct nl_msg *msg, int attrtype, int datalen, const void *data)
 	nla = nla_reserve (msg, attrtype, datalen);
 	if (!nla) {
 		if (datalen < 0)
-			g_return_val_if_reached (-NLE_BUG);
+			g_return_val_if_reached (-NME_BUG);
 
 		return -ENOMEM;
 	}
@@ -531,7 +532,7 @@ _nest_end (struct nl_msg *msg, struct nlattr *start, int keep_empty)
 		nla_nest_cancel (msg, start);
 
 		/* Return error only if nlattr size was exceeded */
-		return (len == NLA_HDRLEN) ? 0 : -NLE_ATTRSIZE;
+		return (len == NLA_HDRLEN) ? 0 : -NME_NL_ATTRSIZE;
 	}
 
 	start->nla_len = len;
@@ -539,13 +540,13 @@ _nest_end (struct nl_msg *msg, struct nlattr *start, int keep_empty)
 	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.
+		 * Data inside attribute does not end at a alignment boundary.
 		 * 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);
+			g_return_val_if_reached (-NME_BUG);
 	}
 
 	return 0;
@@ -580,7 +581,7 @@ validate_nla (const struct nlattr *nla, int maxtype,
 	pt = &policy[type];
 
 	if (pt->type > NLA_TYPE_MAX)
-		g_return_val_if_reached (-NLE_BUG);
+		g_return_val_if_reached (-NME_BUG);
 
 	if (pt->minlen)
 		minlen = pt->minlen;
@@ -588,15 +589,19 @@ validate_nla (const struct nlattr *nla, int maxtype,
 		minlen = nla_attr_minlen[pt->type];
 
 	if (nla_len (nla) < minlen)
-		return -NLE_UNSPEC;
+		return -NME_UNSPEC;
 
 	if (pt->maxlen && nla_len (nla) > pt->maxlen)
-		return -NLE_UNSPEC;
+		return -NME_UNSPEC;
 
 	if (pt->type == NLA_STRING) {
-		const char *data = nla_data (nla);
+		const char *data;
+
+		nm_assert (minlen > 0);
+
+		data = nla_data (nla);
 		if (data[nla_len (nla) - 1] != '\0')
-			return -NLE_UNSPEC;
+			return -NME_UNSPEC;
 	}
 
 	return 0;
@@ -607,7 +612,7 @@ nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
            const struct nla_policy *policy)
 {
 	struct nlattr *nla;
-	int rem, nlerr;
+	int rem, nmerr;
 
 	memset (tb, 0, sizeof (struct nlattr *) * (maxtype + 1));
 
@@ -618,17 +623,15 @@ nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
 			continue;
 
 		if (policy) {
-			nlerr = validate_nla (nla, maxtype, policy);
-			if (nlerr < 0)
-				goto errout;
+			nmerr = validate_nla (nla, maxtype, policy);
+			if (nmerr < 0)
+				return nmerr;
 		}
 
 		tb[type] = nla;
 	}
 
-	nlerr = 0;
-errout:
-	return nlerr;
+	return 0;
 }
 
 /*****************************************************************************/
@@ -654,7 +657,7 @@ nlmsg_set_src (struct nl_msg *msg, struct sockaddr_nl *addr)
 struct ucred *
 nlmsg_get_creds (struct nl_msg *msg)
 {
-	if (msg->nm_flags & NL_MSG_CRED_PRESENT)
+	if (msg->nm_creds_has)
 		return &msg->nm_creds;
 	return NULL;
 }
@@ -662,8 +665,11 @@ nlmsg_get_creds (struct nl_msg *msg)
 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;
+	if (creds) {
+		memcpy (&msg->nm_creds, creds, sizeof (*creds));
+		msg->nm_creds_has = TRUE;
+	} else
+		msg->nm_creds_has = FALSE;
 }
 
 /*****************************************************************************/
@@ -754,7 +760,7 @@ genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
 	struct genlmsghdr *ghdr;
 
 	if (!genlmsg_valid_hdr (nlh, hdrlen))
-		return -NLE_MSG_TOOSHORT;
+		return -NME_NL_MSG_TOOSHORT;
 
 	ghdr = nlmsg_data (nlh);
 	return nla_parse (tb, maxtype, genlmsg_attrdata (ghdr, hdrlen),
@@ -764,21 +770,21 @@ genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
 static int
 _genl_parse_getfamily (struct nl_msg *msg, void *arg)
 {
-	static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
+	static const struct nla_policy ctrl_policy[] = {
 		[CTRL_ATTR_FAMILY_ID]    = { .type = NLA_U16 },
 		[CTRL_ATTR_FAMILY_NAME]  = { .type = NLA_STRING,
-		                            .maxlen = GENL_NAMSIZ },
+		                             .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 nlattr *tb[G_N_ELEMENTS (ctrl_policy)];
 	struct nlmsghdr *nlh = nlmsg_hdr (msg);
 	gint32 *response_data = arg;
 
-	if (genlmsg_parse (nlh, 0, tb, CTRL_ATTR_MAX, ctrl_policy))
+	if (genlmsg_parse_arr (nlh, 0, tb, ctrl_policy) < 0)
 		return NL_SKIP;
 
 	if (tb[CTRL_ATTR_FAMILY_ID])
@@ -791,7 +797,7 @@ int
 genl_ctrl_resolve (struct nl_sock *sk, const char *name)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
-	int nlerr;
+	int nmerr;
 	gint32 response_data = -1;
 	const struct nl_cb cb = {
 		.valid_cb = _genl_parse_getfamily,
@@ -804,25 +810,25 @@ genl_ctrl_resolve (struct nl_sock *sk, const char *name)
 	                  0, 0, CTRL_CMD_GETFAMILY, 1))
 		return -ENOMEM;
 
-	nlerr = nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name);
-	if (nlerr < 0)
-		return nlerr;
+	nmerr = nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name);
+	if (nmerr < 0)
+		return nmerr;
 
-	nlerr = nl_send_auto (sk, msg);
-	if (nlerr < 0)
-		return nlerr;
+	nmerr = nl_send_auto (sk, msg);
+	if (nmerr < 0)
+		return nmerr;
 
-	nlerr = nl_recvmsgs (sk, &cb);
-	if (nlerr < 0)
-		return nlerr;
+	nmerr = nl_recvmsgs (sk, &cb);
+	if (nmerr < 0)
+		return nmerr;
 
 	/* If search was successful, request may be ACKed after data */
-	nlerr = nl_wait_for_ack (sk, NULL);
-	if (nlerr < 0)
-		return nlerr;
+	nmerr = nl_wait_for_ack (sk, NULL);
+	if (nmerr < 0)
+		return nmerr;
 
 	if (response_data < 0)
-		return -NLE_UNSPEC;
+		return -NME_UNSPEC;
 
 	return response_data;
 }
@@ -879,12 +885,12 @@ nl_socket_set_passcred (struct nl_sock *sk, int state)
 	int err;
 
 	if (sk->s_fd == -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_PASSCRED,
 	                  &state, sizeof (state));
 	if (err < 0)
-		return -nl_syserr2nlerr (errno);
+		return -nm_errno_from_native (errno);
 
 	if (state)
 		sk->s_flags |= NL_SOCK_PASSCRED;
@@ -912,10 +918,10 @@ int
 nl_socket_set_nonblocking (const struct nl_sock *sk)
 {
 	if (sk->s_fd == -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	if (fcntl (sk->s_fd, F_SETFL, O_NONBLOCK) < 0)
-		return -nl_syserr2nlerr (errno);
+		return -nm_errno_from_native (errno);
 
 	return 0;
 }
@@ -932,18 +938,18 @@ nl_socket_set_buffer_size (struct nl_sock *sk, int rxbuf, int txbuf)
 		txbuf = 32768;
 
 	if (sk->s_fd == -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_SNDBUF,
 	                  &txbuf, sizeof (txbuf));
 	if (err < 0) {
-		return -nl_syserr2nlerr (errno);
+		return -nm_errno_from_native (errno);
 	}
 
 	err = setsockopt (sk->s_fd, SOL_SOCKET, SO_RCVBUF,
 	                  &rxbuf, sizeof (rxbuf));
 	if (err < 0) {
-		return -nl_syserr2nlerr (errno);
+		return -nm_errno_from_native (errno);
 	}
 
 	return 0;
@@ -956,14 +962,14 @@ nl_socket_add_memberships (struct nl_sock *sk, int group, ...)
 	va_list ap;
 
 	if (sk->s_fd == -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	va_start (ap, group);
 
 	while (group != 0) {
 		if (group < 0) {
 			va_end (ap);
-			g_return_val_if_reached (-NLE_BUG);
+			g_return_val_if_reached (-NME_BUG);
 		}
 
 		err = setsockopt (sk->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
@@ -972,7 +978,7 @@ nl_socket_add_memberships (struct nl_sock *sk, int group, ...)
 			int errsv = errno;
 
 			va_end (ap);
-			return -nl_syserr2nlerr (errsv);
+			return -nm_errno_from_native (errsv);
 		}
 
 		group = va_arg (ap, int);
@@ -989,12 +995,12 @@ nl_socket_set_ext_ack (struct nl_sock *sk, gboolean enable)
 	int err, val;
 
 	if (sk->s_fd == -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_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 -nm_errno_from_native (errno);
 
 	return 0;
 }
@@ -1008,21 +1014,21 @@ void nl_socket_disable_msg_peek (struct nl_sock *sk)
 int
 nl_connect (struct nl_sock *sk, int protocol)
 {
-	int err, nlerr;
+	int err, nmerr;
 	socklen_t addrlen;
 	struct sockaddr_nl local = { 0 };
 
 	if (sk->s_fd != -1)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	sk->s_fd = socket (AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
 	if (sk->s_fd < 0) {
-		nlerr = -nl_syserr2nlerr (errno);
+		nmerr = -nm_errno_from_native (errno);
 		goto errout;
 	}
 
-	nlerr = nl_socket_set_buffer_size (sk, 0, 0);
-	if (nlerr < 0)
+	nmerr = nl_socket_set_buffer_size (sk, 0, 0);
+	if (nmerr < 0)
 		goto errout;
 
 	nm_assert (sk->s_local.nl_pid == 0);
@@ -1030,7 +1036,7 @@ nl_connect (struct nl_sock *sk, int protocol)
 	err = bind (sk->s_fd, (struct sockaddr*) &sk->s_local,
 	            sizeof (sk->s_local));
 	if (err != 0) {
-		nlerr = -nl_syserr2nlerr (errno);
+		nmerr = -nm_errno_from_native (errno);
 		goto errout;
 	}
 
@@ -1038,17 +1044,17 @@ nl_connect (struct nl_sock *sk, int protocol)
 	err = getsockname (sk->s_fd, (struct sockaddr *) &local,
 	                   &addrlen);
 	if (err < 0) {
-		nlerr = -nl_syserr2nlerr (errno);
+		nmerr = -nm_errno_from_native (errno);
 		goto errout;
 	}
 
 	if (addrlen != sizeof (local)) {
-		nlerr = -NLE_UNSPEC;
+		nmerr = -NME_UNSPEC;
 		goto errout;
 	}
 
 	if (local.nl_family != AF_NETLINK) {
-		nlerr = -NLE_UNSPEC;
+		nmerr = -NME_UNSPEC;
 		goto errout;
 	}
 
@@ -1062,7 +1068,7 @@ errout:
 		close (sk->s_fd);
 		sk->s_fd = -1;
 	}
-	return nlerr;
+	return nmerr;
 }
 
 /*****************************************************************************/
@@ -1098,22 +1104,22 @@ nl_wait_for_ack (struct nl_sock *sk,
 do { \
 	const struct nl_cb *_cb = (cb); \
 	\
-	if (_cb->type##_cb) { \
+	if (_cb && _cb->type##_cb) { \
 		/* the returned value here must be either a negative
 		 * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */ \
-		nlerr = _cb->type##_cb ((msg), _cb->type##_arg); \
-		switch (nlerr) { \
+		nmerr = _cb->type##_cb ((msg), _cb->type##_arg); \
+		switch (nmerr) { \
 		case NL_OK: \
-			nlerr = 0; \
+			nm_assert (nmerr == 0); \
 			break; \
 		case NL_SKIP: \
 			goto skip; \
 		case NL_STOP: \
 			goto stop; \
 		default: \
-			if (nlerr >= 0) { \
+			if (nmerr >= 0) { \
 				nm_assert_not_reached (); \
-				nlerr = -NLE_BUG; \
+				nmerr = -NME_BUG; \
 			} \
 			goto out; \
 		} \
@@ -1123,14 +1129,15 @@ do { \
 int
 nl_recvmsgs (struct nl_sock *sk, const struct nl_cb *cb)
 {
-	int n, nlerr = 0, multipart = 0, interrupted = 0, nrecv = 0;
+	int n, nmerr = 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;
+	struct ucred creds;
+	gboolean creds_has;
 
 continue_reading:
-	n = nl_recv (sk, &nla, &buf, &creds);
+	n = nl_recv (sk, &nla, &buf, &creds, &creds_has);
 	if (n <= 0)
 		return n;
 
@@ -1142,15 +1149,14 @@ continue_reading:
 
 		nlmsg_set_proto (msg, sk->s_proto);
 		nlmsg_set_src (msg, &nla);
-		if (creds)
-			nlmsg_set_creds (msg, creds);
+		nlmsg_set_creds (msg, creds_has ? &creds : NULL);
 
 		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) {
-				nlerr = -NLE_SEQ_MISMATCH;
+				nmerr = -NME_NL_SEQ_MISMATCH;
 				goto out;
 			}
 		}
@@ -1196,7 +1202,7 @@ continue_reading:
 		 * quit parsing. The user may overrule this action by retuning
 		 * NL_SKIP or NL_PROCEED (dangerous) */
 		else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
-			nlerr = -NLE_MSG_OVERFLOW;
+			nmerr = -NME_NL_MSG_OVERFLOW;
 			goto out;
 		}
 
@@ -1209,27 +1215,27 @@ continue_reading:
 				 * is to stop parsing. The user may overrule
 				 * this action by returning NL_SKIP or
 				 * NL_PROCEED (dangerous) */
-				nlerr = -NLE_MSG_TRUNC;
+				nmerr = -NME_NL_MSG_TRUNC;
 				goto out;
 			}
 			if (e->error) {
 				/* Error message reported back from kernel. */
-				if (cb->err_cb) {
+				if (cb && cb->err_cb) {
 					/* the returned value here must be either a negative
 					 * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */
-					nlerr = cb->err_cb (&nla, e,
+					nmerr = cb->err_cb (&nla, e,
 					                    cb->err_arg);
-					if (nlerr < 0)
+					if (nmerr < 0)
 						goto out;
-					else if (nlerr == NL_SKIP)
+					else if (nmerr == NL_SKIP)
 						goto skip;
-					else if (nlerr == NL_STOP) {
-						nlerr = -nl_syserr2nlerr (e->error);
+					else if (nmerr == NL_STOP) {
+						nmerr = -nm_errno_from_native (e->error);
 						goto out;
 					}
-					nm_assert (nlerr == NL_OK);
+					nm_assert (nmerr == NL_OK);
 				} else {
-					nlerr = -nl_syserr2nlerr (e->error);
+					nmerr = -nm_errno_from_native (e->error);
 					goto out;
 				}
 			} else
@@ -1241,27 +1247,27 @@ continue_reading:
 			NL_CB_CALL (cb, valid, msg);
 		}
 skip:
-		nlerr = 0;
+		nmerr = 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);
 
+		nmerr = 0;
 		goto continue_reading;
 	}
 
 stop:
-	nlerr = 0;
+	nmerr = 0;
 
 out:
 	if (interrupted)
-		nlerr = -NLE_DUMP_INTR;
+		nmerr = -NME_NL_DUMP_INTR;
 
-	nm_assert (nlerr <= 0);
-	return nlerr ?: nrecv;
+	nm_assert (nmerr <= 0);
+	return nmerr ?: nrecv;
 }
 
 int
@@ -1270,13 +1276,13 @@ nl_sendmsg (struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr)
 	int ret;
 
 	if (sk->s_fd < 0)
-		return -NLE_BAD_SOCK;
+		return -NME_NL_BAD_SOCK;
 
 	nlmsg_set_src (msg, &sk->s_local);
 
 	ret = sendmsg (sk->s_fd, hdr, 0);
 	if (ret < 0)
-		return -nl_syserr2nlerr (errno);
+		return -nm_errno_from_native (errno);
 
 	return ret;
 }
@@ -1359,12 +1365,14 @@ int nl_send_auto (struct nl_sock *sk, struct nl_msg *msg)
 }
 
 int
-nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
-         unsigned char **buf, struct ucred **creds)
+nl_recv (struct nl_sock *sk,
+         struct sockaddr_nl *nla,
+         unsigned char **buf,
+         struct ucred *out_creds,
+         gboolean *out_creds_has)
 {
 	ssize_t n;
 	int flags = 0;
-	static int page_size = 0;
 	struct iovec iov;
 	struct msghdr msg = {
 		.msg_name = (void *) nla,
@@ -1372,25 +1380,25 @@ nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
 		.msg_iov = &iov,
 		.msg_iovlen = 1,
 	};
-	gs_free struct ucred* tmpcreds = NULL;
+	struct ucred tmpcreds;
+	gboolean tmpcreds_has = FALSE;
 	int retval;
+	int errsv;
 
 	nm_assert (nla);
 	nm_assert (buf && !*buf);
-	nm_assert (!creds || !*creds);
+	nm_assert (!out_creds_has == !out_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_len =    sk->s_bufsize
+	              ?: (((size_t) nm_utils_getpagesize ()) * 4u);
 	iov.iov_base = g_malloc (iov.iov_len);
 
-	if (   creds
+	if (   out_creds
 	    && (sk->s_flags & NL_SOCK_PASSCRED)) {
 		msg.msg_controllen = CMSG_SPACE (sizeof (struct ucred));
 		msg.msg_control = g_malloc (msg.msg_controllen);
@@ -1404,16 +1412,16 @@ retry:
 	}
 
 	if (n < 0) {
-		if (errno == EINTR)
+		errsv = errno;
+		if (errsv == EINTR)
 			goto retry;
-
-		retval = -nl_syserr2nlerr (errno);
+		retval = -nm_errno_from_native (errsv);
 		goto abort;
 	}
 
 	if (msg.msg_flags & MSG_CTRUNC) {
 		if (msg.msg_controllen == 0) {
-			retval = -NLE_MSG_TRUNC;
+			retval = -NME_NL_MSG_TRUNC;
 			goto abort;
 		}
 
@@ -1426,7 +1434,7 @@ retry:
 	    || (msg.msg_flags & MSG_TRUNC)) {
 		/* respond with error to an incomplete message */
 		if (flags == 0) {
-			retval = -NLE_MSG_TRUNC;
+			retval = -NME_NL_MSG_TRUNC;
 			goto abort;
 		}
 
@@ -1446,11 +1454,11 @@ retry:
 	}
 
 	if (msg.msg_namelen != sizeof (struct sockaddr_nl)) {
-		retval =  -NLE_UNSPEC;
+		retval =  -NME_UNSPEC;
 		goto abort;
 	}
 
-	if (creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
+	if (out_creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
 		struct cmsghdr *cmsg;
 
 		for (cmsg = CMSG_FIRSTHDR (&msg); cmsg; cmsg = CMSG_NXTHDR (&msg, cmsg)) {
@@ -1458,7 +1466,8 @@ retry:
 				continue;
 			if (cmsg->cmsg_type != SCM_CREDENTIALS)
 				continue;
-			tmpcreds = nm_memdup (CMSG_DATA (cmsg), sizeof (*tmpcreds));
+			memcpy (&tmpcreds, CMSG_DATA (cmsg), sizeof (tmpcreds));
+			tmpcreds_has = TRUE;
 			break;
 		}
 	}
@@ -1474,6 +1483,8 @@ abort:
 	}
 
 	*buf = iov.iov_base;
-	NM_SET_OUT (creds, g_steal_pointer (&tmpcreds));
+	if (out_creds && tmpcreds_has)
+		*out_creds = tmpcreds;
+	NM_SET_OUT (out_creds_has, tmpcreds_has);
 	return retval;
 }
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
index d5df7ab9..094a3c6f 100644
--- a/src/platform/nm-netlink.h
+++ b/src/platform/nm-netlink.h
@@ -25,21 +25,9 @@
 #include <linux/rtnetlink.h>
 #include <linux/genetlink.h>
 
+#include "nm-utils/unaligned.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
@@ -51,50 +39,6 @@
 #define NLM_F_ACK_TLVS                  0x200
 #endif
 
-static inline int
-nl_errno (int nlerr)
-{
-	/* Normalizes an netlink error to be positive. Various API returns negative
-	 * error codes, and this function converts the negative value to its
-	 * positive.
-	 *
-	 * It's very similar to nm_errno(), but not exactly. The difference is that
-	 * nm_errno() is for plain errno, while nl_errno() is for netlink error numbers.
-	 * Yes, netlink error number are ~almost~ the same as errno, except that a particular
-	 * range (_NLE_BASE, _NLE_BASE_END) is reserved. The difference between the two
-	 * functions is only how G_MININT is mapped.
-	 *
-	 * See also nl_syserr2nlerr() below. */
-	return nlerr >= 0
-	       ? nlerr
-	       : ((nlerr == G_MININT) ? NLE_BUG : -nlerr);
-}
-
-static inline int
-nl_syserr2nlerr (int errsv)
-{
-	/* this maps a native errno to a (always non-negative) netlink error number.
-	 *
-	 * Note that netlink error numbers are embedded into the range of regular
-	 * errno. The only difference is, that netlink error numbers reserve a
-	 * range (_NLE_BASE, _NLE_BASE_END) for their own purpose.
-	 *
-	 * That means, converting an errno to netlink error number means in
-	 * most cases just returning itself (negative values are normalized
-	 * to be positive). Only values G_MININT and [_NLE_BASE, _NLE_BASE_END]
-	 * are coerced to the special value NLE_NATIVE_ERRNO, as they cannot
-	 * otherwise be represented in netlink error number domain. */
-	if (errsv == G_MININT)
-		return NLE_NATIVE_ERRNO;
-	if (errsv < 0)
-		errsv = -errsv;
-	return (errsv >= _NLE_BASE && errsv < _NLE_BASE_END)
-	       ? NLE_NATIVE_ERRNO
-	       : errsv;
-}
-
-const char *nl_geterror (int nlerr);
-
 /*****************************************************************************/
 
 /* Basic attribute data types */
@@ -145,8 +89,26 @@ struct nla_policy {
 
 /*****************************************************************************/
 
+/* static asserts that @tb and @policy are suitable arguments to nla_parse(). */
+#define _nl_static_assert_tb(tb, policy) \
+	G_STMT_START { \
+		\
+		G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (tb) > 0); \
+		\
+		/* we allow @policy to be either NULL or a C array. */ \
+		G_STATIC_ASSERT_EXPR (   sizeof (policy) == sizeof (NULL) \
+		                      || G_N_ELEMENTS (tb) == (sizeof (policy) / sizeof (struct nla_policy))); \
+		\
+		/* For above check to work, we don't support policy being an array with same size as
+		 * sizeof(NULL), otherwise, the compile time check breaks down. */ \
+		G_STATIC_ASSERT_EXPR (sizeof (NULL) != G_N_ELEMENTS (tb) * sizeof (struct nla_policy)); \
+		\
+	} G_STMT_END
+
+/*****************************************************************************/
+
 static inline int
-nla_attr_size(int payload)
+nla_attr_size (int payload)
 {
 	nm_assert (payload >= 0);
 
@@ -162,7 +124,7 @@ nla_total_size (int payload)
 static inline int
 nla_padlen (int payload)
 {
-	return nla_total_size(payload) - nla_attr_size(payload);
+	return nla_total_size (payload) - nla_attr_size (payload);
 }
 
 struct nlattr *nla_reserve (struct nl_msg *msg, int attrtype, int attrlen);
@@ -170,32 +132,56 @@ 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;
+	nm_assert (nla);
+	nm_assert (nla->nla_len >= NLA_HDRLEN);
+
+	return ((int) nla->nla_len) - NLA_HDRLEN;
 }
 
 static inline int
 nla_type (const struct nlattr *nla)
 {
+	nm_assert (nla_len (nla) >= 0);
+
 	return nla->nla_type & NLA_TYPE_MASK;
 }
 
 static inline void *
 nla_data (const struct nlattr *nla)
 {
-	nm_assert (nla);
-	return (char *) nla + NLA_HDRLEN;
+	nm_assert (nla_len (nla) >= 0);
+
+	return &(((char *) nla)[NLA_HDRLEN]);
 }
 
+#define nla_data_as(type, nla) \
+	({ \
+		const struct nlattr *_nla = (nla); \
+		\
+		nm_assert (nla_len (_nla) >= sizeof (type)); \
+		\
+		/* note that casting the pointer is undefined behavior in C, if
+		 * the data has wrong alignment. Netlink data is aligned to 4 bytes,
+		 * that means, if the alignment is larger than 4, this is invalid. */ \
+		G_STATIC_ASSERT_EXPR (_nm_alignof (type) <= NLA_ALIGNTO); \
+		\
+		(type *) nla_data (_nla); \
+	})
+
 static inline uint8_t
 nla_get_u8 (const struct nlattr *nla)
 {
-	return *(const uint8_t *) nla_data (nla);
+	nm_assert (nla_len (nla) >= sizeof (uint8_t));
+
+	return *((const uint8_t *) nla_data (nla));
 }
 
-static inline uint8_t
+static inline int8_t
 nla_get_s8 (const struct nlattr *nla)
 {
-	return *(const int8_t *) nla_data (nla);
+	nm_assert (nla_len (nla) >= sizeof (int8_t));
+
+	return *((const int8_t *) nla_data (nla));
 }
 
 static inline uint8_t
@@ -210,91 +196,150 @@ nla_get_u8_cond (/*const*/ struct nlattr *const*tb, int attr, uint8_t default_va
 static inline uint16_t
 nla_get_u16 (const struct nlattr *nla)
 {
-	return *(const uint16_t *) nla_data (nla);
+	nm_assert (nla_len (nla) >= sizeof (uint16_t));
+
+	return *((const uint16_t *) nla_data (nla));
 }
 
 static inline uint32_t
-nla_get_u32(const struct nlattr *nla)
+nla_get_u32 (const struct nlattr *nla)
 {
-	return *(const uint32_t *) nla_data (nla);
+	nm_assert (nla_len (nla) >= sizeof (uint32_t));
+
+	return *((const uint32_t *) nla_data (nla));
 }
 
 static inline int32_t
-nla_get_s32(const struct nlattr *nla)
+nla_get_s32 (const struct nlattr *nla)
 {
-	return *(const int32_t *) nla_data (nla);
+	nm_assert (nla_len (nla) >= sizeof (int32_t));
+
+	return *((const int32_t *) nla_data (nla));
 }
 
-uint64_t nla_get_u64 (const struct nlattr *nla);
+static inline uint64_t
+nla_get_u64 (const struct nlattr *nla)
+{
+	nm_assert (nla_len (nla) >= sizeof (uint64_t));
+
+	return unaligned_read_ne64 (nla_data (nla));
+}
+
+static inline uint64_t
+nla_get_be64 (const struct nlattr *nla)
+{
+	nm_assert (nla_len (nla) >= sizeof (uint64_t));
+
+	return unaligned_read_be64 (nla_data (nla));
+}
 
 static inline char *
 nla_get_string (const struct nlattr *nla)
 {
+	nm_assert (nla_len (nla) >= 0);
+
 	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);
+size_t nla_memcpy (void *dst, const struct nlattr *nla, size_t dstsize);
+
+#define nla_memcpy_checked_size(dst, nla, dstsize) \
+	G_STMT_START { \
+		void *const _dst = (dst); \
+		const struct nlattr *const _nla = (nla); \
+		const size_t _dstsize = (dstsize); \
+		size_t _srcsize; \
+		\
+		/* assert that, if @nla is given, that it has the exact expected
+		 * size. This implies that the caller previously verified the length
+		 * of the attribute (via minlen/maxlen at nla_parse()). */ \
+		\
+		if (_nla) { \
+			_srcsize = nla_memcpy (_dst, _nla, _dstsize); \
+			nm_assert (_srcsize == _dstsize); \
+		} \
+	} G_STMT_END
 
 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);
+	nm_assert (str);
+
+	return nla_put (msg, attrtype, strlen (str) + 1, str);
+}
+
+static inline int
+nla_put_uint8 (struct nl_msg *msg, int attrtype, uint8_t val)
+{
+	return nla_put (msg, attrtype, sizeof (val), &val);
+}
+
+static inline int
+nla_put_uint16 (struct nl_msg *msg, int attrtype, uint16_t val)
+{
+	return nla_put (msg, attrtype, sizeof (val), &val);
+}
+
+static inline int
+nla_put_uint32 (struct nl_msg *msg, int attrtype, uint32_t val)
+{
+	return nla_put (msg, attrtype, sizeof (val), &val);
 }
 
 #define NLA_PUT(msg, attrtype, attrlen, data) \
-	do { \
-		if (nla_put(msg, attrtype, attrlen, data) < 0) \
+	G_STMT_START { \
+		if (nla_put (msg, attrtype, attrlen, data) < 0) \
 			goto nla_put_failure; \
-	} while(0)
+	} G_STMT_END
 
 #define NLA_PUT_TYPE(msg, type, attrtype, value) \
-	do { \
+	G_STMT_START { \
 		type __nla_tmp = value; \
-		NLA_PUT(msg, attrtype, sizeof(type), &__nla_tmp); \
-	} while(0)
+		NLA_PUT (msg, attrtype, sizeof (type), &__nla_tmp); \
+	} G_STMT_END
 
 #define NLA_PUT_U8(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, uint8_t, attrtype, value)
+	NLA_PUT_TYPE (msg, uint8_t, attrtype, value)
 
 #define NLA_PUT_S8(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, int8_t, attrtype, value)
+	NLA_PUT_TYPE (msg, int8_t, attrtype, value)
 
 #define NLA_PUT_U16(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, uint16_t, 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)
+	NLA_PUT_TYPE (msg, uint32_t, attrtype, value)
 
 #define NLA_PUT_S32(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, int32_t, attrtype, value)
+	NLA_PUT_TYPE (msg, int32_t, attrtype, value)
 
 #define NLA_PUT_U64(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, uint64_t, attrtype, value)
+	NLA_PUT_TYPE (msg, uint64_t, attrtype, value)
 
 #define NLA_PUT_STRING(msg, attrtype, value) \
-	NLA_PUT(msg, attrtype, (int) strlen(value) + 1, value)
+	NLA_PUT (msg, attrtype, (int) strlen (value) + 1, value)
 
 #define NLA_PUT_FLAG(msg, attrtype) \
-	NLA_PUT(msg, attrtype, 0, NULL)
+	NLA_PUT (msg, attrtype, 0, NULL)
 
 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 >= (int) sizeof(*nla) &&
-	       nla->nla_len >= sizeof(*nla) &&
+	return remaining >= (int) sizeof (*nla) &&
+	       nla->nla_len >= sizeof (*nla) &&
 	       nla->nla_len <= remaining;
 }
 
 static inline struct nlattr *
-nla_next(const struct nlattr *nla, int *remaining)
+nla_next (const struct nlattr *nla, int *remaining)
 {
-	int totlen = NLA_ALIGN(nla->nla_len);
+	int totlen = NLA_ALIGN (nla->nla_len);
 
 	*remaining -= totlen;
 	return (struct nlattr *) ((char *) nla + totlen);
@@ -302,28 +347,47 @@ nla_next(const struct nlattr *nla, int *remaining)
 
 #define nla_for_each_attr(pos, head, len, rem) \
 	for (pos = head, rem = len; \
-	     nla_ok(pos, rem); \
-	     pos = nla_next(pos, &(rem)))
+	     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)))
+	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,
+int nla_parse (struct nlattr *tb[],
+               int maxtype,
+               struct nlattr *head,
+               int len,
                const struct nla_policy *policy);
 
+#define nla_parse_arr(tb, head, len, policy) \
+	({ \
+		_nl_static_assert_tb ((tb), (policy)); \
+		\
+		nla_parse ((tb), G_N_ELEMENTS (tb) - 1, (head), (len), (policy)); \
+	})
+
 static inline int
-nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla,
+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);
+	return nla_parse (tb, maxtype, nla_data (nla), nla_len (nla), policy);
 }
 
+#define nla_parse_nested_arr(tb, nla, policy) \
+	({ \
+		_nl_static_assert_tb ((tb), (policy)); \
+		\
+		nla_parse_nested ((tb), G_N_ELEMENTS (tb) - 1, (nla), (policy)); \
+	})
+
 /*****************************************************************************/
 
 struct nl_msg *nlmsg_alloc (void);
@@ -336,7 +400,13 @@ 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);
+int nlmsg_append (struct nl_msg *n,
+                  const void *data,
+                  size_t len,
+                  int pad);
+
+#define nlmsg_append_struct(n, data) \
+	nlmsg_append (n, (data), sizeof (*(data)), NLMSG_ALIGNTO)
 
 void nlmsg_free (struct nl_msg *msg);
 
@@ -356,15 +426,15 @@ nlmsg_total_size (int payload)
 static inline int
 nlmsg_ok (const struct nlmsghdr *nlh, int remaining)
 {
-	return (remaining >= (int)sizeof(struct nlmsghdr) &&
-	       nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&
+	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);
+	int totlen = NLMSG_ALIGN (nlh->nlmsg_len);
 
 	*remaining -= totlen;
 
@@ -384,7 +454,7 @@ _nm_auto_nl_msg_cleanup (struct nl_msg **ptr)
 {
 	nlmsg_free (*ptr);
 }
-#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup)
+#define nm_auto_nlmsg nm_auto (_nm_auto_nl_msg_cleanup)
 
 static inline void *
 nlmsg_data (const struct nlmsghdr *nlh)
@@ -395,13 +465,13 @@ nlmsg_data (const struct nlmsghdr *nlh)
 static inline void *
 nlmsg_tail (const struct nlmsghdr *nlh)
 {
-	return (unsigned char *) nlh + NLMSG_ALIGN(nlh->nlmsg_len);
+	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)
+nlmsg_valid_hdr (const struct nlmsghdr *nlh, int hdrlen)
 {
 	if (nlh->nlmsg_len < nlmsg_size (hdrlen))
 		return 0;
@@ -424,8 +494,8 @@ nlmsg_attrlen (const struct nlmsghdr *nlh, int hdrlen)
 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));
+	unsigned char *data = nlmsg_data (nlh);
+	return (struct nlattr *) (data + NLMSG_ALIGN (hdrlen));
 }
 
 static inline struct nlattr *
@@ -436,8 +506,19 @@ nlmsg_find_attr (struct nlmsghdr *nlh, int hdrlen, int attrtype)
 	                 attrtype);
 }
 
-int nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
-                 int maxtype, const struct nla_policy *policy);
+int nlmsg_parse (struct nlmsghdr *nlh,
+                 int hdrlen,
+                 struct nlattr *tb[],
+                 int maxtype,
+                 const struct nla_policy *policy);
+
+#define nlmsg_parse_arr(nlh, hdrlen, tb, policy) \
+	({ \
+		_nl_static_assert_tb ((tb), (policy)); \
+		G_STATIC_ASSERT_EXPR ((hdrlen) >= 0); \
+		\
+		nlmsg_parse ((nlh), (hdrlen), (tb), G_N_ELEMENTS (tb) - 1, (policy)); \
+	})
 
 struct nlmsghdr *nlmsg_put (struct nl_msg *n, uint32_t pid, uint32_t seq,
                             int type, int payload, int flags);
@@ -474,8 +555,11 @@ 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_recv (struct nl_sock *sk,
+             struct sockaddr_nl *nla,
+             unsigned char **buf,
+             struct ucred *out_creds,
+             gboolean *out_creds_has);
 
 int nl_send (struct nl_sock *sk, struct nl_msg *msg);
 
@@ -536,8 +620,20 @@ 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 genlmsg_parse (struct nlmsghdr *nlh,
+                   int hdrlen,
+                   struct nlattr *tb[],
+                   int maxtype,
+                   const struct nla_policy *policy);
+
+#define genlmsg_parse_arr(nlh, hdrlen, tb, policy) \
+	({ \
+		_nl_static_assert_tb ((tb), (policy)); \
+		G_STATIC_ASSERT_EXPR ((hdrlen) >= 0); \
+		\
+		genlmsg_parse ((nlh), (hdrlen), (tb), G_N_ELEMENTS (tb) - 1, (policy)); \
+	})
 
 int genl_ctrl_resolve (struct nl_sock *sk, const char *name);
 
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index cc43b27f..93cd0b3c 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -22,9 +22,7 @@
 
 #include "nm-platform-utils.h"
 
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <linux/ethtool.h>
 #include <linux/sockios.h>
@@ -86,7 +84,7 @@ socket_handle_init (SocketHandle *shandle, int ifindex)
 	shandle->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
 	if (shandle->fd < 0) {
 		shandle->ifindex = 0;
-		return -errno;
+		return -NM_ERRNO_NATIVE (errno);
 	}
 
 	shandle->ifindex = ifindex;
@@ -159,8 +157,8 @@ ethtool_call_handle (SocketHandle *shandle, gpointer edata)
 		              shandle->ifindex,
 		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
 		              shandle->ifname,
-		              strerror (errsv));
-		return -errsv;
+		              nm_strerror_native (errsv));
+		return -NM_ERRNO_NATIVE (errsv);
 	}
 
 	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: success",
@@ -183,7 +181,7 @@ ethtool_call_ifindex (int ifindex, gpointer edata)
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return r;
 	}
 
@@ -489,7 +487,7 @@ nmp_utils_ethtool_get_features (int ifindex)
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              "get-features",
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -620,7 +618,7 @@ nmp_utils_ethtool_set_features (int ifindex,
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              "set-features",
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -656,7 +654,7 @@ nmp_utils_ethtool_set_features (int ifindex,
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure setting features (%s)",
 		              ifindex,
 		              "set-features",
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -665,7 +663,7 @@ nmp_utils_ethtool_set_features (int ifindex,
 	              "set-features",
 	              success
 	                ? "successfully setting features"
-	                : "at least some of the features were not successfuly set");
+	                : "at least some of the features were not successfully set");
 	return success;
 }
 
@@ -766,7 +764,7 @@ nmp_utils_ethtool_supports_vlans (int ifindex)
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              "support-vlans",
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -805,7 +803,7 @@ nmp_utils_ethtool_get_peer_ifindex (int ifindex)
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              "get-peer-ifindex",
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -894,7 +892,7 @@ nmp_utils_ethtool_get_link_settings (int ifindex,
                          | ADVERTISED_1000baseT_Full \
                          | ADVERTISED_10000baseT_Full )
 
-static inline guint32
+static guint32
 get_baset_mode (guint32 speed, NMPlatformLinkDuplexType duplex)
 {
 	if (duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN)
@@ -1045,13 +1043,14 @@ nmp_utils_mii_supports_carrier_detect (int ifindex)
 	int r;
 	struct ifreq ifr;
 	struct mii_ioctl_data *mii;
+	int errsv;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
 	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
 		nm_log_trace (LOGD_PLATFORM, "mii[%d]: carrier-detect no: failed creating ethtool socket: %s",
 		              ifindex,
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -1059,7 +1058,8 @@ nmp_utils_mii_supports_carrier_detect (int ifindex)
 	memcpy (ifr.ifr_name, shandle.ifname, IFNAMSIZ);
 
 	if (ioctl (shandle.fd, SIOCGMIIPHY, &ifr) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, shandle.ifname, strerror (errno));
+		errsv = errno;
+		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, shandle.ifname, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -1068,7 +1068,8 @@ nmp_utils_mii_supports_carrier_detect (int ifindex)
 	mii->reg_num = MII_BMSR;
 
 	if (ioctl (shandle.fd, SIOCGMIIREG, &ifr) != 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, shandle.ifname, strerror (errno));
+		errsv = errno;
+		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, shandle.ifname, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -1248,7 +1249,7 @@ nmp_utils_ip_config_source_to_string (NMIPConfigSource source, char *buf, gsize
  * @ifindex: the ifindex for which to open "/sys/class/net/%s"
  * @ifname_guess: (allow-none): optional argument, if present used as initial
  *   guess as the current name for @ifindex. If guessed right,
- *   it saves an addtional if_indextoname() call.
+ *   it saves an additional if_indextoname() call.
  * @out_ifname: (allow-none): if present, must be at least IFNAMSIZ
  *   characters. On success, this will contain the actual ifname
  *   found while opening the directory.
@@ -1277,7 +1278,6 @@ nmp_utils_sysctl_open_netdir (int ifindex,
 	for (try_count = 0; try_count < 10; try_count++, ifname = NULL) {
 		nm_auto_close int fd_dir = -1;
 		nm_auto_close int fd_ifindex = -1;
-		int fd;
 
 		if (!ifname) {
 			ifname = nmp_utils_if_indextoname (ifindex, ifname_buf);
@@ -1310,15 +1310,13 @@ nmp_utils_sysctl_open_netdir (int ifindex,
 			continue;
 		fd_buf[nn] = '\0';
 
-		if (ifindex != _nm_utils_ascii_str_to_int64 (fd_buf, 10, 1, G_MAXINT, -1))
+		if (ifindex != (int) _nm_utils_ascii_str_to_int64 (fd_buf, 10, 1, G_MAXINT, -1))
 			continue;
 
 		if (out_ifname)
 			strcpy (out_ifname, ifname);
 
-		fd = fd_dir;
-		fd_dir = -1;
-		return fd;
+		return nm_steal_fd (&fd_dir);
 	}
 
 	return -1;
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 757a0f43..fe0cb662 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -23,13 +23,11 @@
 #include "nm-platform.h"
 
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
 #include <netdb.h>
-#include <string.h>
 #include <linux/ip.h>
 #include <linux/if.h>
 #include <linux/if_tun.h>
@@ -41,6 +39,7 @@
 #include "nm-core-internal.h"
 #include "nm-utils/nm-dedup-multi.h"
 #include "nm-utils/nm-udev-utils.h"
+#include "nm-utils/nm-secret-utils.h"
 
 #include "nm-core-utils.h"
 #include "nm-platform-utils.h"
@@ -58,22 +57,48 @@ G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OF
 
 #define _NMLOG_DOMAIN           LOGD_PLATFORM
 #define _NMLOG_PREFIX_NAME      "platform"
+
+
+#define NMLOG_COMMON(level, name, ...) \
+        char __prefix[32]; \
+        const char *__p_prefix = _NMLOG_PREFIX_NAME; \
+        const NMPlatform *const __self = (self); \
+        const char *__name = name; \
+        \
+        if (__self && NM_PLATFORM_GET_PRIVATE (__self)->log_with_ptr) { \
+            g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
+            __p_prefix = __prefix; \
+        } \
+        _nm_log (__level, _NMLOG_DOMAIN, 0, __name, NULL, \
+                 "%s: %s%s%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                 __p_prefix, \
+                 NM_PRINT_FMT_QUOTED (__name, "(", __name, ") ", "") \
+                 _NM_UTILS_MACRO_REST (__VA_ARGS__));
+
 #define _NMLOG(level, ...) \
     G_STMT_START { \
         const NMLogLevel __level = (level); \
         \
         if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
-            char __prefix[32]; \
-            const char *__p_prefix = _NMLOG_PREFIX_NAME; \
-            const NMPlatform *const __self = (self); \
-            \
-            if (__self && NM_PLATFORM_GET_PRIVATE (__self)->log_with_ptr) { \
-                g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
-                __p_prefix = __prefix; \
-            } \
-            _nm_log (__level, _NMLOG_DOMAIN, 0, NULL, NULL, \
-                     "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                     __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+            NMLOG_COMMON(level, NULL, __VA_ARGS__);  \
+        } \
+    } G_STMT_END
+
+#define _NMLOG2(level, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        \
+        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
+            NMLOG_COMMON(level, name, __VA_ARGS__);  \
+        } \
+    } G_STMT_END
+
+#define _NMLOG3(level, ...)  \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        \
+        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
+            NMLOG_COMMON(level, ifindex > 0 ? nm_platform_link_get_name (self, ifindex) : NULL, __VA_ARGS__);  \
         } \
     } G_STMT_END
 
@@ -229,58 +254,6 @@ nm_platform_get_multi_idx (NMPlatform *self)
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_nm_platform_error_to_string, NMPlatformError,
-	NM_UTILS_LOOKUP_DEFAULT (NULL),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_SUCCESS,     "success"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_BUG,         "bug"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_UNSPECIFIED, "unspecified"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NOT_FOUND,   "not-found"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_EXISTS,      "exists"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_WRONG_TYPE,  "wrong-type"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NOT_SLAVE,   "not-slave"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NO_FIRMWARE, "no-firmware"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_OPNOTSUPP,   "not-supported"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NETLINK,     "netlink"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_CANT_SET_MTU, "cant-set-mtu"),
-	NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_PLATFORM_ERROR_MININT),
-);
-
-/**
- * nm_platform_error_to_string:
- * @error_code: the error code to stringify.
- * @buf: (allow-none): buffer
- * @buf_len: size of buffer
- *
- * Returns: A string representation of the error.
- * For negative numbers, this function interprets
- * the code as -errno.
- * For invalid (positive) numbers it returns NULL.
- */
-const char *
-nm_platform_error_to_string (NMPlatformError error_code, char *buf, gsize buf_len)
-{
-	const char *s;
-
-	if (error_code < 0) {
-		int errsv = -((int) error_code);
-
-		nm_utils_to_string_buffer_init (&buf, &buf_len);
-		g_snprintf (buf, buf_len, "%s (%d)", g_strerror (errsv), errsv);
-	} else {
-		s = _nm_platform_error_to_string (error_code);
-		if (s) {
-			if (!buf)
-				return s;
-			g_strlcpy (buf, s, buf_len);
-		} else {
-			nm_utils_to_string_buffer_init (&buf, &buf_len);
-			g_snprintf (buf, buf_len, "(%d)", (int) error_code);
-		}
-	}
-
-	return buf;
-}
-
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_nmp_nlm_flag_to_string_lookup, NMPNlmFlags,
 	NM_UTILS_LOOKUP_DEFAULT (NULL),
 	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_ADD,     "add"),
@@ -450,7 +423,9 @@ nm_platform_sysctl_set (NMPlatform *self, const char *pathid, int dirfd, const c
 }
 
 gboolean
-nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface, int value)
+nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (NMPlatform *self,
+                                                    const char *iface,
+                                                    int value)
 {
 	const char *path;
 	gint64 cur;
@@ -544,7 +519,14 @@ nm_platform_sysctl_get_int32 (NMPlatform *self, const char *pathid, int dirfd, c
  * (inclusive) or @fallback.
  */
 gint64
-nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *pathid, int dirfd, const char *path, guint base, gint64 min, gint64 max, gint64 fallback)
+nm_platform_sysctl_get_int_checked (NMPlatform *self,
+                                    const char *pathid,
+                                    int dirfd,
+                                    const char *path,
+                                    guint base,
+                                    gint64 min,
+                                    gint64 max,
+                                    gint64 fallback)
 {
 	char *value = NULL;
 	gint32 ret;
@@ -571,6 +553,81 @@ nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *pathid, int di
 
 /*****************************************************************************/
 
+char *
+nm_platform_sysctl_ip_conf_get (NMPlatform *platform,
+                                int addr_family,
+                                const char *ifname,
+                                const char *property)
+{
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	return nm_platform_sysctl_get (platform,
+	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
+	                                                                                         buf,
+	                                                                                         ifname,
+	                                                                                         property)));
+}
+
+gint64
+nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
+                                            int addr_family,
+                                            const char *ifname,
+                                            const char *property,
+                                            guint base,
+                                            gint64 min,
+                                            gint64 max,
+                                            gint64 fallback)
+{
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	return nm_platform_sysctl_get_int_checked (platform,
+	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
+	                                                                                                     buf,
+	                                                                                                     ifname,
+	                                                                                                     property)),
+	                                           base,
+	                                           min,
+	                                           max,
+	                                           fallback);
+}
+
+gboolean
+nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
+                                int addr_family,
+                                const char *ifname,
+                                const char *property,
+                                const char *value)
+{
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	return nm_platform_sysctl_set (platform,
+	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
+	                                                                                         buf,
+	                                                                                         ifname,
+	                                                                                         property)),
+	                               value);
+}
+
+gboolean
+nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
+                                      int addr_family,
+                                      const char *ifname,
+                                      const char *property,
+                                      gint64 value)
+{
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+	char s[64];
+
+	return nm_platform_sysctl_set (platform,
+	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
+	                                                                                         buf,
+	                                                                                         ifname,
+	                                                                                         property)),
+	                               nm_sprintf_buf (s, "%"G_GINT64_FORMAT, value));
+}
+
+/*****************************************************************************/
+
 static int
 _link_get_all_presort (gconstpointer  p_a,
                        gconstpointer  p_b,
@@ -728,6 +785,8 @@ nm_platform_link_get_obj (NMPlatform *self,
 {
 	const NMPObject *obj_cache;
 
+	_CHECK_SELF (self, klass, NULL);
+
 	obj_cache = nmp_cache_lookup_link (nm_platform_get_cache (self), ifindex);
 	if (   !obj_cache
 	    || (   visible_only
@@ -753,15 +812,7 @@ nm_platform_link_get_obj (NMPlatform *self,
 const NMPlatformLink *
 nm_platform_link_get (NMPlatform *self, int ifindex)
 {
-	const NMPObject *obj;
-
-	_CHECK_SELF (self, klass, NULL);
-
-	if (ifindex <= 0)
-		return NULL;
-
-	obj = nm_platform_link_get_obj (self, ifindex, TRUE);
-	return NMP_OBJECT_CAST_LINK (obj);
+	return NMP_OBJECT_CAST_LINK (nm_platform_link_get_obj (self, ifindex, TRUE));
 }
 
 /**
@@ -834,7 +885,7 @@ nm_platform_link_get_by_address (NMPlatform *self,
 	return NMP_OBJECT_CAST_LINK (obj);
 }
 
-static NMPlatformError
+static int
 _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, const NMPlatformLink **out_link)
 {
 	const NMPlatformLink *pllink;
@@ -844,20 +895,19 @@ _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, c
 		gboolean wrong_type;
 
 		wrong_type = type != NM_LINK_TYPE_NONE && pllink->type != type;
-		_LOGD ("link: skip adding link due to existing interface '%s' of type %s%s%s",
-		       name,
-		       nm_link_type_to_string (pllink->type),
-		       wrong_type ? ", expected " : "",
-		       wrong_type ? nm_link_type_to_string (type) : "");
+		_LOG2D ("link: skip adding link due to existing interface of type %s%s%s",
+		        nm_link_type_to_string (pllink->type),
+		        wrong_type ? ", expected " : "",
+		        wrong_type ? nm_link_type_to_string (type) : "");
 		if (out_link)
 			*out_link = pllink;
 		if (wrong_type)
-			return NM_PLATFORM_ERROR_WRONG_TYPE;
-		return NM_PLATFORM_ERROR_EXISTS;
+			return -NME_PL_WRONG_TYPE;
+		return -NME_PL_EXISTS;
 	}
 	if (out_link)
 		*out_link = NULL;
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return 0;
 }
 
 /**
@@ -871,16 +921,16 @@ _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, c
  * @out_link: on success, the link object
  *
  * Add a software interface.  If the interface already exists and is of type
- * @type, return NM_PLATFORM_ERROR_EXISTS and returns the link
+ * @type, return -NME_PL_EXISTS and returns the link
  * in @out_link.  If the interface already exists and is not of type @type,
- * return NM_PLATFORM_ERROR_WRONG_TYPE.
+ * return -NME_PL_WRONG_TYPE.
  *
  * Any link-changed ADDED signal will be emitted directly, before this
  * function finishes.
  *
- * Returns: the error reason or NM_PLATFORM_ERROR_SUCCESS.
+ * Returns: the negative nm-error on failure.
  */
-static NMPlatformError
+static int
 nm_platform_link_add (NMPlatform *self,
                       const char *name,
                       NMLinkType type,
@@ -889,38 +939,35 @@ nm_platform_link_add (NMPlatform *self,
                       size_t address_len,
                       const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 	char addr_buf[NM_UTILS_HWADDR_LEN_MAX * 3];
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail ((address != NULL) ^ (address_len == 0) , NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (address_len <= NM_UTILS_HWADDR_LEN_MAX, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail ((!!veth_peer) == (type == NM_LINK_TYPE_VETH), NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
+	g_return_val_if_fail ((address != NULL) ^ (address_len == 0) , -NME_BUG);
+	g_return_val_if_fail (address_len <= NM_UTILS_HWADDR_LEN_MAX, -NME_BUG);
+	g_return_val_if_fail ((!!veth_peer) == (type == NM_LINK_TYPE_VETH), -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, type, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, type, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("link: adding link '%s': %s (%d)"
-	       "%s%s" /* address */
-	       "%s%s" /* veth peer */
-	       "",
-	       name,
-	       nm_link_type_to_string (type),
-	       (int) type,
-	       address ? ", address: " : "",
-	       address ? nm_utils_hwaddr_ntoa_buf (address, address_len, FALSE, addr_buf, sizeof (addr_buf)) : "",
-	       veth_peer ? ", veth-peer: " : "",
-	       veth_peer ?: "");
+	_LOG2D ("link: adding link: %s (%d)"
+	        "%s%s" /* address */
+	        "%s%s" /* veth peer */
+	        "",
+	        nm_link_type_to_string (type),
+	        (int) type,
+	        address ? ", address: " : "",
+	        address ? nm_utils_hwaddr_ntoa_buf (address, address_len, FALSE, addr_buf, sizeof (addr_buf)) : "",
+	        veth_peer ? ", veth-peer: " : "",
+	        veth_peer ?: "");
 
-	if (!klass->link_add (self, name, type, veth_peer, address, address_len, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return klass->link_add (self, name, type, veth_peer, address, address_len, out_link);
 }
 
-NMPlatformError
+int
 nm_platform_link_veth_add (NMPlatform *self,
                             const char *name,
                             const char *peer,
@@ -937,7 +984,7 @@ nm_platform_link_veth_add (NMPlatform *self,
  *
  * Create a software ethernet-like interface
  */
-NMPlatformError
+int
 nm_platform_link_dummy_add (NMPlatform *self,
                             const char *name,
                             const NMPlatformLink **out_link)
@@ -953,15 +1000,11 @@ nm_platform_link_dummy_add (NMPlatform *self,
 gboolean
 nm_platform_link_delete (NMPlatform *self, int ifindex)
 {
-	const NMPlatformLink *pllink;
-
 	_CHECK_SELF (self, klass, FALSE);
 
-	pllink = nm_platform_link_get (self, ifindex);
-	if (!pllink)
-		return FALSE;
+	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	_LOGD ("link: deleting '%s' (%d)", pllink->name, ifindex);
+	_LOG3D ("link: deleting");
 	return klass->link_delete (self, ifindex);
 }
 
@@ -976,18 +1019,12 @@ nm_platform_link_delete (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_set_netns (NMPlatform *self, int ifindex, int netns_fd)
 {
-	const NMPlatformLink *pllink;
-
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (netns_fd > 0, FALSE);
 
-	pllink = nm_platform_link_get (self, ifindex);
-	if (!pllink)
-		return FALSE;
-
-	_LOGD ("link: move link %d to network namespace with fd %d", ifindex, netns_fd);
+	_LOG3D ("link: move link to network namespace with fd %d", netns_fd);
 	return klass->link_set_netns (self, ifindex, netns_fd);
 }
 
@@ -997,7 +1034,7 @@ nm_platform_link_set_netns (NMPlatform *self, int ifindex, int netns_fd)
  * @name: Interface name
  *
  * Returns: The interface index corresponding to the given interface name
- * or 0. Inteface name is owned by #NMPlatform, don't free it.
+ * or 0. Interface name is owned by #NMPlatform, don't free it.
  */
 int
 nm_platform_link_get_ifindex (NMPlatform *self, const char *name)
@@ -1037,8 +1074,6 @@ nm_platform_link_get_name (NMPlatform *self, int ifindex)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, NULL);
-
 	pllink = nm_platform_link_get (self, ifindex);
 	return pllink ? pllink->name : NULL;
 }
@@ -1056,8 +1091,6 @@ nm_platform_link_get_type (NMPlatform *self, int ifindex)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, NM_LINK_TYPE_NONE);
-
 	pllink = nm_platform_link_get (self, ifindex);
 	return pllink ? pllink->type : NM_LINK_TYPE_NONE;
 }
@@ -1076,16 +1109,13 @@ nm_platform_link_get_type_name (NMPlatform *self, int ifindex)
 {
 	const NMPObject *obj;
 
-	_CHECK_SELF (self, klass, NULL);
-
 	obj = nm_platform_link_get_obj (self, ifindex, TRUE);
-
 	if (!obj)
 		return NULL;
 
 	if (obj->link.type != NM_LINK_TYPE_UNKNOWN) {
 		/* We could detect the @link_type. In this case the function returns
-		 * our internel module names, which differs from rtnl_link_get_type():
+		 * our internal module names, which differs from rtnl_link_get_type():
 		 *   - NM_LINK_TYPE_INFINIBAND (gives "infiniband", instead of "ipoib")
 		 *   - NM_LINK_TYPE_TAP (gives "tap", instead of "tun").
 		 * Note that this functions is only used by NMDeviceGeneric to
@@ -1200,11 +1230,6 @@ nm_platform_link_get_ifi_flags (NMPlatform *self,
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, -EINVAL);
-
-	if (ifindex <= 0)
-		return -EINVAL;
-
 	/* include invisible links (only in netlink, not udev). */
 	pllink = NMP_OBJECT_CAST_LINK (nm_platform_link_get_obj (self, ifindex, FALSE));
 	if (!pllink)
@@ -1243,8 +1268,6 @@ nm_platform_link_is_connected (NMPlatform *self, int ifindex)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, FALSE);
-
 	pllink = nm_platform_link_get (self, ifindex);
 	return pllink ? pllink->connected : FALSE;
 }
@@ -1310,10 +1333,6 @@ nm_platform_link_get_udev_device (NMPlatform *self, int ifindex)
 {
 	const NMPObject *obj_cache;
 
-	_CHECK_SELF (self, klass, FALSE);
-
-	g_return_val_if_fail (ifindex >= 0, NULL);
-
 	obj_cache = nm_platform_link_get_obj (self, ifindex, FALSE);
 	return obj_cache ? obj_cache->_link.udev.device : NULL;
 }
@@ -1334,10 +1353,6 @@ nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, FALSE);
-
-	g_return_val_if_fail (ifindex >= 0, FALSE);
-
 	pllink = nm_platform_link_get (self, ifindex);
 	if (pllink && pllink->inet6_addr_gen_mode_inv)
 		return _nm_platform_uint8_inv (pllink->inet6_addr_gen_mode_inv) == NM_IN6_ADDR_GEN_MODE_NONE;
@@ -1353,14 +1368,14 @@ nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex)
  * platform or OS doesn't support changing the IPv6LL address mode, this call
  * will fail and return %FALSE.
  *
- * Returns: %NM_PLATFORM_ERROR_SUCCESS if the operation was successful or an error code otherwise.
+ * Returns: the negative nm-error on failure.
  */
-NMPlatformError
+int
 nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled)
 {
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (ifindex > 0, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (ifindex > 0, -NME_BUG);
 
 	return klass->link_set_user_ipv6ll_enabled (self, ifindex, enabled);
 }
@@ -1373,21 +1388,19 @@ nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolea
  *
  * Set interface MAC address.
  */
-NMPlatformError
+int
 nm_platform_link_set_address (NMPlatform *self, int ifindex, gconstpointer address, size_t length)
 {
 	gs_free char *mac = NULL;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (ifindex > 0, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (address, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (length > 0, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (ifindex > 0, -NME_BUG);
+	g_return_val_if_fail (address, -NME_BUG);
+	g_return_val_if_fail (length > 0, -NME_BUG);
 
-	_LOGD ("link: setting %s (%d) hardware address to %s",
-	       nm_strquote_a (20, nm_platform_link_get_name (self, ifindex)),
-	       ifindex,
-	       (mac = nm_utils_hwaddr_ntoa (address, length)));
+	_LOG3D ("link: setting hardware address to %s",
+	        (mac = nm_utils_hwaddr_ntoa (address, length)));
 	return klass->link_set_address (self, ifindex, address, length);
 }
 
@@ -1405,12 +1418,7 @@ nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, NULL);
-
-	g_return_val_if_fail (ifindex > 0, NULL);
-
 	pllink = nm_platform_link_get (self, ifindex);
-
 	if (   !pllink
 	    || pllink->addr.len <= 0) {
 		NM_SET_OUT (length, 0);
@@ -1502,12 +1510,7 @@ nm_platform_link_set_sriov_params (NMPlatform *self,
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	_LOGD ("link: setting %u total VFs and autoprobe %d for %s (%d)",
-	       num_vfs,
-	       (int) autoprobe,
-	       nm_strquote_a (25, nm_platform_link_get_name (self, ifindex)),
-	       ifindex);
-
+	_LOG3D ("link: setting %u total VFs and autoprobe %d", num_vfs, (int) autoprobe);
 	return klass->link_set_sriov_params (self, ifindex, num_vfs, autoprobe);
 }
 
@@ -1519,14 +1522,11 @@ nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformV
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	_LOGD ("link: setting VFs for \"%s\" (%d):",
-	       nm_platform_link_get_name (self, ifindex),
-	       ifindex);
-
+	_LOG3D ("link: setting VFs");
 	for (i = 0; vfs[i]; i++) {
 		const NMPlatformVF *vf = vfs[i];
 
-		_LOGD ("link:   VF %s", nm_platform_vf_to_string (vf, NULL, 0));
+		_LOG3D ("link:   VF %s", nm_platform_vf_to_string (vf, NULL, 0));
 	}
 
 	return klass->link_set_sriov_vfs (self, ifindex, vfs);
@@ -1547,7 +1547,7 @@ nm_platform_link_set_up (NMPlatform *self, int ifindex, gboolean *out_no_firmwar
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	_LOGD ("link: setting up %s (%d)", nm_strquote_a (25, nm_platform_link_get_name (self, ifindex)), ifindex);
+	_LOG3D ("link: setting up");
 	return klass->link_set_up (self, ifindex, out_no_firmware);
 }
 
@@ -1565,7 +1565,7 @@ nm_platform_link_set_down (NMPlatform *self, int ifindex)
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	_LOGD ("link: setting down %s (%d)", nm_strquote_a (25, nm_platform_link_get_name (self, ifindex)), ifindex);
+	_LOG3D ("link: setting down");
 	return klass->link_set_down (self, ifindex);
 }
 
@@ -1583,7 +1583,7 @@ nm_platform_link_set_arp (NMPlatform *self, int ifindex)
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 
-	_LOGD ("link: setting arp %s (%d)", nm_strquote_a (25, nm_platform_link_get_name (self, ifindex)), ifindex);
+	_LOG3D ("link: setting arp");
 	return klass->link_set_arp (self, ifindex);
 }
 
@@ -1601,7 +1601,7 @@ nm_platform_link_set_noarp (NMPlatform *self, int ifindex)
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 
-	_LOGD ("link: setting noarp '%s' (%d)", nm_platform_link_get_name (self, ifindex), ifindex);
+	_LOG3D ("link: setting noarp");
 	return klass->link_set_noarp (self, ifindex);
 }
 
@@ -1613,7 +1613,7 @@ nm_platform_link_set_noarp (NMPlatform *self, int ifindex)
  *
  * Set interface MTU.
  */
-NMPlatformError
+int
 nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu)
 {
 	_CHECK_SELF (self, klass, FALSE);
@@ -1621,7 +1621,7 @@ nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu)
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 	g_return_val_if_fail (mtu > 0, FALSE);
 
-	_LOGD ("link: setting '%s' (%d) mtu %"G_GUINT32_FORMAT, nm_platform_link_get_name (self, ifindex), ifindex, mtu);
+	_LOG3D ("link: setting mtu %"G_GUINT32_FORMAT, mtu);
 	return klass->link_set_mtu (self, ifindex, mtu);
 }
 
@@ -1637,8 +1637,6 @@ nm_platform_link_get_mtu (NMPlatform *self, int ifindex)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, 0);
-
 	pllink = nm_platform_link_get (self, ifindex);
 	return pllink ? pllink->mtu : 0;
 }
@@ -1659,7 +1657,7 @@ nm_platform_link_set_name (NMPlatform *self, int ifindex, const char *name)
 	g_return_val_if_fail (ifindex >= 0, FALSE);
 	g_return_val_if_fail (name, FALSE);
 
-	_LOGD ("link: setting '%s' (%d) name %s", nm_platform_link_get_name (self, ifindex), ifindex, name);
+	_LOG3D ("link: setting name %s", name);
 
 	if (strlen (name) + 1 > IFNAMSIZ)
 		return FALSE;
@@ -1770,47 +1768,43 @@ nm_platform_link_get_driver_info (NMPlatform *self,
  * nm_platform_link_enslave:
  * @self: platform instance
  * @master: Interface index of the master
- * @slave: Interface index of the slave
+ * @ifindex: Interface index of the slave
  *
- * Enslave @slave to @master.
+ * Enslave @ifindex to @master.
  */
 gboolean
-nm_platform_link_enslave (NMPlatform *self, int master, int slave)
+nm_platform_link_enslave (NMPlatform *self, int master, int ifindex)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (master > 0, FALSE);
-	g_return_val_if_fail (slave> 0, FALSE);
+	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	_LOGD ("link: enslaving '%s' (%d) to master '%s' (%d)",
-	       nm_platform_link_get_name (self, slave), slave,
-	       nm_platform_link_get_name (self, master), master);
-	return klass->link_enslave (self, master, slave);
+	_LOG3D ("link: enslaving to master '%s'", nm_platform_link_get_name (self, master));
+	return klass->link_enslave (self, master, ifindex);
 }
 
 /**
  * nm_platform_link_release:
  * @self: platform instance
  * @master: Interface index of the master
- * @slave: Interface index of the slave
+ * @ifindex: Interface index of the slave
  *
  * Release @slave from @master.
  */
 gboolean
-nm_platform_link_release (NMPlatform *self, int master, int slave)
+nm_platform_link_release (NMPlatform *self, int master, int ifindex)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (master > 0, FALSE);
-	g_return_val_if_fail (slave > 0, FALSE);
+	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if (nm_platform_link_get_master (self, slave) != master)
+	if (nm_platform_link_get_master (self, ifindex) != master)
 		return FALSE;
 
-	_LOGD ("link: releasing '%s' (%d) from master '%s' (%d)",
-	       nm_platform_link_get_name (self, slave), slave,
-	       nm_platform_link_get_name (self, master), master);
-	return klass->link_release (self, master, slave);
+	_LOG3D ("link: releasing from master '%s'", nm_platform_link_get_name (self, master));
+	return klass->link_release (self, master, ifindex);
 }
 
 /**
@@ -1825,10 +1819,6 @@ nm_platform_link_get_master (NMPlatform *self, int slave)
 {
 	const NMPlatformLink *pllink;
 
-	_CHECK_SELF (self, klass, 0);
-
-	g_return_val_if_fail (slave >= 0, FALSE);
-
 	pllink = nm_platform_link_get (self, slave);
 	return pllink ? pllink->master : 0;
 }
@@ -1866,7 +1856,7 @@ nm_platform_link_can_assume (NMPlatform *self, int ifindex)
  * Returns: the internal link lnk object. The returned object
  * is owned by the platform cache and must not be modified. Note
  * however, that the object is guaranteed to be immutable, so
- * you can savely take a reference and keep it for yourself
+ * you can safely take a reference and keep it for yourself
  * (but don't modify it).
  */
 const NMPObject *
@@ -1874,15 +1864,11 @@ nm_platform_link_get_lnk (NMPlatform *self, int ifindex, NMLinkType link_type, c
 {
 	const NMPObject *obj;
 
-	_CHECK_SELF (self, klass, FALSE);
-
-	NM_SET_OUT (out_link, NULL);
-
-	g_return_val_if_fail (ifindex > 0, NULL);
-
 	obj = nm_platform_link_get_obj (self, ifindex, TRUE);
-	if (!obj)
+	if (!obj) {
+		NM_SET_OUT (out_link, NULL);
 		return NULL;
+	}
 
 	NM_SET_OUT (out_link, &obj->link);
 
@@ -1997,6 +1983,90 @@ nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatf
 
 /*****************************************************************************/
 
+NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_flags_to_string, NMPlatformWireGuardChangeFlags,
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE,            "none"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS,   "replace-peers"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY, "has-private-key"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT, "has-listen-port"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK,      "has-fwmark"),
+);
+
+NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_peer_flags_to_string, NMPlatformWireGuardChangePeerFlags,
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE,                   "none"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME,              "remove"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY,      "psk"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL, "ka"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT,           "ep"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS,         "aips"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS,     "remove-aips"),
+);
+
+int
+nm_platform_link_wireguard_add (NMPlatform *self,
+                                const char *name,
+                                const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, name, NM_LINK_TYPE_WIREGUARD, NULL, NULL, 0, out_link);
+}
+
+int
+nm_platform_link_wireguard_change (NMPlatform *self,
+                                   int ifindex,
+                                   const NMPlatformLnkWireGuard *lnk_wireguard,
+                                   const NMPWireGuardPeer *peers,
+                                   const NMPlatformWireGuardChangePeerFlags *peer_flags,
+                                   guint peers_len,
+                                   NMPlatformWireGuardChangeFlags change_flags)
+{
+	_CHECK_SELF (self, klass, -NME_BUG);
+
+	nm_assert (klass->link_wireguard_change);
+
+	if (_LOGD_ENABLED ()) {
+		char buf_lnk[256];
+		char buf_peers[512];
+		char buf_change_flags[100];
+
+		buf_peers[0] = '\0';
+		if (peers_len > 0) {
+			char *b = buf_peers;
+			gsize len = sizeof (buf_peers);
+			guint i;
+
+			nm_utils_strbuf_append_str (&b, &len, " { ");
+			for (i = 0; i < peers_len; i++) {
+				nm_utils_strbuf_append_str (&b, &len, " { ");
+				nm_platform_wireguard_peer_to_string (&peers[i], b, len);
+				nm_utils_strbuf_seek_end (&b, &len);
+				if (peer_flags) {
+					nm_utils_strbuf_append (&b, &len,
+					                       " (%s)",
+					                       _wireguard_change_peer_flags_to_string (peer_flags[i], buf_change_flags, sizeof (buf_change_flags)));
+				}
+				nm_utils_strbuf_append_str (&b, &len, " } ");
+			}
+			nm_utils_strbuf_append_str (&b, &len, "}");
+		}
+
+		_LOG3D ("link: change wireguard ifindex %d, %s, (%s), %u peers%s",
+		        ifindex,
+		        nm_platform_lnk_wireguard_to_string (lnk_wireguard, buf_lnk, sizeof (buf_lnk)),
+		        _wireguard_change_flags_to_string (change_flags, buf_change_flags, sizeof (buf_change_flags)),
+		        peers_len,
+		        buf_peers);
+	}
+
+	return klass->link_wireguard_change (self,
+	                                     ifindex,
+	                                     lnk_wireguard,
+	                                     peers,
+	                                     peer_flags,
+	                                     peers_len,
+	                                     change_flags);
+}
+
+/*****************************************************************************/
+
 /**
  * nm_platform_link_bridge_add:
  * @self: platform instance
@@ -2007,7 +2077,7 @@ nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatf
  *
  * Create a software bridge.
  */
-NMPlatformError
+int
 nm_platform_link_bridge_add (NMPlatform *self,
                              const char *name,
                              const void *address,
@@ -2025,7 +2095,7 @@ nm_platform_link_bridge_add (NMPlatform *self,
  *
  * Create a software bonding device.
  */
-NMPlatformError
+int
 nm_platform_link_bond_add (NMPlatform *self,
                            const char *name,
                            const NMPlatformLink **out_link)
@@ -2041,7 +2111,7 @@ nm_platform_link_bond_add (NMPlatform *self,
  *
  * Create a software teaming device.
  */
-NMPlatformError
+int
 nm_platform_link_team_add (NMPlatform *self,
                            const char *name,
                            const NMPlatformLink **out_link)
@@ -2059,7 +2129,7 @@ nm_platform_link_team_add (NMPlatform *self,
  *
  * Create a software VLAN device.
  */
-NMPlatformError
+int
 nm_platform_link_vlan_add (NMPlatform *self,
                            const char *name,
                            int parent,
@@ -2067,24 +2137,24 @@ nm_platform_link_vlan_add (NMPlatform *self,
                            guint32 vlanflags,
                            const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (parent >= 0, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (vlanid >= 0, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (parent >= 0, -NME_BUG);
+	g_return_val_if_fail (vlanid >= 0, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_VLAN, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_VLAN, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("link: adding link '%s': vlan parent %d vlanid %d vlanflags %x",
-	       name, parent, vlanid, vlanflags);
+	_LOG2D ("link: adding link vlan parent %d vlanid %d vlanflags %x",
+	        parent, vlanid, vlanflags);
 
 	if (!klass->vlan_add (self, name, parent, vlanid, vlanflags, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2096,28 +2166,27 @@ nm_platform_link_vlan_add (NMPlatform *self,
  *
  * Create a VXLAN device.
  */
-NMPlatformError
+int
 nm_platform_link_vxlan_add (NMPlatform *self,
                             const char *name,
                             const NMPlatformLnkVxlan *props,
                             const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_VXLAN, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_VXLAN, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("link: adding link '%s': %s",
-	       name, nm_platform_lnk_vxlan_to_string (props, NULL, 0));
+	_LOG2D ("link: adding link %s", nm_platform_lnk_vxlan_to_string (props, NULL, 0));
 
 	if (!klass->link_vxlan_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2139,7 +2208,7 @@ nm_platform_link_vxlan_add (NMPlatform *self,
  *
  * Create a TUN or TAP interface.
  */
-NMPlatformError
+int
 nm_platform_link_tun_add (NMPlatform *self,
                           const char *name,
                           const NMPlatformLnkTun *props,
@@ -2147,30 +2216,29 @@ nm_platform_link_tun_add (NMPlatform *self,
                           int *out_fd)
 {
 	char b[255];
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_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);
+	g_return_val_if_fail (name, -NME_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (NM_IN_SET (props->type, IFF_TUN, IFF_TAP), -NME_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);
+	g_return_val_if_fail (props->persist || out_fd, -NME_BUG);
 
 	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;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_TUN, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("link: adding link '%s': %s",
-	       name, nm_platform_lnk_tun_to_string (props, b, sizeof (b)));
+	_LOG2D ("link: adding link %s", 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;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2182,39 +2250,38 @@ nm_platform_link_tun_add (NMPlatform *self,
  *
  * Create a 6LoWPAN interface.
  */
-NMPlatformError
+int
 nm_platform_link_6lowpan_add (NMPlatform *self,
                               const char *name,
                               int parent,
                               const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_6LOWPAN, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_6LOWPAN, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': 6lowpan parent %u", name, parent);
+	_LOG2D ("adding link 6lowpan parent %u", parent);
 
 	if (!klass->link_6lowpan_add (self, name, parent, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 gboolean
 nm_platform_link_6lowpan_get_properties (NMPlatform *self, int ifindex, int *out_parent)
 {
 	const NMPlatformLink *plink;
-	_CHECK_SELF (self, klass, FALSE);
 
 	plink = nm_platform_link_get (self, ifindex);
-
 	if (!plink)
 		return FALSE;
+
 	if (plink->type != NM_LINK_TYPE_6LOWPAN)
 		return FALSE;
 
@@ -2253,9 +2320,10 @@ link_set_option (NMPlatform *self, int ifindex, const char *category, const char
 	if (dirfd < 0)
 		return FALSE;
 
-	path = nm_sprintf_bufa (strlen (category) + strlen (option) + 2,
-	                        "%s/%s",
-	                        category, option);
+	path = nm_sprintf_buf_unsafe_a (strlen (category) + strlen (option) + 2,
+	                                "%s/%s",
+	                                category,
+	                                option);
 	return nm_platform_sysctl_set (self, NMP_SYSCTL_PATHID_NETDIR_unsafe (dirfd, ifname_verified, path), value);
 }
 
@@ -2273,9 +2341,9 @@ link_get_option (NMPlatform *self, int ifindex, const char *category, const char
 	if (dirfd < 0)
 		return NULL;
 
-	path = nm_sprintf_bufa (strlen (category) + strlen (option) + 2,
-	                        "%s/%s",
-	                        category, option);
+	path = nm_sprintf_buf_unsafe_a (strlen (category) + strlen (option) + 2,
+	                                "%s/%s",
+	                                category, option);
 	return nm_platform_sysctl_get (self, NMP_SYSCTL_PATHID_NETDIR_unsafe (dirfd, ifname_verified, path));
 }
 
@@ -2408,7 +2476,7 @@ nm_platform_link_vlan_change (NMPlatform *self,
 				nm_utils_strbuf_append_str (&b, &len, " (reset-all)");
 		}
 
-		_LOGD ("link: change vlan %d:%s", ifindex, buf);
+		_LOG3D ("link: change vlan %s", buf);
 	}
 	return klass->link_vlan_change (self,
 	                                ifindex,
@@ -2453,81 +2521,78 @@ nm_platform_link_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, i
  *
  * Create a software GRE device.
  */
-NMPlatformError
+int
 nm_platform_link_gre_add (NMPlatform *self,
                           const char *name,
                           const NMPlatformLnkGre *props,
                           const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_gre_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_gre_to_string (props, NULL, 0));
 
 	if (!klass->link_gre_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
-static NMPlatformError
+static int
 _infiniband_add_add_or_delete (NMPlatform *self,
-                               int parent,
+                               int ifindex,
                                int p_key,
                                gboolean add,
                                const NMPlatformLink **out_link)
 {
 	char name[IFNAMSIZ];
 	const NMPlatformLink *parent_link;
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (parent >= 0, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (p_key >= 0 && p_key <= 0xffff, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (ifindex >= 0, -NME_BUG);
+	g_return_val_if_fail (p_key >= 0 && p_key <= 0xffff, -NME_BUG);
 
 	/* the special keys 0x0000 and 0x8000 are not allowed. */
 	if (NM_IN_SET (p_key, 0, 0x8000))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
+		return -NME_UNSPEC;
 
-	parent_link = nm_platform_link_get (self, parent);
+	parent_link = nm_platform_link_get (self, ifindex);
 	if (!parent_link)
-		return NM_PLATFORM_ERROR_NOT_FOUND;
+		return -NME_PL_NOT_FOUND;
 
 	if (parent_link->type != NM_LINK_TYPE_INFINIBAND)
-		return NM_PLATFORM_ERROR_WRONG_TYPE;
+		return -NME_PL_WRONG_TYPE;
 
 	nm_utils_new_infiniband_name (name, parent_link->name, p_key);
 
 	if (add) {
-		plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_INFINIBAND, out_link);
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-			return plerr;
-
-		_LOGD ("link: adding infiniband partition %s for parent '%s' (%d), key %d",
-		       name, parent_link->name, parent, p_key);
-		if (!klass->infiniband_partition_add (self, parent, p_key, out_link))
-			return NM_PLATFORM_ERROR_UNSPECIFIED;
+		r = _link_add_check_existing (self, name, NM_LINK_TYPE_INFINIBAND, out_link);
+		if (r < 0)
+			return r;
+
+		_LOG3D ("link: adding infiniband partition %s, key %d", name, p_key);
+		if (!klass->infiniband_partition_add (self, ifindex, p_key, out_link))
+			return -NME_UNSPEC;
 	} else {
-		_LOGD ("link: deleting infiniband partition %s for parent '%s' (%d), key %d",
-		       name, parent_link->name, parent, p_key);
+		_LOG3D ("link: deleting infiniband partition %s, key %d", name, p_key);
 
-		if (!klass->infiniband_partition_delete (self, parent, p_key))
-			return NM_PLATFORM_ERROR_UNSPECIFIED;
+		if (!klass->infiniband_partition_delete (self, ifindex, p_key))
+			return -NME_UNSPEC;
 	}
 
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return 0;
 }
 
-NMPlatformError
+int
 nm_platform_link_infiniband_add (NMPlatform *self,
                                  int parent,
                                  int p_key,
@@ -2536,7 +2601,7 @@ nm_platform_link_infiniband_add (NMPlatform *self,
 	return _infiniband_add_add_or_delete (self, parent, p_key, TRUE, out_link);
 }
 
-NMPlatformError
+int
 nm_platform_link_infiniband_delete (NMPlatform *self,
                                    int parent,
                                    int p_key)
@@ -2613,30 +2678,29 @@ nm_platform_link_infiniband_get_properties (NMPlatform *self,
  *
  * Create an IPv6 tunnel.
  */
-NMPlatformError
+int
 nm_platform_link_ip6tnl_add (NMPlatform *self,
                              const char *name,
                              const NMPlatformLnkIp6Tnl *props,
                              const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (!props->is_gre, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
+	g_return_val_if_fail (!props->is_gre, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_IP6TNL, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_IP6TNL, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
 
 	if (!klass->link_ip6tnl_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2648,35 +2712,34 @@ nm_platform_link_ip6tnl_add (NMPlatform *self,
  *
  * Create an IPv6 GRE/GRETAP tunnel.
  */
-NMPlatformError
+int
 nm_platform_link_ip6gre_add (NMPlatform *self,
                              const char *name,
                              const NMPlatformLnkIp6Tnl *props,
                              const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (props->is_gre, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
+	g_return_val_if_fail (props->is_gre, -NME_BUG);
 
-	plerr = _link_add_check_existing (self,
-	                                  name,
-	                                  props->is_tap
-	                                      ? NM_LINK_TYPE_IP6GRETAP
-	                                      : NM_LINK_TYPE_IP6GRE,
-	                                  out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self,
+	                              name,
+	                              props->is_tap
+	                                  ? NM_LINK_TYPE_IP6GRETAP
+	                                  : NM_LINK_TYPE_IP6GRE,
+	                              out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
 
 	if (!klass->link_ip6gre_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2688,29 +2751,28 @@ nm_platform_link_ip6gre_add (NMPlatform *self,
  *
  * Create an IPIP tunnel.
  */
-NMPlatformError
+int
 nm_platform_link_ipip_add (NMPlatform *self,
                            const char *name,
                            const NMPlatformLnkIpIp *props,
                            const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_IPIP, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_IPIP, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_ipip_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_ipip_to_string (props, NULL, 0));
 
 	if (!klass->link_ipip_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2723,30 +2785,29 @@ nm_platform_link_ipip_add (NMPlatform *self,
  *
  * Create a MACsec interface.
  */
-NMPlatformError
+int
 nm_platform_link_macsec_add (NMPlatform *self,
                              const char *name,
                              int parent,
                              const NMPlatformLnkMacsec *props,
                              const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_MACSEC, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_MACSEC, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_macsec_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_macsec_to_string (props, NULL, 0));
 
 	if (!klass->link_macsec_add (self, name, parent, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2758,33 +2819,32 @@ nm_platform_link_macsec_add (NMPlatform *self,
  *
  * Create a MACVLAN or MACVTAP device.
  */
-NMPlatformError
+int
 nm_platform_link_macvlan_add (NMPlatform *self,
                               const char *name,
                               int parent,
                               const NMPlatformLnkMacvlan *props,
                               const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 	NMLinkType type;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
 	type = props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN;
 
-	plerr = _link_add_check_existing (self, name, type, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, type, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_macvlan_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_macvlan_to_string (props, NULL, 0));
 
 	if (!klass->link_macvlan_add (self, name, parent, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 /**
@@ -2796,29 +2856,28 @@ nm_platform_link_macvlan_add (NMPlatform *self,
  *
  * Create a software SIT device.
  */
-NMPlatformError
+int
 nm_platform_link_sit_add (NMPlatform *self,
                           const char *name,
                           const NMPlatformLnkSit *props,
                           const NMPlatformLink **out_link)
 {
-	NMPlatformError plerr;
+	int r;
 
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (name, -NME_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_SIT, out_link);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
-		return plerr;
+	r = _link_add_check_existing (self, name, NM_LINK_TYPE_SIT, out_link);
+	if (r < 0)
+		return r;
 
-	_LOGD ("adding link '%s': %s",
-	       name, nm_platform_lnk_sit_to_string (props, NULL, 0));
+	_LOG2D ("adding link %s", nm_platform_lnk_sit_to_string (props, NULL, 0));
 
 	if (!klass->link_sit_add (self, name, props, out_link))
-		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return NM_PLATFORM_ERROR_SUCCESS;
+		return -NME_UNSPEC;
+	return 0;
 }
 
 gboolean
@@ -2826,12 +2885,11 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe
 {
 	const NMPlatformLink *plink;
 	int peer_ifindex;
-	_CHECK_SELF (self, klass, FALSE);
 
 	plink = nm_platform_link_get (self, ifindex);
-
 	if (!plink)
 		return FALSE;
+
 	if (plink->type != NM_LINK_TYPE_VETH)
 		return FALSE;
 
@@ -2859,7 +2917,7 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe
  * 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
+ * @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
@@ -2889,14 +2947,11 @@ nm_platform_link_tun_get_properties (NMPlatform *self,
 	gint64 group;
 	gint64 flags;
 
-	_CHECK_SELF (self, klass, FALSE);
-
-	g_return_val_if_fail (ifindex > 0, FALSE);
-
 	/* 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;
 
@@ -3149,6 +3204,16 @@ nm_platform_wpan_set_short_addr (NMPlatform *self, int ifindex, guint16 short_ad
 	return klass->wpan_set_short_addr (self, ifindex, short_addr);
 }
 
+gboolean
+nm_platform_wpan_set_channel (NMPlatform *self, int ifindex, guint8 page, guint8 channel)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wpan_set_channel (self, ifindex, page, channel);
+}
+
 #define TO_STRING_DEV_BUF_SIZE (5+15+1)
 static const char *
 _to_string_dev (NMPlatform *self, int ifindex, char *buf, size_t size)
@@ -3388,7 +3453,7 @@ nm_platform_ip4_address_add (NMPlatform *self,
 		if (label)
 			g_strlcpy (addr.label, label, sizeof (addr.label));
 
-		_LOGD ("address: adding or updating IPv4 address: %s", nm_platform_ip4_address_to_string (&addr, NULL, 0));
+		_LOG3D ("address: adding or updating IPv4 address: %s", nm_platform_ip4_address_to_string (&addr, NULL, 0));
 	}
 	return klass->ip4_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, flags, label);
 }
@@ -3422,7 +3487,7 @@ nm_platform_ip6_address_add (NMPlatform *self,
 		addr.preferred = preferred;
 		addr.n_ifa_flags = flags;
 
-		_LOGD ("address: adding or updating IPv6 address: %s", nm_platform_ip6_address_to_string (&addr, NULL, 0));
+		_LOG3D ("address: adding or updating IPv6 address: %s", nm_platform_ip6_address_to_string (&addr, NULL, 0));
 	}
 	return klass->ip6_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, flags);
 }
@@ -3431,20 +3496,24 @@ gboolean
 nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address)
 {
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
-	char str_peer2[NM_UTILS_INET_ADDRSTRLEN];
-	char str_peer[100];
+	char b1[NM_UTILS_INET_ADDRSTRLEN];
+	char b2[NM_UTILS_INET_ADDRSTRLEN];
+	char str_peer[INET_ADDRSTRLEN + 50];
 
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (plen <= 32, FALSE);
 
-	_LOGD ("address: deleting IPv4 address %s/%d, %sifindex %d%s",
-	       nm_utils_inet4_ntop (address, NULL), plen,
-	       peer_address != address
-	           ? nm_sprintf_buf (str_peer, "peer %s, ", nm_utils_inet4_ntop (peer_address, str_peer2)) : "",
-	       ifindex,
-	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
+	_LOG3D ("address: deleting IPv4 address %s/%d, %s%s",
+	        nm_utils_inet4_ntop (address, b1),
+	        plen,
+	        peer_address != address
+	            ? nm_sprintf_buf (str_peer,
+	                              "peer %s, ",
+	                              nm_utils_inet4_ntop (peer_address, b2))
+	            : "",
+	        _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
 	return klass->ip4_address_delete (self, ifindex, address, plen, peer_address);
 }
 
@@ -3452,15 +3521,16 @@ gboolean
 nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen)
 {
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (plen <= 128, FALSE);
 
-	_LOGD ("address: deleting IPv6 address %s/%d, ifindex %d%s",
-	       nm_utils_inet6_ntop (&address, NULL), plen, ifindex,
-	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
+	_LOG3D ("address: deleting IPv6 address %s/%d, %s",
+	        nm_utils_inet6_ntop (&address, sbuf), plen,
+	        _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
 	return klass->ip6_address_delete (self, ifindex, address, plen);
 }
 
@@ -3712,7 +3782,7 @@ ip4_addr_subnets_is_secondary (const NMPObject *address,
  *   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.
+ *   telling which addresses were successfully added.
  *   Addresses are removed by unrefing the instance via nmp_object_unref()
  *   and leaving a NULL tombstone.
  *
@@ -3886,7 +3956,7 @@ ip6_address_scope_cmp (gconstpointer a, gconstpointer b)
  *   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.
+ *   telling which addresses were successfully 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.
@@ -4172,7 +4242,7 @@ nm_platform_ip_route_get_prune_list (NMPlatform *self,
  *   at the end of the operation. Note that if @routes contains
  *   the same route, then it will not be deleted. @routes overrules
  *   @routes_prune list.
- * @out_temporary_not_available: (allow-none): (out): routes that could
+ * @out_temporary_not_available: (allow-none) (out): routes that could
  *   currently not be synced. The caller shall keep them and try later again.
  *
  * Returns: %TRUE on success.
@@ -4194,7 +4264,6 @@ nm_platform_ip_route_sync (NMPlatform *self,
 	gboolean success = TRUE;
 	char sbuf1[sizeof (_nm_utils_to_string_buffer)];
 	char sbuf2[sizeof (_nm_utils_to_string_buffer)];
-	char sbuf_err[60];
 
 	nm_assert (NM_IS_PLATFORM (self));
 	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
@@ -4206,7 +4275,7 @@ 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, plerr2;
+			int r, r2;
 			gboolean gateway_route_added = FALSE;
 
 			conf_o = routes->pdata[i];
@@ -4228,8 +4297,8 @@ nm_platform_ip_route_sync (NMPlatform *self,
 				                               (GEqualFunc) nmp_object_id_equal);
 			}
 			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)));
+				_LOG3D ("route-sync: skip adding duplicate route %s",
+				        nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)));
 				continue;
 			}
 
@@ -4246,7 +4315,7 @@ nm_platform_ip_route_sync (NMPlatform *self,
 				                   NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) == 0)
 					continue;
 
-				/* we need to replace the existing route with a (slightly) differnt
+				/* we need to replace the existing route with a (slightly) different
 				 * one. Delete it first. */
 				if (!nm_platform_object_delete (self, plat_o)) {
 					/* ignore error. */
@@ -4254,12 +4323,12 @@ 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,
-			                                  conf_o);
-			if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
-				if (-((int) plerr) == EEXIST) {
+			r = nm_platform_ip_route_add (self,
+			                                NMP_NLM_FLAG_APPEND
+			                              | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
+			                              conf_o);
+			if (r < 0) {
+				if (r == -EEXIST) {
 					/* Don't fail for EEXIST. It's not clear that the existing route
 					 * is identical to the one that we were about to add. However,
 					 * above we should have deleted conflicting (non-identical) routes. */
@@ -4268,92 +4337,92 @@ sync_route_add:
 						                                       NMP_CACHE_ID_TYPE_OBJECT_TYPE,
 						                                       conf_o);
 						if (!plat_entry) {
-							_LOGD ("route-sync: adding route %s failed with EEXIST, however we cannot find such a route",
-							       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)));
+							_LOG3D ("route-sync: adding route %s failed with EEXIST, however we cannot find such a route",
+							        nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)));
 						} else if (vt->route_cmp (NMP_OBJECT_CAST_IPX_ROUTE (conf_o),
 						                          NMP_OBJECT_CAST_IPX_ROUTE (plat_entry->obj),
 						                          NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) != 0) {
-							_LOGD ("route-sync: adding route %s failed due to existing (different!) route %s",
-							       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-							       nmp_object_to_string (plat_entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
+							_LOG3D ("route-sync: adding route %s failed due to existing (different!) route %s",
+							        nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+							        nmp_object_to_string (plat_entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
 						}
 					}
 				} else if (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",
+					_LOG3D ("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
+					       nm_strerror (r));
+				} else if (   r == -EINVAL
 				           && out_temporary_not_available
 				           && _err_inval_due_to_ipv6_tentative_pref_src (self, conf_o)) {
-					_LOGD ("route-sync: ignore failure to add IPv6 route with tentative IPv6 pref-src: %s: %s",
-					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
-					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
+					_LOG3D ("route-sync: ignore failure to add IPv6 route with tentative IPv6 pref-src: %s: %s",
+					        nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+					        nm_strerror (r));
 					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 (   !gateway_route_added
-				           && (   (   -((int) plerr) == ENETUNREACH
+				           && (   (   r == -ENETUNREACH
 				                   && vt->is_ip4
 				                   && !!NMP_OBJECT_CAST_IP4_ROUTE (conf_o)->gateway)
-				               || (   -((int) plerr) == EHOSTUNREACH
+				               || (   r == -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);
+						const NMPlatformIP4Route *rt = NMP_OBJECT_CAST_IP4_ROUTE (conf_o);
 
 						nmp_object_stackinit (&oo,
 						                      NMP_OBJECT_TYPE_IP4_ROUTE,
 						                      &((NMPlatformIP4Route) {
-						                          .ifindex = r->ifindex,
-						                          .network = r->gateway,
+						                          .ifindex = rt->ifindex,
+						                          .network = rt->gateway,
 						                          .plen = 32,
-						                          .metric = r->metric,
-						                          .rt_source = r->rt_source,
-						                          .table_coerced = r->table_coerced,
+						                          .metric = rt->metric,
+						                          .rt_source = rt->rt_source,
+						                          .table_coerced = rt->table_coerced,
 						                      }));
 					} else {
-						const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (conf_o);
+						const NMPlatformIP6Route *rt = NMP_OBJECT_CAST_IP6_ROUTE (conf_o);
 
 						nmp_object_stackinit (&oo,
 						                      NMP_OBJECT_TYPE_IP6_ROUTE,
 						                      &((NMPlatformIP6Route) {
-						                          .ifindex = r->ifindex,
-						                          .network = r->gateway,
+						                          .ifindex = rt->ifindex,
+						                          .network = rt->gateway,
 						                          .plen = 128,
-						                          .metric = r->metric,
-						                          .rt_source = r->rt_source,
-						                          .table_coerced = r->table_coerced,
+						                          .metric = rt->metric,
+						                          .rt_source = rt->rt_source,
+						                          .table_coerced = rt->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)),
-					       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)));
+					_LOG3D ("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_strerror (r),
+					        nmp_object_to_string (&oo, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
+
+					r2 = nm_platform_ip_route_add (self,
+					                                 NMP_NLM_FLAG_APPEND
+					                               | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
+					                               &oo);
+
+					if (r2 < 0) {
+						_LOG3D ("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_strerror (r2));
 					}
 
 					gateway_route_added = TRUE;
 					goto sync_route_add;
 				} else {
-					_LOGW ("route-sync: failure to add IPv%c route: %s: %s",
+					_LOG3W ("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)));
+					       nm_strerror (r));
 					success = FALSE;
 				}
 			}
@@ -4495,30 +4564,32 @@ nm_platform_ip_route_normalize (int addr_family,
 	}
 }
 
-static NMPlatformError
+static int
 _ip_route_add (NMPlatform *self,
                NMPNlmFlags flags,
                int addr_family,
                gconstpointer route)
 {
 	char sbuf[sizeof (_nm_utils_to_string_buffer)];
+	int ifindex;
 
 	_CHECK_SELF (self, klass, FALSE);
 
 	nm_assert (route);
 	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	_LOGD ("route: %-10s IPv%c route: %s",
-	       _nmp_nlm_flag_to_string (flags & NMP_NLM_FLAG_FMASK),
-	       nm_utils_addr_family_to_char (addr_family),
-	       addr_family == AF_INET
-	         ? nm_platform_ip4_route_to_string (route, sbuf, sizeof (sbuf))
-	         : nm_platform_ip6_route_to_string (route, sbuf, sizeof (sbuf)));
+	ifindex = ((NMPlatformObject *)route)->ifindex;
+	_LOG3D ("route: %-10s IPv%c route: %s",
+	        _nmp_nlm_flag_to_string (flags & NMP_NLM_FLAG_FMASK),
+	        nm_utils_addr_family_to_char (addr_family),
+	        addr_family == AF_INET
+	          ? nm_platform_ip4_route_to_string (route, sbuf, sizeof (sbuf))
+	          : nm_platform_ip6_route_to_string (route, sbuf, sizeof (sbuf)));
 
 	return klass->ip_route_add (self, flags, addr_family, route);
 }
 
-NMPlatformError
+int
 nm_platform_ip_route_add (NMPlatform *self,
                           NMPNlmFlags flags,
                           const NMPObject *route)
@@ -4539,7 +4610,7 @@ nm_platform_ip_route_add (NMPlatform *self,
 	return _ip_route_add (self, flags, addr_family, NMP_OBJECT_CAST_IP_ROUTE (route));
 }
 
-NMPlatformError
+int
 nm_platform_ip4_route_add (NMPlatform *self,
                            NMPNlmFlags flags,
                            const NMPlatformIP4Route *route)
@@ -4547,7 +4618,7 @@ nm_platform_ip4_route_add (NMPlatform *self,
 	return _ip_route_add (self, flags, AF_INET, route);
 }
 
-NMPlatformError
+int
 nm_platform_ip6_route_add (NMPlatform *self,
                            NMPNlmFlags flags,
                            const NMPlatformIP6Route *route)
@@ -4559,6 +4630,7 @@ gboolean
 nm_platform_object_delete (NMPlatform *self,
                            const NMPObject *obj)
 {
+	int ifindex = obj->object.ifindex;
 	_CHECK_SELF (self, klass, FALSE);
 
 	if (!NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
@@ -4567,16 +4639,16 @@ nm_platform_object_delete (NMPlatform *self,
 	                                           NMP_OBJECT_TYPE_TFILTER))
 		g_return_val_if_reached (FALSE);
 
-	_LOGD ("%s: delete %s",
-	       NMP_OBJECT_GET_CLASS (obj)->obj_type_name,
-	       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+	_LOG3D ("%s: delete %s",
+	        NMP_OBJECT_GET_CLASS (obj)->obj_type_name,
+	        nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
 
 	return klass->object_delete (self, obj);
 }
 
 /*****************************************************************************/
 
-NMPlatformError
+int
 nm_platform_ip_route_get (NMPlatform *self,
                           int addr_family,
                           gconstpointer address /* in_addr_t or struct in6_addr */,
@@ -4584,16 +4656,15 @@ nm_platform_ip_route_get (NMPlatform *self,
                           NMPObject **out_route)
 {
 	nm_auto_nmpobj NMPObject *route = NULL;
-	NMPlatformError result;
+	int result;
 	char buf[NM_UTILS_INET_ADDRSTRLEN];
-	char buf_err[200];
 	char buf_oif[64];
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (address, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (address, -NME_BUG);
 	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET,
-	                                              AF_INET6), NM_PLATFORM_ERROR_BUG);
+	                                              AF_INET6), -NME_BUG);
 
 	_LOGT ("route: get IPv%c route for: %s%s",
 	       nm_utils_addr_family_to_char (addr_family),
@@ -4601,7 +4672,7 @@ nm_platform_ip_route_get (NMPlatform *self,
 	       oif_ifindex > 0 ? nm_sprintf_buf (buf_oif, " oif %d", oif_ifindex) : "");
 
 	if (!klass->ip_route_get)
-		result = NM_PLATFORM_ERROR_OPNOTSUPP;
+		result = -NME_PL_OPNOTSUPP;
 	else {
 		result = klass->ip_route_get (self,
 		                              addr_family,
@@ -4610,12 +4681,12 @@ nm_platform_ip_route_get (NMPlatform *self,
 		                              &route);
 	}
 
-	if (result != NM_PLATFORM_ERROR_SUCCESS) {
+	if (result < 0) {
 		nm_assert (!route);
 		_LOGW ("route: get IPv%c route for: %s failed with %s",
 		       nm_utils_addr_family_to_char (addr_family),
 		       inet_ntop (addr_family, address, buf, sizeof (buf)),
-		       nm_platform_error_to_string (result, buf_err, sizeof (buf_err)));
+		       nm_strerror (result));
 	} else {
 		nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (route), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
 		nm_assert (!NMP_OBJECT_IS_STACKINIT (route));
@@ -4808,7 +4879,7 @@ _ip4_dev_route_blacklist_schedule (NMPlatform *self)
  * route, however it has a wrong metric of zero. We add our own device route (with
  * proper metric), but need to delete the route that kernel adds.
  *
- * The problem is, that kernel does not immidiately add the route, when adding
+ * The problem is, that kernel does not immediately add the route, when adding
  * the address. It only shows up some time later. So, we register here a list
  * of blacklisted routes, and when they show up within a time out, we assume it's
  * the kernel generated one, and we delete it.
@@ -4899,14 +4970,15 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 
 /*****************************************************************************/
 
-NMPlatformError
+int
 nm_platform_qdisc_add (NMPlatform *self,
                        NMPNlmFlags flags,
                        const NMPlatformQdisc *qdisc)
 {
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	int ifindex = qdisc->ifindex;
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	_LOGD ("adding or updating a qdisc: %s", nm_platform_qdisc_to_string (qdisc, NULL, 0));
+	_LOG3D ("adding or updating a qdisc: %s", nm_platform_qdisc_to_string (qdisc, NULL, 0));
 	return klass->qdisc_add (self, flags, qdisc);
 }
 
@@ -4955,7 +5027,7 @@ nm_platform_qdisc_sync (NMPlatform *self,
 			const NMPObject *q = g_ptr_array_index (known_qdiscs, i);
 
 			success &= (nm_platform_qdisc_add (self, NMP_NLM_FLAG_ADD,
-			                                   NMP_OBJECT_CAST_QDISC (q)) == NM_PLATFORM_ERROR_SUCCESS);
+			                                   NMP_OBJECT_CAST_QDISC (q)) >= 0);
 		}
 	}
 
@@ -4964,14 +5036,15 @@ nm_platform_qdisc_sync (NMPlatform *self,
 
 /*****************************************************************************/
 
-NMPlatformError
+int
 nm_platform_tfilter_add (NMPlatform *self,
                          NMPNlmFlags flags,
                          const NMPlatformTfilter *tfilter)
 {
-	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+	int ifindex = tfilter->ifindex;
+	_CHECK_SELF (self, klass, -NME_BUG);
 
-	_LOGD ("adding or updating a tfilter: %s", nm_platform_tfilter_to_string (tfilter, NULL, 0));
+	_LOG3D ("adding or updating a tfilter: %s", nm_platform_tfilter_to_string (tfilter, NULL, 0));
 	return klass->tfilter_add (self, flags, tfilter);
 }
 
@@ -5020,7 +5093,7 @@ nm_platform_tfilter_sync (NMPlatform *self,
 			const NMPObject *q = g_ptr_array_index (known_tfilters, i);
 
 			success &= (nm_platform_tfilter_add (self, NMP_NLM_FLAG_ADD,
-			                                     NMP_OBJECT_CAST_TFILTER (q)) == NM_PLATFORM_ERROR_SUCCESS);
+			                                     NMP_OBJECT_CAST_TFILTER (q)) >= 0);
 		}
 	}
 
@@ -5478,6 +5551,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 	char str_dst_port[25];
 	char str_tos[25];
 	char str_ttl[25];
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
 		return buf;
@@ -5488,7 +5562,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		g_snprintf (str_group, sizeof (str_group),
 		            " %s %s",
 		            IN_MULTICAST (ntohl (lnk->group)) ? "group" : "remote",
-		            nm_utils_inet4_ntop (lnk->group, NULL));
+		            nm_utils_inet4_ntop (lnk->group, sbuf));
 	}
 	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->group6))
 		str_group6[0] = '\0';
@@ -5497,7 +5571,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		            " %s%s %s",
 		            IN6_IS_ADDR_MULTICAST (&lnk->group6) ? "group" : "remote",
 		            str_group[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */
-		            nm_utils_inet6_ntop (&lnk->group6, NULL));
+		            nm_utils_inet6_ntop (&lnk->group6, sbuf));
 	}
 
 	if (lnk->local == 0)
@@ -5505,7 +5579,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 	else {
 		g_snprintf (str_local, sizeof (str_local),
 		            " local %s",
-		            nm_utils_inet4_ntop (lnk->local, NULL));
+		            nm_utils_inet4_ntop (lnk->local, sbuf));
 	}
 	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->local6))
 		str_local6[0] = '\0';
@@ -5513,7 +5587,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		g_snprintf (str_local6, sizeof (str_local6),
 		            " local%s %s",
 		            str_local[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */
-		            nm_utils_inet6_ntop (&lnk->local6, NULL));
+		            nm_utils_inet6_ntop (&lnk->local6, sbuf));
 	}
 
 	g_snprintf (buf, len,
@@ -5555,42 +5629,45 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 const char *
 nm_platform_wireguard_peer_to_string (const NMPWireGuardPeer *peer, char *buf, gsize len)
 {
+	char *buf0 = buf;
 	gs_free char *public_key_b64 = NULL;
-	char s_endpoint[NM_UTILS_INET_ADDRSTRLEN + 100];
+	char s_sockaddr[NM_UTILS_INET_ADDRSTRLEN + 100];
+	char s_endpoint[20 + sizeof (s_sockaddr)];
 	char s_addr[NM_UTILS_INET_ADDRSTRLEN];
+	char s_keepalive[100];
 	guint i;
 
 	nm_utils_to_string_buffer_init (&buf, &len);
 
-	if (peer->endpoint_family == AF_INET) {
-		nm_sprintf_buf (s_endpoint,
-		                " endpoint %s:%u",
-		                nm_utils_inet4_ntop (peer->endpoint_addr.addr4, s_addr),
-		                (guint) peer->endpoint_port);
-	} else if (peer->endpoint_family == AF_INET6) {
+	public_key_b64 = g_base64_encode (peer->public_key, sizeof (peer->public_key));
+
+	if (peer->endpoint.sa.sa_family != AF_UNSPEC) {
 		nm_sprintf_buf (s_endpoint,
-		                " endpoint [%s]:%u",
-		                nm_utils_inet6_ntop (&peer->endpoint_addr.addr6, s_addr),
-		                (guint) peer->endpoint_port);
+		                " endpoint %s",
+		                nm_sock_addr_union_to_string (&peer->endpoint,
+		                                              s_sockaddr,
+		                                              sizeof (s_sockaddr)));
 	} else
 		s_endpoint[0] = '\0';
 
-	public_key_b64 = g_base64_encode (peer->public_key, sizeof (peer->public_key));
-
 	nm_utils_strbuf_append (&buf, &len,
 	                        "public-key %s"
-	                        "%s" /* preshared-key */
-	                        "%s" /* endpoint */
+	                        "%s"  /* preshared-key */
+	                        "%s"  /* endpoint */
 	                        " rx %"G_GUINT64_FORMAT
 	                        " tx %"G_GUINT64_FORMAT
+	                        "%s"  /* persistent-keepalive */
 	                        "%s", /* allowed-ips */
 	                        public_key_b64,
-	                        nm_utils_mem_all_zero (peer->preshared_key, sizeof (peer->preshared_key))
+	                        nm_utils_memeqzero_secret (peer->preshared_key, sizeof (peer->preshared_key))
 	                          ? ""
 	                          : " preshared-key (hidden)",
 	                        s_endpoint,
 	                        peer->rx_bytes,
 	                        peer->tx_bytes,
+	                        peer->persistent_keepalive_interval > 0
+	                          ? nm_sprintf_buf (s_keepalive, " keepalive %u", (guint) peer->persistent_keepalive_interval)
+	                          : "",
 	                        peer->allowed_ips_len > 0
 	                          ? " allowed-ips"
 	                          : "");
@@ -5604,7 +5681,7 @@ nm_platform_wireguard_peer_to_string (const NMPWireGuardPeer *peer, char *buf, g
 		                        allowed_ip->mask);
 	}
 
-	return buf;
+	return buf0;
 }
 
 const char *
@@ -5615,7 +5692,7 @@ nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireGuard *lnk, char *bu
 	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
 		return buf;
 
-	if (!nm_utils_mem_all_zero (lnk->public_key, sizeof (lnk->public_key)))
+	if (!nm_utils_memeqzero (lnk->public_key, sizeof (lnk->public_key)))
 		public_b64 = g_base64_encode (lnk->public_key, sizeof (lnk->public_key));
 
 	g_snprintf (buf, len,
@@ -5628,7 +5705,7 @@ nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireGuard *lnk, char *bu
 	              ? " public-key "
 	              : "",
 	            public_b64 ?: "",
-	            nm_utils_mem_all_zero (lnk->private_key, sizeof (lnk->private_key))
+	            nm_utils_memeqzero_secret (lnk->private_key, sizeof (lnk->private_key))
 	              ? ""
 	              : " private-key (hidden)",
 	            lnk->listen_port,
@@ -5927,13 +6004,21 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 const char *
 nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsize len)
 {
-	char s_network[INET6_ADDRSTRLEN], s_gateway[INET6_ADDRSTRLEN], s_pref_src[INET6_ADDRSTRLEN];
-	char s_src_all[INET6_ADDRSTRLEN + 40], s_src[INET6_ADDRSTRLEN];
+	char s_network[INET6_ADDRSTRLEN];
+	char s_gateway[INET6_ADDRSTRLEN];
+	char s_pref_src[INET6_ADDRSTRLEN];
+	char s_src_all[INET6_ADDRSTRLEN + 40];
+	char s_src[INET6_ADDRSTRLEN];
 	char str_table[30];
 	char str_pref[40];
 	char str_pref2[30];
-	char str_dev[TO_STRING_DEV_BUF_SIZE], s_source[50];
-	char str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32];
+	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	char s_source[50];
+	char str_window[32];
+	char str_cwnd[32];
+	char str_initcwnd[32];
+	char str_initrwnd[32];
+	char str_mtu[32];
 	char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN];
 
 	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
@@ -6983,7 +7068,7 @@ nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPlatfor
 		/* if the lifetime is equal, compare the preferred time. */
 		ta = tb = 0;
 
-		if (a->preferred == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0 /* liftime==0 means permanent! */)
+		if (a->preferred == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0 /* lifetime==0 means permanent! */)
 			ta = G_MAXINT64;
 		else if (a->timestamp)
 			ta = ((gint64) a->timestamp) + a->preferred;
@@ -7018,44 +7103,43 @@ nm_platform_signal_change_type_to_string (NMPlatformSignalChangeType change_type
 static void
 log_link (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformLink *device, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-
-	_LOGD ("signal: link %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_link_to_string (device, NULL, 0));
+	_LOG3D ("signal: link %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_link_to_string (device, NULL, 0));
 }
 
 static void
 log_ip4_address (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP4Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: address 4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_address_to_string (address, NULL, 0));
+	_LOG3D ("signal: address 4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_address_to_string (address, NULL, 0));
 }
 
 static void
 log_ip6_address (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP6Address *address, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: address 6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_address_to_string (address, NULL, 0));
+	_LOG3D ("signal: address 6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_address_to_string (address, NULL, 0));
 }
 
 static void
 log_ip4_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP4Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: route   4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_route_to_string (route, NULL, 0));
+	_LOG3D ("signal: route   4 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip4_route_to_string (route, NULL, 0));
 }
 
 static void
 log_ip6_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformIP6Route *route, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: route   6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_route_to_string (route, NULL, 0));
+	_LOG3D ("signal: route   6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_route_to_string (route, NULL, 0));
 }
 
 static void
 log_qdisc (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformQdisc *qdisc, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: qdisc %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_qdisc_to_string (qdisc, NULL, 0));
+	_LOG3D ("signal: qdisc %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_qdisc_to_string (qdisc, NULL, 0));
 }
 
 static void
 log_tfilter (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformTfilter *tfilter, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
-	_LOGD ("signal: tfilter %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_tfilter_to_string (tfilter, NULL, 0));
+	_LOG3D ("signal: tfilter %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_tfilter_to_string (tfilter, NULL, 0));
 }
 
 /*****************************************************************************/
@@ -7070,6 +7154,7 @@ nm_platform_cache_update_emit_signal (NMPlatform *self,
 	gboolean visible_old;
 	const NMPObject *o;
 	const NMPClass *klass;
+	int ifindex;
 
 	nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, NM_PLATFORM_SIGNAL_NONE,
 	                                                             NM_PLATFORM_SIGNAL_ADDED,
@@ -7111,6 +7196,7 @@ nm_platform_cache_update_emit_signal (NMPlatform *self,
 		return;
 	}
 
+	ifindex = o->object.ifindex;
 	klass = NMP_OBJECT_GET_CLASS (o);
 
 	if (   klass->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE
@@ -7118,10 +7204,10 @@ nm_platform_cache_update_emit_signal (NMPlatform *self,
 	    && NM_IN_SET (cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED))
 		_ip4_dev_route_blacklist_notify_route (self, o);
 
-	_LOGt ("emit signal %s %s: %s",
-	       klass->signal_type,
-	       nm_platform_signal_change_type_to_string ((NMPlatformSignalChangeType) cache_op),
-	       nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+	_LOG3t ("emit signal %s %s: %s",
+	        klass->signal_type,
+	        nm_platform_signal_change_type_to_string ((NMPlatformSignalChangeType) cache_op),
+	        nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
 
 	nmp_object_ref (o);
 	g_signal_emit (self,
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 7e91f1f3..37aa58fd 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -53,6 +53,8 @@
 
 /*****************************************************************************/
 
+struct _NMPWireGuardPeer;
+
 struct udev_device;
 
 typedef gboolean (*NMPObjectPredicateFunc) (const NMPObject *obj,
@@ -149,29 +151,6 @@ typedef enum {
 
 } NMPlatformIPRouteCmpType;
 
-typedef enum { /*< skip >*/
-
-	/* dummy value, to enforce that the enum type is signed and has a size
-	 * to hold an integer. We want to encode errno from <errno.h> as negative
-	 * values. */
-	_NM_PLATFORM_ERROR_MININT = G_MININT,
-
-	NM_PLATFORM_ERROR_SUCCESS = 0,
-
-	NM_PLATFORM_ERROR_BUG,
-
-	NM_PLATFORM_ERROR_UNSPECIFIED,
-
-	NM_PLATFORM_ERROR_NOT_FOUND,
-	NM_PLATFORM_ERROR_EXISTS,
-	NM_PLATFORM_ERROR_WRONG_TYPE,
-	NM_PLATFORM_ERROR_NOT_SLAVE,
-	NM_PLATFORM_ERROR_NO_FIRMWARE,
-	NM_PLATFORM_ERROR_OPNOTSUPP,
-	NM_PLATFORM_ERROR_NETLINK,
-	NM_PLATFORM_ERROR_CANT_SET_MTU,
-} NMPlatformError;
-
 typedef enum {
 
 	/* match-flags are strictly inclusive. That means,
@@ -299,10 +278,10 @@ struct _NMPlatformObject {
 	 *   are permanent. This rule is so that unset addresses (calloc) are permanent by default.
 	 * 2 @lifetime==@preferred==NM_PLATFORM_LIFETIME_PERMANENT: @timestamp is irrelevant (but mostly
 	 *   set to 0). Such addresses are permanent.
-	 * 3 Non permanent addreses should (almost) always have @timestamp > 0. 0 is not a valid timestamp
+	 * 3 Non permanent addresses should (almost) always have @timestamp > 0. 0 is not a valid timestamp
 	 *   and never returned by nm_utils_get_monotonic_timestamp_s(). In this case @valid/@preferred
 	 *   is anchored at @timestamp.
-	 * 4 Non permanent addresses with @timestamp == 0 are implicitely anchored at *now*, thus the time
+	 * 4 Non permanent addresses with @timestamp == 0 are implicitly anchored at *now*, thus the time
 	 *   moves as time goes by. This is usually not useful, except e.g. nm_platform_ip[46]_address_add().
 	 *
 	 * Non permanent addresses from DHCP/RA might have the @timestamp set to the moment of when the
@@ -417,7 +396,7 @@ typedef union {
 	 * On the other hand, for IPv6 you cannot add two IPv6 routes that only differ
 	 * by an RTA_METRICS property.
 	 *
-	 * When deleting a route, kernel seems to ignore the RTA_METRICS propeties.
+	 * When deleting a route, kernel seems to ignore the RTA_METRICS properties.
 	 * That is a problem/bug for IPv4 because you cannot explicitly select which
 	 * route to delete. Kernel just picks the first. See rh#1475642. */ \
 	\
@@ -769,11 +748,35 @@ typedef enum {
 } NMPlatformLinkDuplexType;
 
 typedef enum {
-	NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS               = (1LL <<  0),
-	NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL                      = (1LL <<  1),
-	NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF                         = (1LL <<  2),
+	NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS                 = (1LL <<  0),
+	NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL                        = (1LL <<  1),
+	NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF                           = (1LL <<  2),
 } NMPlatformKernelSupportFlags;
 
+typedef enum {
+	NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE                        = 0,
+	NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS               = (1LL << 0),
+	NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY             = (1LL << 1),
+	NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT             = (1LL << 2),
+	NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK                  = (1LL << 3),
+} NMPlatformWireGuardChangeFlags;
+
+typedef enum {
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE                   = 0,
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME              = (1LL << 0),
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY      = (1LL << 1),
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL = (1LL << 2),
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT           = (1LL << 3),
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS         = (1LL << 4),
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS     = (1LL << 5),
+
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_DEFAULT =   NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY
+	                                                 | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL
+	                                                 | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT
+	                                                 | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS,
+
+} NMPlatformWireGuardChangePeerFlags;
+
 /*****************************************************************************/
 
 struct _NMPlatformPrivate;
@@ -792,13 +795,14 @@ typedef struct {
 
 	void (*refresh_all) (NMPlatform *self, NMPObjectType obj_type);
 
-	gboolean (*link_add) (NMPlatform *,
-	                      const char *name,
-	                      NMLinkType type,
-	                      const char *veth_peer,
-	                      const void *address,
-	                      size_t address_len,
-	                      const NMPlatformLink **out_link);
+	int (*link_add) (NMPlatform *,
+	                 const char *name,
+	                 NMLinkType type,
+	                 const char *veth_peer,
+	                 const void *address,
+	                 size_t address_len,
+	                 const NMPlatformLink **out_link);
+
 	gboolean (*link_delete) (NMPlatform *, int ifindex);
 
 	gboolean (*link_refresh) (NMPlatform *, int ifindex);
@@ -815,15 +819,15 @@ typedef struct {
 	const char *(*link_get_udi) (NMPlatform *self, int ifindex);
 	struct udev_device *(*link_get_udev_device) (NMPlatform *self, int ifindex);
 
-	NMPlatformError (*link_set_user_ipv6ll_enabled) (NMPlatform *, int ifindex, gboolean enabled);
+	int (*link_set_user_ipv6ll_enabled) (NMPlatform *, int ifindex, gboolean enabled);
 	gboolean (*link_set_token) (NMPlatform *, int ifindex, NMUtilsIPv6IfaceId iid);
 
 	gboolean (*link_get_permanent_address) (NMPlatform *,
 	                                        int ifindex,
 	                                        guint8 *buf,
 	                                        size_t *length);
-	NMPlatformError (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
-	NMPlatformError (*link_set_mtu) (NMPlatform *, int ifindex, guint32 mtu);
+	int (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
+	int (*link_set_mtu) (NMPlatform *, int ifindex, guint32 mtu);
 	gboolean (*link_set_name) (NMPlatform *, int ifindex, const char *name);
 	gboolean (*link_set_sriov_params) (NMPlatform *, int ifindex, guint num_vfs, int autoprobe);
 	gboolean (*link_set_sriov_vfs) (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
@@ -846,6 +850,14 @@ typedef struct {
 
 	gboolean (*link_can_assume) (NMPlatform *, int ifindex);
 
+	int (*link_wireguard_change) (NMPlatform *self,
+	                              int ifindex,
+	                              const NMPlatformLnkWireGuard *lnk_wireguard,
+	                              const struct _NMPWireGuardPeer *peers,
+	                              const NMPlatformWireGuardChangePeerFlags *peer_flags,
+	                              guint peers_len,
+	                              NMPlatformWireGuardChangeFlags change_flags);
+
 	gboolean (*vlan_add) (NMPlatform *, const char *name, int parent, int vlanid, guint32 vlanflags, const NMPlatformLink **out_link);
 	gboolean (*link_vlan_change) (NMPlatform *self,
 	                              int ifindex,
@@ -927,6 +939,7 @@ typedef struct {
 	gboolean    (*wpan_set_pan_id)       (NMPlatform *, int ifindex, guint16 pan_id);
 	guint16     (*wpan_get_short_addr)   (NMPlatform *, int ifindex);
 	gboolean    (*wpan_set_short_addr)   (NMPlatform *, int ifindex, guint16 short_addr);
+	gboolean    (*wpan_set_channel)      (NMPlatform *, int ifindex, guint8 page, guint8 channel);
 
 	gboolean (*object_delete) (NMPlatform *, const NMPObject *obj);
 
@@ -950,23 +963,23 @@ typedef struct {
 	gboolean (*ip4_address_delete) (NMPlatform *, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
 	gboolean (*ip6_address_delete) (NMPlatform *, int ifindex, struct in6_addr address, guint8 plen);
 
-	NMPlatformError (*ip_route_add) (NMPlatform *,
-	                                 NMPNlmFlags flags,
-	                                 int addr_family,
-	                                 const NMPlatformIPRoute *route);
-	NMPlatformError (*ip_route_get) (NMPlatform *self,
-	                                 int addr_family,
-	                                 gconstpointer address,
-	                                 int oif_ifindex,
-	                                 NMPObject **out_route);
+	int (*ip_route_add) (NMPlatform *,
+	                     NMPNlmFlags flags,
+	                     int addr_family,
+	                     const NMPlatformIPRoute *route);
+	int (*ip_route_get) (NMPlatform *self,
+	                     int addr_family,
+	                     gconstpointer address,
+	                     int oif_ifindex,
+	                     NMPObject **out_route);
 
-	NMPlatformError (*qdisc_add)   (NMPlatform *self,
-	                                NMPNlmFlags flags,
-	                                const NMPlatformQdisc *qdisc);
+	int (*qdisc_add)   (NMPlatform *self,
+	                    NMPNlmFlags flags,
+	                    const NMPlatformQdisc *qdisc);
 
-	NMPlatformError (*tfilter_add)   (NMPlatform *self,
-	                                  NMPNlmFlags flags,
-	                                  const NMPlatformTfilter *tfilter);
+	int (*tfilter_add)   (NMPlatform *self,
+	                      NMPNlmFlags flags,
+	                      const NMPlatformTfilter *tfilter);
 
 	NMPlatformKernelSupportFlags (*check_kernel_support) (NMPlatform * self,
 	                                                      NMPlatformKernelSupportFlags request_flags);
@@ -1095,18 +1108,19 @@ gboolean nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns);
 
 const char *nm_link_type_to_string (NMLinkType link_type);
 
-const char *nm_platform_error_to_string (NMPlatformError error,
-                                         char *buf,
-                                         gsize buf_len);
-#define nm_platform_error_to_string_a(error) \
-	(nm_platform_error_to_string ((error), g_alloca (30), 30))
-
 #define NMP_SYSCTL_PATHID_ABSOLUTE(path) \
 	((const char *) NULL), -1, (path)
 
 #define NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname, path) \
-	nm_sprintf_bufa (NM_STRLEN ("net:/sys/class/net//\0") + NMP_IFNAMSIZ + strlen (path), \
-	                 "net:/sys/class/net/%s/%s", (ifname), (path)), \
+	nm_sprintf_buf_unsafe_a (  NM_STRLEN ("net:/sys/class/net//\0") \
+	                         + NMP_IFNAMSIZ \
+	                         + ({ \
+	                             const gsize _l = strlen (path); \
+	                             \
+	                             nm_assert (_l < 200); \
+	                             _l; \
+	                            }), \
+	                         "net:/sys/class/net/%s/%s", (ifname), (path)), \
 	(dirfd), (path)
 
 #define NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, path) \
@@ -1120,7 +1134,35 @@ char *nm_platform_sysctl_get (NMPlatform *self, const char *pathid, int dirfd, c
 gint32 nm_platform_sysctl_get_int32 (NMPlatform *self, const char *pathid, int dirfd, const char *path, gint32 fallback);
 gint64 nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *pathid, int dirfd, const char *path, guint base, gint64 min, gint64 max, gint64 fallback);
 
-gboolean nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface, int value);
+char *nm_platform_sysctl_ip_conf_get (NMPlatform *platform,
+                                      int addr_family,
+                                      const char *ifname,
+                                      const char *property);
+
+gint64 nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
+                                                   int addr_family,
+                                                   const char *ifname,
+                                                   const char *property,
+                                                   guint base,
+                                                   gint64 min,
+                                                   gint64 max,
+                                                   gint64 fallback);
+
+gboolean nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
+                                         int addr_family,
+                                         const char *ifname,
+                                         const char *property,
+                                         const char *value);
+
+gboolean nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
+                                               int addr_family,
+                                               const char *ifname,
+                                               const char *property,
+                                               gint64 value);
+
+gboolean nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (NMPlatform *self,
+                                                             const char *iface,
+                                                             int value);
 
 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);
@@ -1135,11 +1177,11 @@ const NMPlatformLink *nm_platform_link_get_by_ifname (NMPlatform *self, const ch
 const NMPlatformLink *nm_platform_link_get_by_address (NMPlatform *self, NMLinkType link_type, gconstpointer address, size_t length);
 
 GPtrArray *nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name);
-NMPlatformError nm_platform_link_dummy_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_bridge_add (NMPlatform *self, const char *name, const void *address, size_t address_len, const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_bond_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_team_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_veth_add (NMPlatform *self, const char *name, const char *peer, const NMPlatformLink **out_link);
+int nm_platform_link_dummy_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
+int nm_platform_link_bridge_add (NMPlatform *self, const char *name, const void *address, size_t address_len, const NMPlatformLink **out_link);
+int nm_platform_link_bond_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
+int nm_platform_link_team_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
+int nm_platform_link_veth_add (NMPlatform *self, const char *name, const char *peer, const NMPlatformLink **out_link);
 
 gboolean nm_platform_link_delete (NMPlatform *self, int ifindex);
 
@@ -1160,7 +1202,7 @@ GPtrArray *nm_platform_lookup_clone (NMPlatform *platform,
                                      NMPObjectPredicateFunc predicate,
                                      gpointer user_data);
 
-/* convienience methods to lookup the link and access fields of NMPlatformLink. */
+/* convenience methods to lookup the link and access fields of NMPlatformLink. */
 int nm_platform_link_get_ifindex (NMPlatform *self, const char *name);
 const char *nm_platform_link_get_name (NMPlatform *self, int ifindex);
 NMLinkType nm_platform_link_get_type (NMPlatform *self, int ifindex);
@@ -1210,12 +1252,12 @@ const char *nm_platform_link_get_udi (NMPlatform *self, int ifindex);
 
 struct udev_device *nm_platform_link_get_udev_device (NMPlatform *self, int ifindex);
 
-NMPlatformError nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled);
+int nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled);
 gboolean nm_platform_link_set_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid);
 
 gboolean nm_platform_link_get_permanent_address (NMPlatform *self, int ifindex, guint8 *buf, size_t *length);
-NMPlatformError nm_platform_link_set_address (NMPlatform *self, int ifindex, const void *address, size_t length);
-NMPlatformError nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu);
+int nm_platform_link_set_address (NMPlatform *self, int ifindex, const void *address, size_t length);
+int nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu);
 gboolean nm_platform_link_set_name (NMPlatform *self, int ifindex, const char *name);
 gboolean nm_platform_link_set_sriov_params (NMPlatform *self, int ifindex, guint num_vfs, int autoprobe);
 gboolean nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
@@ -1259,12 +1301,12 @@ const NMPlatformLnkVlan *nm_platform_link_get_lnk_vlan (NMPlatform *self, int if
 const NMPlatformLnkVxlan *nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkWireGuard *nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 
-NMPlatformError nm_platform_link_vlan_add (NMPlatform *self,
-                                           const char *name,
-                                           int parent,
-                                           int vlanid,
-                                           guint32 vlanflags,
-                                           const NMPlatformLink **out_link);
+int nm_platform_link_vlan_add (NMPlatform *self,
+                               const char *name,
+                               int parent,
+                               int vlanid,
+                               guint32 vlanflags,
+                               const NMPlatformLink **out_link);
 gboolean nm_platform_link_vlan_set_ingress_map (NMPlatform *self, int ifindex, int from, int to);
 gboolean nm_platform_link_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, int to);
 gboolean nm_platform_link_vlan_change (NMPlatform *self,
@@ -1278,18 +1320,18 @@ gboolean nm_platform_link_vlan_change (NMPlatform *self,
                                        const NMVlanQosMapping *egress_map,
                                        gsize n_egress_map);
 
-NMPlatformError nm_platform_link_vxlan_add (NMPlatform *self,
-                                            const char *name,
-                                            const NMPlatformLnkVxlan *props,
-                                            const NMPlatformLink **out_link);
-
-NMPlatformError nm_platform_link_infiniband_add (NMPlatform *self,
-                                                 int parent,
-                                                 int p_key,
-                                                 const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_infiniband_delete (NMPlatform *self,
-                                                    int parent,
-                                                    int p_key);
+int nm_platform_link_vxlan_add (NMPlatform *self,
+                                const char *name,
+                                const NMPlatformLnkVxlan *props,
+                                const NMPlatformLink **out_link);
+
+int nm_platform_link_infiniband_add (NMPlatform *self,
+                                     int parent,
+                                     int p_key,
+                                     const NMPlatformLink **out_link);
+int nm_platform_link_infiniband_delete (NMPlatform *self,
+                                        int parent,
+                                        int p_key);
 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);
@@ -1318,55 +1360,68 @@ guint16     nm_platform_wpan_get_pan_id       (NMPlatform *platform, int ifindex
 gboolean    nm_platform_wpan_set_pan_id       (NMPlatform *platform, int ifindex, guint16 pan_id);
 guint16     nm_platform_wpan_get_short_addr   (NMPlatform *platform, int ifindex);
 gboolean    nm_platform_wpan_set_short_addr   (NMPlatform *platform, int ifindex, guint16 short_addr);
+gboolean    nm_platform_wpan_set_channel      (NMPlatform *platform, int ifindex, guint8 page, guint8 channel);
 
 void                   nm_platform_ip4_address_set_addr (NMPlatformIP4Address *addr, in_addr_t address, guint8 plen);
 const struct in6_addr *nm_platform_ip6_address_get_peer (const NMPlatformIP6Address *addr);
 
 const NMPlatformIP4Address *nm_platform_ip4_address_get (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
 
-NMPlatformError nm_platform_link_gre_add (NMPlatform *self,
-                                          const char *name,
-                                          const NMPlatformLnkGre *props,
-                                          const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_ip6tnl_add (NMPlatform *self,
-                                             const char *name,
-                                             const NMPlatformLnkIp6Tnl *props,
-                                             const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_ip6gre_add (NMPlatform *self,
-                                             const char *name,
-                                             const NMPlatformLnkIp6Tnl *props,
-                                             const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_ipip_add (NMPlatform *self,
-                                           const char *name,
-                                           const NMPlatformLnkIpIp *props,
-                                           const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_macsec_add (NMPlatform *self,
-                                             const char *name,
-                                             int parent,
-                                             const NMPlatformLnkMacsec *props,
-                                             const NMPlatformLink **out_link);
-NMPlatformError nm_platform_link_macvlan_add (NMPlatform *self,
-                                              const char *name,
-                                              int parent,
-                                              const NMPlatformLnkMacvlan *props,
-                                              const NMPlatformLink **out_link);
-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);
-NMPlatformError nm_platform_link_6lowpan_add (NMPlatform *self,
-                                              const char *name,
-                                              int parent,
-                                              const NMPlatformLink **out_link);
+int nm_platform_link_gre_add (NMPlatform *self,
+                              const char *name,
+                              const NMPlatformLnkGre *props,
+                              const NMPlatformLink **out_link);
+int nm_platform_link_ip6tnl_add (NMPlatform *self,
+                                 const char *name,
+                                 const NMPlatformLnkIp6Tnl *props,
+                                 const NMPlatformLink **out_link);
+int nm_platform_link_ip6gre_add (NMPlatform *self,
+                                 const char *name,
+                                 const NMPlatformLnkIp6Tnl *props,
+                                 const NMPlatformLink **out_link);
+int nm_platform_link_ipip_add (NMPlatform *self,
+                               const char *name,
+                               const NMPlatformLnkIpIp *props,
+                               const NMPlatformLink **out_link);
+int nm_platform_link_macsec_add (NMPlatform *self,
+                                 const char *name,
+                                 int parent,
+                                 const NMPlatformLnkMacsec *props,
+                                 const NMPlatformLink **out_link);
+int nm_platform_link_macvlan_add (NMPlatform *self,
+                                  const char *name,
+                                  int parent,
+                                  const NMPlatformLnkMacvlan *props,
+                                  const NMPlatformLink **out_link);
+int nm_platform_link_sit_add (NMPlatform *self,
+                              const char *name,
+                              const NMPlatformLnkSit *props,
+                              const NMPlatformLink **out_link);
+int nm_platform_link_tun_add (NMPlatform *self,
+                              const char *name,
+                              const NMPlatformLnkTun *props,
+                              const NMPlatformLink **out_link,
+                              int *out_fd);
+int nm_platform_link_6lowpan_add (NMPlatform *self,
+                                  const char *name,
+                                  int parent,
+                                  const NMPlatformLink **out_link);
 gboolean nm_platform_link_6lowpan_get_properties (NMPlatform *self,
                                                   int ifindex,
                                                   int *out_parent);
 
+int nm_platform_link_wireguard_add (NMPlatform *self,
+                                    const char *name,
+                                    const NMPlatformLink **out_link);
+
+int nm_platform_link_wireguard_change (NMPlatform *self,
+                                       int ifindex,
+                                       const NMPlatformLnkWireGuard *lnk_wireguard,
+                                       const struct _NMPWireGuardPeer *peers,
+                                       const NMPlatformWireGuardChangePeerFlags *peer_flags,
+                                       guint peers_len,
+                                       NMPlatformWireGuardChangeFlags change_flags);
+
 const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address);
 
 gboolean nm_platform_object_delete (NMPlatform *self, const NMPObject *route);
@@ -1399,11 +1454,11 @@ gboolean nm_platform_ip_address_flush (NMPlatform *self,
 void nm_platform_ip_route_normalize (int addr_family,
                                      NMPlatformIPRoute *route);
 
-NMPlatformError nm_platform_ip_route_add (NMPlatform *self,
-                                          NMPNlmFlags flags,
-                                          const NMPObject *route);
-NMPlatformError nm_platform_ip4_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route);
-NMPlatformError nm_platform_ip6_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route);
+int nm_platform_ip_route_add (NMPlatform *self,
+                              NMPNlmFlags flags,
+                              const NMPObject *route);
+int nm_platform_ip4_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route);
+int nm_platform_ip6_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route);
 
 GPtrArray *nm_platform_ip_route_get_prune_list (NMPlatform *self,
                                                 int addr_family,
@@ -1421,22 +1476,22 @@ gboolean nm_platform_ip_route_flush (NMPlatform *self,
                                      int addr_family,
                                      int ifindex);
 
-NMPlatformError nm_platform_ip_route_get (NMPlatform *self,
-                                          int addr_family,
-                                          gconstpointer address,
-                                          int oif_ifindex,
-                                          NMPObject **out_route);
+int nm_platform_ip_route_get (NMPlatform *self,
+                              int addr_family,
+                              gconstpointer address,
+                              int oif_ifindex,
+                              NMPObject **out_route);
 
-NMPlatformError nm_platform_qdisc_add   (NMPlatform *self,
-                                         NMPNlmFlags flags,
-                                         const NMPlatformQdisc *qdisc);
+int nm_platform_qdisc_add   (NMPlatform *self,
+                             NMPNlmFlags flags,
+                             const NMPlatformQdisc *qdisc);
 gboolean nm_platform_qdisc_sync         (NMPlatform *self,
                                          int ifindex,
                                          GPtrArray *known_qdiscs);
 
-NMPlatformError nm_platform_tfilter_add   (NMPlatform *self,
-                                           NMPNlmFlags flags,
-                                           const NMPlatformTfilter *tfilter);
+int nm_platform_tfilter_add   (NMPlatform *self,
+                               NMPNlmFlags flags,
+                               const NMPlatformTfilter *tfilter);
 gboolean nm_platform_tfilter_sync         (NMPlatform *self,
                                            int ifindex,
                                            GPtrArray *known_tfilters);
@@ -1467,7 +1522,6 @@ const char *nm_platform_vlan_qos_mapping_to_string (const char *name,
                                                     char *buf,
                                                     gsize len);
 
-struct _NMPWireGuardPeer;
 const char *nm_platform_wireguard_peer_to_string (const struct _NMPWireGuardPeer *peer,
                                                   char *buf,
                                                   gsize len);
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
index f1092fe9..f34dc83d 100644
--- a/src/platform/nmp-netns.c
+++ b/src/platform/nmp-netns.c
@@ -19,15 +19,26 @@
  */
 
 #include "nm-default.h"
+
 #include "nmp-netns.h"
 
 #include <fcntl.h>
-#include <errno.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <pthread.h>
+
+/*****************************************************************************/
 
-#include "NetworkManagerUtils.h"
+/* NOTE: NMPNetns and all code used here must be thread-safe! */
+
+/* we may not call logging functions from the main-thread alone. Hence, we
+ * require locking from nm-logging. Indicate that by setting NM_THREAD_SAFE_ON_MAIN_THREAD
+ * to zero. */
+#undef NM_THREAD_SAFE_ON_MAIN_THREAD
+#define NM_THREAD_SAFE_ON_MAIN_THREAD 0
+
+/*****************************************************************************/
 
 #define PROC_SELF_NS_MNT "/proc/self/ns/mnt"
 #define PROC_SELF_NS_NET "/proc/self/ns/net"
@@ -116,49 +127,81 @@ typedef struct {
 	int ns_types;
 } NetnsInfo;
 
-static void _stack_push (NMPNetns *netns, int ns_types);
+static void _stack_push (GArray *netns_stack,
+                         NMPNetns *netns,
+                         int ns_types);
 static NMPNetns *_netns_new (GError **error);
 
 /*****************************************************************************/
 
-static GArray *netns_stack = NULL;
+static NMPNetns *
+_netns_get (NetnsInfo *info)
+{
+	nm_assert (!info || NMP_IS_NETNS (info->netns));
+	return info ? info->netns : NULL;
+}
+
+/*****************************************************************************/
+
+static _nm_thread_local GArray *_netns_stack = NULL;
 
 static void
-_stack_ensure_init_impl (void)
+_netns_stack_clear_cb (gpointer data)
 {
-	NMPNetns *netns;
-	GError *error = NULL;
+	NetnsInfo *info = data;
 
-	nm_assert (!netns_stack);
+	nm_assert (NMP_IS_NETNS (info->netns));
+	g_object_unref (info->netns);
+}
 
-	netns_stack = g_array_new (FALSE, FALSE, sizeof (NetnsInfo));
+static GArray *
+_netns_stack_get_impl (void)
+{
+	gs_unref_object NMPNetns *netns = NULL;
+	gs_free_error GError *error = NULL;
+	pthread_key_t key;
+	GArray *s;
+
+	s = g_array_new (FALSE, FALSE, sizeof (NetnsInfo));
+	g_array_set_clear_func (s, _netns_stack_clear_cb);
+	_netns_stack = s;
 
 	/* at the bottom of the stack we must try to create a netns instance
 	 * that we never pop. It's the base to which we need to return. */
-
 	netns = _netns_new (&error);
-
 	if (!netns) {
-		/* don't know how to recover from this error. Netns are not supported. */
 		_LOGE (NULL, "failed to create initial netns: %s", error->message);
-		g_clear_error (&error);
-		return;
+		return s;
 	}
 
-	_stack_push (netns, _CLONE_NS_ALL);
+	/* we leak this instance inside the stack. */
+	_stack_push (s, netns, _CLONE_NS_ALL);
 
-	/* we leak this instance inside netns_stack. It cannot be popped. */
-	g_object_unref (netns);
+	/* finally, register a destructor function to cleanup the array. If we fail
+	 * to do so, we will leak NMPNetns instances (and their file descriptor) when the
+	 * thread exits. */
+	if (pthread_key_create (&key, (void (*) (void *)) g_array_unref) != 0)
+		_LOGE (NULL, "failure to initialize thread-local storage");
+	else if (pthread_setspecific (key, s) != 0)
+		_LOGE (NULL, "failure to set thread-local storage");
+
+	return s;
 }
-#define _stack_ensure_init() \
-	G_STMT_START { \
-		if (G_UNLIKELY (!netns_stack)) { \
-			_stack_ensure_init_impl (); \
-		} \
-	} G_STMT_END
+
+#define _netns_stack_get() \
+	({ \
+		GArray *_s = _netns_stack; \
+		\
+		if (G_UNLIKELY (!_s)) \
+			_s = _netns_stack_get_impl (); \
+		_s; \
+	})
+
+/*****************************************************************************/
 
 static NMPNetns *
-_stack_current_netns (int ns_types)
+_stack_current_netns (GArray *netns_stack,
+                      int ns_types)
 {
 	guint j;
 
@@ -179,7 +222,9 @@ _stack_current_netns (int ns_types)
 }
 
 static int
-_stack_current_ns_types (NMPNetns *netns, int ns_types)
+_stack_current_ns_types (GArray *netns_stack,
+                         NMPNetns *netns,
+                         int ns_types)
 {
 	const int ns_types_check[] = { _CLONE_NS_ALL_V };
 	guint i, j;
@@ -212,27 +257,25 @@ _stack_current_ns_types (NMPNetns *netns, int ns_types)
 }
 
 static NetnsInfo *
-_stack_peek (void)
+_stack_peek (GArray *netns_stack)
 {
-	nm_assert (netns_stack);
-
 	if (netns_stack->len > 0)
 		return &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1));
 	return NULL;
 }
 
 static NetnsInfo *
-_stack_bottom (void)
+_stack_bottom (GArray *netns_stack)
 {
-	nm_assert (netns_stack);
-
 	if (netns_stack->len > 0)
 		return &g_array_index (netns_stack, NetnsInfo, 0);
 	return NULL;
 }
 
 static void
-_stack_push (NMPNetns *netns, int ns_types)
+_stack_push (GArray *netns_stack,
+             NMPNetns *netns,
+             int ns_types)
 {
 	NetnsInfo *info;
 
@@ -244,13 +287,15 @@ _stack_push (NMPNetns *netns, int ns_types)
 	g_array_set_size (netns_stack, netns_stack->len + 1);
 
 	info = &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1));
-	info->netns = g_object_ref (netns);
-	info->ns_types = ns_types;
-	info->count = 1;
+	*info = (NetnsInfo) {
+		.netns    = g_object_ref (netns),
+		.ns_types = ns_types,
+		.count    = 1,
+	};
 }
 
 static void
-_stack_pop (void)
+_stack_pop (GArray *netns_stack)
 {
 	NetnsInfo *info;
 
@@ -262,13 +307,11 @@ _stack_pop (void)
 	nm_assert (NMP_IS_NETNS (info->netns));
 	nm_assert (info->count == 1);
 
-	g_object_unref (info->netns);
-
 	g_array_set_size (netns_stack, netns_stack->len - 1);
 }
 
 static guint
-_stack_size (void)
+_stack_size (GArray *netns_stack)
 {
 	nm_assert (netns_stack);
 
@@ -289,7 +332,7 @@ _netns_new (GError **error)
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "Failed opening netns: %s",
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		errno = errsv;
 		return NULL;
 	}
@@ -299,7 +342,7 @@ _netns_new (GError **error)
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "Failed opening mntns: %s",
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		nm_close (fd_net);
 		errno = errsv;
 		return NULL;
@@ -332,31 +375,33 @@ _setns (NMPNetns *self, int type)
 }
 
 static gboolean
-_netns_switch_push (NMPNetns *self, int ns_types)
+_netns_switch_push (GArray *netns_stack,
+                    NMPNetns *self,
+                    int ns_types)
 {
 	int errsv;
 
 	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
-	    && !_stack_current_ns_types (self, CLONE_NEWNET)
+	    && !_stack_current_ns_types (netns_stack, self, CLONE_NEWNET)
 	    && _setns (self, CLONE_NEWNET) != 0) {
 		errsv = errno;
-		_LOGE (self, "failed to switch netns: %s", g_strerror (errsv));
+		_LOGE (self, "failed to switch netns: %s", nm_strerror_native (errsv));
 		return FALSE;
 	}
 	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNS)
-	    && !_stack_current_ns_types (self, CLONE_NEWNS)
+	    && !_stack_current_ns_types (netns_stack, self, CLONE_NEWNS)
 	    && _setns (self, CLONE_NEWNS) != 0) {
 		errsv = errno;
-		_LOGE (self, "failed to switch mntns: %s", g_strerror (errsv));
+		_LOGE (self, "failed to switch mntns: %s", nm_strerror_native (errsv));
 
 		/* try to fix the mess by returning to the previous netns. */
 		if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
-	        && !_stack_current_ns_types (self, CLONE_NEWNET)) {
-			self = _stack_current_netns (CLONE_NEWNET);
+	        && !_stack_current_ns_types (netns_stack, self, CLONE_NEWNET)) {
+			self = _stack_current_netns (netns_stack, CLONE_NEWNET);
 			if (   self
 			    && _setns (self, CLONE_NEWNET) != 0) {
 				errsv = errno;
-				_LOGE (self, "failed to restore netns: %s", g_strerror (errsv));
+				_LOGE (self, "failed to restore netns: %s", nm_strerror_native (errsv));
 			}
 		}
 		return FALSE;
@@ -366,33 +411,36 @@ _netns_switch_push (NMPNetns *self, int ns_types)
 }
 
 static gboolean
-_netns_switch_pop (NMPNetns *self, int ns_types)
+_netns_switch_pop (GArray *netns_stack,
+                   NMPNetns *self,
+                   int ns_types)
 {
 	int errsv;
 	NMPNetns *current;
 	int success = TRUE;
 
 	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
-	    && (!self || !_stack_current_ns_types (self, CLONE_NEWNET))) {
-		current = _stack_current_netns (CLONE_NEWNET);
+	    && (   !self
+	        || !_stack_current_ns_types (netns_stack, self, CLONE_NEWNET))) {
+		current = _stack_current_netns (netns_stack, CLONE_NEWNET);
 		if (!current) {
 			g_warn_if_reached ();
 			success = FALSE;
 		} else if (_setns (current, CLONE_NEWNET) != 0) {
 			errsv = errno;
-			_LOGE (self, "failed to switch netns: %s", g_strerror (errsv));
+			_LOGE (self, "failed to switch netns: %s", nm_strerror_native (errsv));
 			success = FALSE;
 		}
 	}
 	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNS)
-	    && (!self || !_stack_current_ns_types (self, CLONE_NEWNS))) {
-		current = _stack_current_netns (CLONE_NEWNS);
+	    && (!self || !_stack_current_ns_types (netns_stack, self, CLONE_NEWNS))) {
+		current = _stack_current_netns (netns_stack, CLONE_NEWNS);
 		if (!current) {
 			g_warn_if_reached ();
 			success = FALSE;
 		} else if (_setns (current, CLONE_NEWNS) != 0) {
 			errsv = errno;
-			_LOGE (self, "failed to switch mntns: %s", g_strerror (errsv));
+			_LOGE (self, "failed to switch mntns: %s", nm_strerror_native (errsv));
 			success = FALSE;
 		}
 	}
@@ -423,32 +471,31 @@ nmp_netns_get_fd_mnt (NMPNetns *self)
 static gboolean
 _nmp_netns_push_type (NMPNetns *self, int ns_types)
 {
+	GArray *netns_stack = _netns_stack_get ();
 	NetnsInfo *info;
 	char sbuf[100];
 
-	_stack_ensure_init ();
-
-	info = _stack_peek ();
+	info = _stack_peek (netns_stack);
 	g_return_val_if_fail (info, FALSE);
 
 	if (info->netns == self && info->ns_types == ns_types) {
 		info->count++;
 		_LOGt (self, "push#%u* %s (increase count to %d)",
-		       _stack_size () - 1,
+		       _stack_size (netns_stack) - 1,
 		       _ns_types_to_str (ns_types, ns_types, sbuf), info->count);
 		return TRUE;
 	}
 
 	_LOGD (self, "push#%u %s",
-	       _stack_size (),
+	       _stack_size (netns_stack),
 	       _ns_types_to_str (ns_types,
-	                         _stack_current_ns_types (self, ns_types),
+	                         _stack_current_ns_types (netns_stack, self, ns_types),
 	                         sbuf));
 
-	if (!_netns_switch_push (self, ns_types))
+	if (!_netns_switch_push (netns_stack, self, ns_types))
 		return FALSE;
 
-	_stack_push (self, ns_types);
+	_stack_push (netns_stack, self, ns_types);
 	return TRUE;
 }
 
@@ -472,14 +519,13 @@ nmp_netns_push_type (NMPNetns *self, int ns_types)
 NMPNetns *
 nmp_netns_new (void)
 {
+	GArray *netns_stack = _netns_stack_get ();
 	NMPNetns *self;
 	int errsv;
 	GError *error = NULL;
 	unsigned long mountflags = 0;
 
-	_stack_ensure_init ();
-
-	if (!_stack_peek ()) {
+	if (!_stack_peek (netns_stack)) {
 		/* there are no netns instances. We cannot create a new one
 		 * (because after unshare we couldn't return to the original one). */
 		errno = ENOTSUP;
@@ -488,19 +534,19 @@ nmp_netns_new (void)
 
 	if (unshare (_CLONE_NS_ALL) != 0) {
 		errsv = errno;
-		_LOGE (NULL, "failed to create new net and mnt namespace: %s", g_strerror (errsv));
+		_LOGE (NULL, "failed to create new net and mnt namespace: %s", nm_strerror_native (errsv));
 		return NULL;
 	}
 
 	if (mount ("", "/", "none", MS_SLAVE | MS_REC, NULL) != 0) {
 		errsv = errno;
-		_LOGE (NULL, "failed mount --make-rslave: %s", g_strerror (errsv));
+		_LOGE (NULL, "failed mount --make-rslave: %s", nm_strerror_native (errsv));
 		goto err_out;
 	}
 
 	if (umount2 ("/sys", MNT_DETACH) != 0) {
 		errsv = errno;
-		_LOGE (NULL, "failed umount /sys: %s", g_strerror (errsv));
+		_LOGE (NULL, "failed umount /sys: %s", nm_strerror_native (errsv));
 		goto err_out;
 	}
 
@@ -509,7 +555,7 @@ nmp_netns_new (void)
 
 	if (mount ("sysfs", "/sys", "sysfs", mountflags, NULL) != 0) {
 		errsv = errno;
-		_LOGE (NULL, "failed mount /sys: %s", g_strerror (errsv));
+		_LOGE (NULL, "failed mount /sys: %s", nm_strerror_native (errsv));
 		goto err_out;
 	}
 
@@ -521,11 +567,11 @@ nmp_netns_new (void)
 		goto err_out;
 	}
 
-	_stack_push (self, _CLONE_NS_ALL);
+	_stack_push (netns_stack, self, _CLONE_NS_ALL);
 
 	return self;
 err_out:
-	_netns_switch_pop (NULL, _CLONE_NS_ALL);
+	_netns_switch_pop (netns_stack, NULL, _CLONE_NS_ALL);
 	errno = errsv;
 	return NULL;
 }
@@ -533,14 +579,13 @@ err_out:
 gboolean
 nmp_netns_pop (NMPNetns *self)
 {
+	GArray *netns_stack = _netns_stack_get ();
 	NetnsInfo *info;
 	int ns_types;
 
 	g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
 
-	_stack_ensure_init ();
-
-	info = _stack_peek ();
+	info = _stack_peek (netns_stack);
 
 	g_return_val_if_fail (info, FALSE);
 	g_return_val_if_fail (info->netns == self, FALSE);
@@ -548,52 +593,42 @@ nmp_netns_pop (NMPNetns *self)
 	if (info->count > 1) {
 		info->count--;
 		_LOGt (self, "pop#%u* (decrease count to %d)",
-		       _stack_size () - 1, info->count);
+		       _stack_size (netns_stack) - 1, info->count);
 		return TRUE;
 	}
 	g_return_val_if_fail (info->count == 1, FALSE);
 
 	/* cannot pop the original netns. */
-	g_return_val_if_fail (_stack_size () > 1, FALSE);
+	g_return_val_if_fail (_stack_size (netns_stack) > 1, FALSE);
 
-	_LOGD (self, "pop#%u", _stack_size () - 1);
+	_LOGD (self, "pop#%u", _stack_size (netns_stack) - 1);
 
 	ns_types = info->ns_types;
 
-	_stack_pop ();
+	_stack_pop (netns_stack);
 
-	return _netns_switch_pop (self, ns_types);
+	return _netns_switch_pop (netns_stack, self, ns_types);
 }
 
 NMPNetns *
 nmp_netns_get_current (void)
 {
-	NetnsInfo *info;
-
-	_stack_ensure_init ();
-
-	info = _stack_peek ();
-	return info ? info->netns : NULL;
+	return _netns_get (_stack_peek (_netns_stack_get ()));
 }
 
 NMPNetns *
 nmp_netns_get_initial (void)
 {
-	NetnsInfo *info;
-
-	_stack_ensure_init ();
-
-	info = _stack_bottom ();
-	return info ? info->netns : NULL;
+	return _netns_get (_stack_bottom (_netns_stack_get ()));
 }
 
 gboolean
 nmp_netns_is_initial (void)
 {
-	if (G_UNLIKELY (!netns_stack))
-		return TRUE;
+	GArray *netns_stack = _netns_stack_get ();
 
-	return nmp_netns_get_current () == nmp_netns_get_initial ();
+	return (   _netns_get (_stack_peek (netns_stack))
+	        == _netns_get (_stack_bottom (netns_stack)));
 }
 
 /*****************************************************************************/
@@ -618,7 +653,7 @@ nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd)
 		errsv = errno;
 		if (errsv != EEXIST) {
 			_LOGE (self, "bind: failed to create directory %s: %s",
-			       dirname, g_strerror (errsv));
+			       dirname, nm_strerror_native (errsv));
 			return FALSE;
 		}
 	}
@@ -626,7 +661,7 @@ nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd)
 	if ((fd = creat (filename, S_IRUSR | S_IRGRP | S_IROTH)) == -1) {
 		errsv = errno;
 		_LOGE (self, "bind: failed to create %s: %s",
-		       filename, g_strerror (errsv));
+		       filename, nm_strerror_native (errsv));
 		return FALSE;
 	}
 	nm_close (fd);
@@ -634,7 +669,7 @@ nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd)
 	if (mount (PROC_SELF_NS_NET, filename, "none", MS_BIND, NULL) != 0) {
 		errsv = errno;
 		_LOGE (self, "bind: failed to mount %s to %s: %s",
-		       PROC_SELF_NS_NET, filename, g_strerror (errsv));
+		       PROC_SELF_NS_NET, filename, nm_strerror_native (errsv));
 		unlink (filename);
 		return FALSE;
 	}
@@ -642,7 +677,7 @@ nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd)
 	if (out_fd) {
 		if ((fd = open (filename, O_RDONLY | O_CLOEXEC)) == -1) {
 			errsv = errno;
-			_LOGE (self, "bind: failed to open %s: %s", filename, g_strerror (errsv));
+			_LOGE (self, "bind: failed to open %s: %s", filename, nm_strerror_native (errsv));
 			umount2 (filename, MNT_DETACH);
 			unlink (filename);
 			return FALSE;
@@ -663,12 +698,12 @@ nmp_netns_bind_to_path_destroy (NMPNetns *self, const char *filename)
 
 	if (umount2 (filename, MNT_DETACH) != 0) {
 		errsv = errno;
-		_LOGE (self, "bind: failed to unmount2 %s: %s", filename, g_strerror (errsv));
+		_LOGE (self, "bind: failed to unmount2 %s: %s", filename, nm_strerror_native (errsv));
 		return FALSE;
 	}
 	if (unlink (filename) != 0) {
 		errsv = errno;
-		_LOGE (self, "bind: failed to unlink %s: %s", filename, g_strerror (errsv));
+		_LOGE (self, "bind: failed to unlink %s: %s", filename, nm_strerror_native (errsv));
 		return FALSE;
 	}
 	return TRUE;
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index f7fa6cb3..6ec6fea1 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -89,6 +89,192 @@ struct _NMPCache {
 
 /*****************************************************************************/
 
+int
+nm_sock_addr_union_cmp (const NMSockAddrUnion *a, const NMSockAddrUnion *b)
+{
+	nm_assert (!a || NM_IN_SET (a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6));
+	nm_assert (!b || NM_IN_SET (b->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	NM_CMP_SELF (a, b);
+
+	NM_CMP_FIELD (a, b, sa.sa_family);
+	switch (a->sa.sa_family) {
+	case AF_INET:
+		NM_CMP_DIRECT (ntohl (a->in.sin_addr.s_addr), ntohl (b->in.sin_addr.s_addr));
+		NM_CMP_DIRECT (htons (a->in.sin_port), htons (b->in.sin_port));
+		break;
+	case AF_INET6:
+		NM_CMP_DIRECT_IN6ADDR (&a->in6.sin6_addr, &b->in6.sin6_addr);
+		NM_CMP_DIRECT (htons (a->in6.sin6_port), htons (b->in6.sin6_port));
+		NM_CMP_FIELD (a, b, in6.sin6_scope_id);
+		NM_CMP_FIELD (a, b, in6.sin6_flowinfo);
+		break;
+	}
+	return 0;
+}
+
+void
+nm_sock_addr_union_hash_update (const NMSockAddrUnion *a, NMHashState *h)
+{
+	if (!a) {
+		nm_hash_update_val (h, 1241364739u);
+		return;
+	}
+
+	nm_assert (NM_IN_SET (a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	switch (a->sa.sa_family) {
+	case AF_INET:
+		nm_hash_update_vals (h,
+		                     a->in.sin_family,
+		                     a->in.sin_addr.s_addr,
+		                     a->in.sin_port);
+		return;
+	case AF_INET6:
+		nm_hash_update_vals (h,
+		                     a->in6.sin6_family,
+		                     a->in6.sin6_addr,
+		                     a->in6.sin6_port,
+		                     a->in6.sin6_scope_id,
+		                     a->in6.sin6_flowinfo);
+		return;
+	default:
+		nm_hash_update_val (h, a->sa.sa_family);
+		return;
+	}
+}
+
+/**
+ * nm_sock_addr_union_cpy:
+ * @dst: the destination #NMSockAddrUnion. It will always be fully initialized,
+ *   to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of
+ *   error).
+ * @src: (allow-none): the source buffer with an sockaddr to copy. It may be unaligned in
+ *   memory. If not %NULL, the buffer must be at least large enough to contain
+ *   sa.sa_family, and then, depending on sa.sa_family, it must be large enough
+ *   to hold struct sockaddr_in or struct sockaddr_in6.
+ *
+ * @dst will always be fully initialized (including setting all un-used bytes to zero).
+ */
+void
+nm_sock_addr_union_cpy (NMSockAddrUnion *dst,
+                        gconstpointer src /* unaligned (const NMSockAddrUnion *) */)
+{
+	struct sockaddr sa;
+	gsize src_len;
+
+	nm_assert (dst);
+
+	*dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+
+	if (!src)
+		return;
+
+	memcpy (&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof (sa.sa_family));
+
+	if (sa.sa_family == AF_INET)
+		src_len = sizeof (struct sockaddr_in);
+	else if (sa.sa_family == AF_INET6)
+		src_len = sizeof (struct sockaddr_in6);
+	else
+		return;
+
+	memcpy (dst, src, src_len);
+	nm_assert (dst->sa.sa_family == sa.sa_family);
+}
+
+/**
+ * nm_sock_addr_union_cpy_untrusted:
+ * @dst: the destination #NMSockAddrUnion. It will always be fully initialized,
+ *   to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of
+ *   error).
+ * @src: the source buffer with an sockaddr to copy. It may be unaligned in
+ *   memory.
+ * @src_len: the length of @src in bytes.
+ *
+ * The function requires @src_len to be either sizeof(struct sockaddr_in) or sizeof (struct sockaddr_in6).
+ * If that's the case, then @src will be interpreted as such structure (unaligned), and
+ * accessed. It will check sa.sa_family to match the expected sizes, and if it does, the
+ * struct will be copied.
+ *
+ * On any failure, @dst will be set to sa.sa_family AF_UNSPEC.
+ * @dst will always be fully initialized (including setting all un-used bytes to zero).
+ */
+void
+nm_sock_addr_union_cpy_untrusted (NMSockAddrUnion *dst,
+                                  gconstpointer src /* unaligned (const NMSockAddrUnion *) */,
+                                  gsize src_len)
+{
+	int f_expected;
+	struct sockaddr sa;
+
+	nm_assert (dst);
+
+	*dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+
+	if (src_len == sizeof (struct sockaddr_in))
+		f_expected = AF_INET;
+	else if (src_len == sizeof (struct sockaddr_in6))
+		f_expected = AF_INET6;
+	else
+		return;
+
+	memcpy (&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof (sa.sa_family));
+
+	if (sa.sa_family != f_expected)
+		return;
+
+	memcpy (dst, src, src_len);
+	nm_assert (dst->sa.sa_family == sa.sa_family);
+}
+
+const char *
+nm_sock_addr_union_to_string (const NMSockAddrUnion *sa,
+                              char *buf,
+                              gsize len)
+{
+	char s_addr[NM_UTILS_INET_ADDRSTRLEN];
+	char s_scope_id[40];
+
+	if (!nm_utils_to_string_buffer_init_null (sa, &buf, &len))
+		return buf;
+
+	/* maybe we should use getnameinfo(), but here implement it ourself.
+	 *
+	 * We want to see the actual bytes for debugging (as we understand them),
+	 * and now what getnameinfo() makes of it. Also, it's simpler this way. */
+
+	switch (sa->sa.sa_family) {
+	case AF_INET:
+		g_snprintf (buf, len,
+		            "%s:%u",
+		            nm_utils_inet4_ntop (sa->in.sin_addr.s_addr, s_addr),
+		            (guint) htons (sa->in.sin_port));
+		break;
+	case AF_INET6:
+		g_snprintf (buf, len,
+		            "[%s%s]:%u",
+		            nm_utils_inet6_ntop (&sa->in6.sin6_addr, s_addr),
+		            (  sa->in6.sin6_scope_id != 0
+		             ? nm_sprintf_buf (s_scope_id, "%u", sa->in6.sin6_scope_id)
+		             : ""),
+		            (guint) htons (sa->in6.sin6_port));
+		break;
+	case AF_UNSPEC:
+		g_snprintf (buf, len, "unspec");
+		break;
+	default:
+		g_snprintf (buf, len,
+		            "{addr-family:%u}",
+		            (unsigned) sa->sa.sa_family);
+		break;
+	}
+
+	return buf;
+}
+
+/*****************************************************************************/
+
 static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class;
 
 static void
@@ -392,14 +578,9 @@ _wireguard_peer_hash_update (const NMPWireGuardPeer *peer,
 	                     peer->rx_bytes,
 	                     peer->tx_bytes,
 	                     peer->last_handshake_time.tv_sec,
-	                     peer->last_handshake_time.tv_nsec,
-	                     peer->endpoint_port,
-	                     peer->endpoint_family);
+	                     peer->last_handshake_time.tv_nsec);
 
-	if (peer->endpoint_family == AF_INET)
-		nm_hash_update_val (h, peer->endpoint_addr.addr4);
-	else if (peer->endpoint_family == AF_INET6)
-		nm_hash_update_val (h, peer->endpoint_addr.addr6);
+	nm_sock_addr_union_hash_update (&peer->endpoint, h);
 
 	for (i = 0; i < peer->allowed_ips_len; i++)
 		_wireguard_allowed_ip_hash_update (&peer->allowed_ips[i], h);
@@ -419,15 +600,11 @@ _wireguard_peer_cmp (const NMPWireGuardPeer *a,
 	NM_CMP_FIELD (a, b, tx_bytes);
 	NM_CMP_FIELD (a, b, allowed_ips_len);
 	NM_CMP_FIELD (a, b, persistent_keepalive_interval);
-	NM_CMP_FIELD (a, b, endpoint_port);
-	NM_CMP_FIELD (a, b, endpoint_family);
+	NM_CMP_FIELD (a, b, endpoint.sa.sa_family);
 	NM_CMP_FIELD_MEMCMP (a, b, public_key);
 	NM_CMP_FIELD_MEMCMP (a, b, preshared_key);
 
-	if (a->endpoint_family == AF_INET)
-		NM_CMP_FIELD (a, b, endpoint_addr.addr4);
-	else if (a->endpoint_family == AF_INET6)
-		NM_CMP_FIELD_IN6ADDR (a, b, endpoint_addr.addr6);
+	NM_CMP_RETURN (nm_sock_addr_union_cmp (&a->endpoint, &b->endpoint));
 
 	for (i = 0; i < a->allowed_ips_len; i++) {
 		NM_CMP_RETURN (_wireguard_allowed_ip_cmp (&a->allowed_ips[i],
@@ -630,9 +807,12 @@ _nmp_object_stackinit_from_class (NMPObject *obj, const NMPClass *klass)
 	nm_assert (obj);
 	nm_assert (klass);
 
-	memset (obj, 0, sizeof (NMPObject));
-	obj->_class = klass;
-	obj->parent._ref_count = NM_OBJ_REF_COUNT_STACKINIT;
+	*obj = (NMPObject) {
+		.parent = {
+			.klass      = (const NMDedupMultiObjClass *) klass,
+			._ref_count = NM_OBJ_REF_COUNT_STACKINIT,
+		},
+	};
 }
 
 static NMPObject *
@@ -644,9 +824,12 @@ _nmp_object_stackinit_from_type (NMPObject *obj, NMPObjectType obj_type)
 	klass = nmp_class_from_type (obj_type);
 	nm_assert (klass);
 
-	memset (obj, 0, sizeof (NMPObject));
-	obj->_class = klass;
-	obj->parent._ref_count = NM_OBJ_REF_COUNT_STACKINIT;
+	*obj = (NMPObject) {
+		.parent = {
+			.klass      = (const NMDedupMultiObjClass *) klass,
+			._ref_count = NM_OBJ_REF_COUNT_STACKINIT,
+		},
+	};
 	return obj;
 }
 
@@ -897,11 +1080,9 @@ static const char * \
 _vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsize buf_len) \
 { \
 	plat_type *const obj = (plat_type *) _obj; \
-	char buf1[NM_UTILS_INET_ADDRSTRLEN]; \
-	char buf2[NM_UTILS_INET_ADDRSTRLEN]; \
+	_nm_unused char buf1[NM_UTILS_INET_ADDRSTRLEN]; \
+	_nm_unused char buf2[NM_UTILS_INET_ADDRSTRLEN]; \
 	\
-	(void) buf1; \
-	(void) buf2; \
 	g_snprintf (buf, buf_len, \
 	            __VA_ARGS__); \
 	return buf; \
@@ -1364,13 +1545,13 @@ _vt_cmd_plobj_id_hash_update (tfilter, NMPlatformTfilter, {
 	                     obj->handle);
 })
 
-static inline void
+static void
 _vt_cmd_plobj_hash_update_ip4_route (const NMPlatformObject *obj, NMHashState *h)
 {
 	return nm_platform_ip4_route_hash_update ((const NMPlatformIP4Route *) obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, h);
 }
 
-static inline void
+static void
 _vt_cmd_plobj_hash_update_ip6_route (const NMPlatformObject *obj, NMHashState *h)
 {
 	return nm_platform_ip6_route_hash_update ((const NMPlatformIP6Route *) obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, h);
@@ -1637,7 +1818,7 @@ nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *m
  * The flag obj->link.connected depends on the state of other links in the
  * @cache. See also nmp_cache_link_connected_needs_toggle(). Given an ifindex
  * of a master, check if the cache contains such a master link that needs
- * toogling of the connected flag.
+ * toggling of the connected flag.
  *
  * Returns: NULL if there is no master link with ifindex @master_ifindex that should be toggled.
  *   Otherwise, return the link object from inside the cache with the given ifindex.
@@ -1976,6 +2157,8 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 	} else if (!ifname && !match_fn)
 		return NULL;
 	else {
+		const NMPObject *obj_best = NULL;
+
 		if (ifname) {
 			if (strlen (ifname) >= IFNAMSIZ)
 				return NULL;
@@ -1987,16 +2170,21 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 		nmp_cache_iter_for_each_link (&iter, head_entry, &link) {
 			obj = NMP_OBJECT_UP_CAST (link);
 
-			if (visible_only && !nmp_object_is_visible (obj))
-				continue;
 			if (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type)
 				continue;
+			if (visible_only && !nmp_object_is_visible (obj))
+				continue;
 			if (match_fn && !match_fn (obj, user_data))
 				continue;
 
-			return obj;
+			/* if there are multiple candidates, prefer the visible ones. */
+			if (   visible_only
+			    || nmp_object_is_visible (obj))
+				return obj;
+			if (!obj_best)
+				obj_best = obj;
 		}
-		return NULL;
+		return obj_best;
 	}
 }
 
@@ -2331,15 +2519,15 @@ nmp_cache_remove_netlink (NMPCache *cache,
  *    afterwards. Hence, during a dump, every update should move the object to the
  *    end of the list, to obtain the correct order. That means, to use NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE,
  *    instead of NM_DEDUP_MULTI_IDX_MODE_APPEND.
- * @out_obj_old: (allow-none): (out): return the object with same ID as @obj_hand_over,
+ * @out_obj_old: (allow-none) (out): return the object with same ID as @obj_hand_over,
  *    that was in the cache before update. If an object is returned, the caller must
  *    unref it afterwards.
- * @out_obj_new: (allow-none): (out): return the object from the cache after update.
+ * @out_obj_new: (allow-none) (out): return the object from the cache after update.
  *    The caller must unref this object.
  *
  * Returns: how the cache changed.
  *
- * Even if there was no change in the cace (NMP_CACHE_OPS_UNCHANGED), @out_obj_old
+ * Even if there was no change in the cache (NMP_CACHE_OPS_UNCHANGED), @out_obj_old
  * and @out_obj_new will be set accordingly.
  **/
 NMPCacheOpsType
@@ -2610,7 +2798,7 @@ update_done:
 		nm_dedup_multi_entry_reorder (entry_cur, NULL, FALSE);
 		break;
 	default:
-		/* this is an unexecpted case, probably a bug that we need to handle better. */
+		/* this is an unexpected case, probably a bug that we need to handle better. */
 		resync_required = TRUE;
 		break;
 	}
@@ -2732,15 +2920,15 @@ nmp_cache_update_link_master_connected (NMPCache *cache,
 /*****************************************************************************/
 
 void
-nmp_cache_dirty_set_all (NMPCache *cache, NMPObjectType obj_type)
+nmp_cache_dirty_set_all (NMPCache *cache,
+                         const NMPLookup *lookup)
 {
-	NMPObject obj_needle;
-
 	nm_assert (cache);
+	nm_assert (lookup);
 
 	nm_dedup_multi_index_dirty_set_head (cache->multi_idx,
-	                                     _idx_type_get (cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE),
-	                                     _nmp_object_stackinit_from_type (&obj_needle, obj_type));
+	                                     _idx_type_get (cache, lookup->cache_id_type),
+	                                     &lookup->selector_obj);
 }
 
 /*****************************************************************************/
@@ -2789,7 +2977,6 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectLink),
 		.sizeof_public                      = sizeof (NMPlatformLink),
 		.obj_type_name                      = "link",
-		.addr_family                        = AF_UNSPEC,
 		.rtm_gettype                        = RTM_GETLINK,
 		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_LINK,
 		.signal_type                        = NM_PLATFORM_SIGNAL_LINK_CHANGED,
@@ -3097,7 +3284,7 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_obj_dispose                    = _vt_cmd_obj_dispose_lnk_wireguard,
 		.cmd_obj_to_string                  = _vt_cmd_obj_to_string_lnk_wireguard,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_wireguard_to_string,
-		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vlan_hash_update,
-		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vlan_cmp,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_wireguard_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_wireguard_cmp,
 	},
 };
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index 97be8321..525e1440 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -21,6 +21,8 @@
 #ifndef __NMP_OBJECT_H__
 #define __NMP_OBJECT_H__
 
+#include <netinet/in.h>
+
 #include "nm-utils/nm-obj.h"
 #include "nm-utils/nm-dedup-multi.h"
 #include "nm-platform.h"
@@ -29,6 +31,50 @@ struct udev_device;
 
 /*****************************************************************************/
 
+/* "struct __kernel_timespec" uses "long long", but we use gint64. In practice,
+ * these are the same types. */
+G_STATIC_ASSERT (sizeof (long long) == sizeof (gint64));
+
+typedef struct {
+	/* like "struct __kernel_timespec". */
+	gint64 tv_sec;
+	gint64 tv_nsec;
+} NMPTimespec64;
+
+/*****************************************************************************/
+
+typedef union {
+	struct sockaddr     sa;
+	struct sockaddr_in  in;
+	struct sockaddr_in6 in6;
+} NMSockAddrUnion;
+
+#define NM_SOCK_ADDR_UNION_INIT_UNSPEC \
+	{ \
+		.sa = { \
+			.sa_family = AF_UNSPEC, \
+		}, \
+	}
+
+int nm_sock_addr_union_cmp (const NMSockAddrUnion *a,
+                            const NMSockAddrUnion *b);
+
+void nm_sock_addr_union_hash_update (const NMSockAddrUnion *a,
+                                     NMHashState *h);
+
+void nm_sock_addr_union_cpy (NMSockAddrUnion *dst,
+                             gconstpointer src /* unaligned (const NMSockAddrUnion *) */);
+
+void nm_sock_addr_union_cpy_untrusted (NMSockAddrUnion *dst,
+                                       gconstpointer src /* unaligned (const NMSockAddrUnion *) */,
+                                       gsize src_len);
+
+const char *nm_sock_addr_union_to_string (const NMSockAddrUnion *sa,
+                                          char *buf,
+                                          gsize len);
+
+/*****************************************************************************/
+
 typedef struct {
 	NMIPAddr addr;
 	guint8 family;
@@ -36,10 +82,13 @@ typedef struct {
 } NMPWireGuardAllowedIP;
 
 typedef struct _NMPWireGuardPeer {
-	NMIPAddr endpoint_addr;
-	struct timespec last_handshake_time;
+	NMSockAddrUnion endpoint;
+
+	NMPTimespec64 last_handshake_time;
+
 	guint64 rx_bytes;
 	guint64 tx_bytes;
+
 	union {
 		const NMPWireGuardAllowedIP *allowed_ips;
 		guint _construct_idx_start;
@@ -48,11 +97,11 @@ typedef struct _NMPWireGuardPeer {
 		guint allowed_ips_len;
 		guint _construct_idx_end;
 	};
+
 	guint16 persistent_keepalive_interval;
-	guint16 endpoint_port;
+
 	guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN];
 	guint8 preshared_key[NMP_WIREGUARD_SYMMETRIC_KEY_LEN];
-	guint8 endpoint_family;
 } NMPWireGuardPeer;
 
 /*****************************************************************************/
@@ -100,14 +149,14 @@ typedef enum { /*< skip >*/
 	 *
 	 * Also, note that links may be considered invisible. This index type
 	 * expose all links, even invisible ones. For addresses/routes, this
-	 * distiction doesn't exist, as all addresses/routes that are alive
+	 * distinction doesn't exist, as all addresses/routes that are alive
 	 * are visible as well. */
 	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
 
 	/* index for the link objects by ifname. */
 	NMP_CACHE_ID_TYPE_LINK_BY_IFNAME,
 
-	/* indeces for the visible default-routes, ignoring ifindex.
+	/* indices for the visible default-routes, ignoring ifindex.
 	 * This index only contains two partitions: all visible default-routes,
 	 * separate for IPv4 and IPv6. */
 	NMP_CACHE_ID_TYPE_DEFAULT_ROUTES,
@@ -504,7 +553,7 @@ nmp_object_ref (const NMPObject *obj)
 	}
 
 	/* ref and unref accept const pointers. NMPObject is supposed to be shared
-	 * and kept immutable. Disallowing to take/retrun a reference to a const
+	 * and kept immutable. Disallowing to take/return a reference to a const
 	 * NMPObject is cumbersome, because callers are precisely expected to
 	 * keep a ref on the otherwise immutable object. */
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
@@ -735,7 +784,8 @@ NMPCacheOpsType nmp_cache_update_link_master_connected (NMPCache *cache,
                                                         const NMPObject **out_obj_old,
                                                         const NMPObject **out_obj_new);
 
-void nmp_cache_dirty_set_all (NMPCache *cache, NMPObjectType obj_type);
+void nmp_cache_dirty_set_all (NMPCache *cache,
+                              const NMPLookup *lookup);
 
 NMPCache *nmp_cache_new (NMDedupMultiIndex *multi_idx, gboolean use_udev);
 void nmp_cache_free (NMPCache *cache);
diff --git a/src/platform/tests/meson.build b/src/platform/tests/meson.build
index bedc6916..8086a46c 100644
--- a/src/platform/tests/meson.build
+++ b/src/platform/tests/meson.build
@@ -1,14 +1,14 @@
 test_units = [
-  ['test-link-fake',     'test-link.c',       test_nm_dep_fake,  30],
-  ['test-link-linux',    'test-link.c',       test_nm_dep_linux, 180],
-  ['test-address-fake',  'test-address.c',    test_nm_dep_fake,  30],
-  ['test-address-linux', 'test-address.c',    test_nm_dep_linux, 30],
-  ['test-general',       'test-general.c',    test_nm_dep,       30],
-  ['test-nmp-object',    'test-nmp-object.c', test_nm_dep,       30],
-  ['test-route-fake',    'test-route.c',      test_nm_dep_fake,  30],
-  ['test-route-linux',   'test-route.c',      test_nm_dep_linux, 30],
-  ['test-cleanup-fake',  'test-cleanup.c',    test_nm_dep_fake,  30],
-  ['test-cleanup-linux', 'test-cleanup.c',    test_nm_dep_linux, 30],
+  ['test-link-fake',     'test-link.c',       test_nm_dep_fake,  default_test_timeout],
+  ['test-link-linux',    'test-link.c',       test_nm_dep_linux, 900],
+  ['test-address-fake',  'test-address.c',    test_nm_dep_fake,  default_test_timeout],
+  ['test-address-linux', 'test-address.c',    test_nm_dep_linux, default_test_timeout],
+  ['test-general',       'test-general.c',    test_nm_dep,       default_test_timeout],
+  ['test-nmp-object',    'test-nmp-object.c', test_nm_dep,       default_test_timeout],
+  ['test-route-fake',    'test-route.c',      test_nm_dep_fake,  default_test_timeout],
+  ['test-route-linux',   'test-route.c',      test_nm_dep_linux, default_test_timeout],
+  ['test-cleanup-fake',  'test-cleanup.c',    test_nm_dep_fake,  default_test_timeout],
+  ['test-cleanup-linux', 'test-cleanup.c',    test_nm_dep_linux, default_test_timeout],
 ]
 
 foreach test_unit: test_units
@@ -22,7 +22,7 @@ foreach test_unit: test_units
     'platform/' + test_unit[0],
     test_script,
     timeout: test_unit[3],
-    args: test_args + [exe.full_path()]
+    args: test_args + [exe.full_path()],
   )
 endforeach
 
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index ddef8853..d84a806d 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -189,7 +189,7 @@ test_ip4_address_general_2 (void)
 	inet_pton (AF_INET, IP4_ADDRESS, &addr);
 	g_assert (ifindex > 0);
 
-	/* Looks like addresses are not announced by kerenl when the interface
+	/* Looks like addresses are not announced by kernel when the interface
 	 * is down. Link-local IPv6 address is automatically added.
 	 */
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, DEVICE_IFINDEX, NULL));
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 12d91812..6c73a63e 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -52,7 +52,7 @@ test_cleanup_internal (void)
 	inet_pton (AF_INET6, "2001:db8:e:f:1:2:3:4", &gateway6);
 
 	/* Create and set up device */
-	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL)));
 	accept_signal (link_added);
 	free_signal (link_added);
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME), NULL));
@@ -128,8 +128,7 @@ _nmtstp_init_tests (int *argc, char ***argv)
 void
 _nmtstp_setup_tests (void)
 {
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE);
 
 	g_test_add_func ("/internal", test_cleanup_internal);
 	/* FIXME: add external cleanup check */
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 1cb2f516..c510710b 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -366,9 +366,12 @@ _nmtstp_assert_ip4_route_exists (const char *file,
 	                    &c);
 
 	if (c != c_exists && c_exists != -1) {
+		char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
 		g_error ("[%s:%u] %s(): The ip4 route %s/%d metric %u tos %u shall exist %u times, but platform has it %u times",
 		         file, line, func,
-		         nm_utils_inet4_ntop (network, NULL), plen,
+		         nm_utils_inet4_ntop (network, sbuf),
+		         plen,
 		         metric,
 		         tos,
 		         c_exists,
@@ -821,7 +824,8 @@ _ip_address_add (NMPlatform *platform,
 		gs_free char *s_valid = NULL;
 		gs_free char *s_preferred = NULL;
 		gs_free char *s_label = NULL;
-		char b1[NM_UTILS_INET_ADDRSTRLEN], b2[NM_UTILS_INET_ADDRSTRLEN];
+		char b1[NM_UTILS_INET_ADDRSTRLEN];
+		char b2[NM_UTILS_INET_ADDRSTRLEN];
 
 		ifname = nm_platform_link_get_name (platform, ifindex);
 		g_assert (ifname);
@@ -834,14 +838,14 @@ _ip_address_add (NMPlatform *platform,
 			s_label = g_strdup_printf ("%s:%s", ifname, label);
 
 		if (is_v4) {
-			char s_peer[100];
+			char s_peer[NM_UTILS_INET_ADDRSTRLEN + 50];
 
 			g_assert (flags == 0);
 
 			if (   peer_address->addr4 != address->addr4
 			    || nmtst_get_rand_int () % 2) {
 				/* If the peer is the same as the local address, we can omit it. The result should be identical */
-				g_snprintf (s_peer, sizeof (s_peer), " peer %s", nm_utils_inet4_ntop (peer_address->addr4, b2));
+				nm_sprintf_buf (s_peer, " peer %s", nm_utils_inet4_ntop (peer_address->addr4, b2));
 			} else
 				s_peer[0] = '\0';
 
@@ -1006,7 +1010,7 @@ void nmtstp_ip4_route_add (NMPlatform *platform,
 	route.metric = metric;
 	route.mss = mss;
 
-	g_assert_cmpint (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_REPLACE, &route), ==, NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_REPLACE, &route)));
 }
 
 void nmtstp_ip6_route_add (NMPlatform *platform,
@@ -1030,7 +1034,7 @@ void nmtstp_ip6_route_add (NMPlatform *platform,
 	route.metric = metric;
 	route.mss = mss;
 
-	g_assert_cmpint (nm_platform_ip6_route_add (platform, NMP_NLM_FLAG_REPLACE, &route), ==, NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_ip6_route_add (platform, NMP_NLM_FLAG_REPLACE, &route)));
 }
 
 /*****************************************************************************/
@@ -1052,7 +1056,8 @@ _ip_address_del (NMPlatform *platform,
 
 	if (external_command) {
 		const char *ifname;
-		char b1[NM_UTILS_INET_ADDRSTRLEN], b2[NM_UTILS_INET_ADDRSTRLEN];
+		char b1[NM_UTILS_INET_ADDRSTRLEN];
+		char b2[NM_UTILS_INET_ADDRSTRLEN];
 		int success;
 		gboolean had_address;
 
@@ -1198,7 +1203,7 @@ nmtstp_link_veth_add (NMPlatform *platform,
 			nmtstp_assert_wait_for_link (platform, peer, NM_LINK_TYPE_VETH, 10);
 		}
 	} else
-		success = nm_platform_link_veth_add (platform, name, peer, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_veth_add (platform, name, peer, &pllink));
 
 	g_assert (success);
 	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_VETH);
@@ -1225,7 +1230,7 @@ nmtstp_link_dummy_add (NMPlatform *platform,
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_DUMMY, 100);
 	} else
-		success = nm_platform_link_dummy_add (platform, name, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add (platform, name, &pllink));
 
 	g_assert (success);
 	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_DUMMY);
@@ -1240,7 +1245,8 @@ nmtstp_link_gre_add (NMPlatform *platform,
 {
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
-	char buffer[INET_ADDRSTRLEN];
+	char b1[INET_ADDRSTRLEN];
+	char b2[INET_ADDRSTRLEN];
 	NMLinkType link_type;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
@@ -1265,15 +1271,15 @@ nmtstp_link_gre_add (NMPlatform *platform,
 		                                name,
 		                                type,
 		                                dev ?: "",
-		                                nm_utils_inet4_ntop (lnk->local, NULL),
-		                                nm_utils_inet4_ntop (lnk->remote, buffer),
+		                                nm_utils_inet4_ntop (lnk->local, b1),
+		                                nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, link_type, 100);
 	} else
-		success = nm_platform_link_gre_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_gre_add (platform, name, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, link_type);
 
@@ -1288,7 +1294,8 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 {
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
-	char buffer[INET6_ADDRSTRLEN];
+	char b1[NM_UTILS_INET_ADDRSTRLEN];
+	char b2[NM_UTILS_INET_ADDRSTRLEN];
 	char encap[20];
 	char tclass[20];
 	gboolean encap_ignore;
@@ -1326,8 +1333,8 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 		                                name,
 		                                mode,
 		                                dev,
-		                                nm_utils_inet6_ntop (&lnk->local, NULL),
-		                                nm_utils_inet6_ntop (&lnk->remote, buffer),
+		                                nm_utils_inet6_ntop (&lnk->local, b1),
+		                                nm_utils_inet6_ntop (&lnk->remote, b2),
 		                                lnk->ttl,
 		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
 		                                encap_ignore ? "none" : nm_sprintf_buf (encap, "%u", lnk->encap_limit),
@@ -1335,7 +1342,7 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_IP6TNL, 100);
 	} else
-		success = nm_platform_link_ip6tnl_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_ip6tnl_add (platform, name, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_IP6TNL);
 
@@ -1350,7 +1357,8 @@ nmtstp_link_ip6gre_add (NMPlatform *platform,
 {
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
-	char buffer[INET6_ADDRSTRLEN];
+	char b1[NM_UTILS_INET_ADDRSTRLEN];
+	char b2[NM_UTILS_INET_ADDRSTRLEN];
 	char tclass[20];
 	gboolean tclass_inherit;
 
@@ -1373,8 +1381,8 @@ nmtstp_link_ip6gre_add (NMPlatform *platform,
 		                                name,
 		                                lnk->is_tap ? "ip6gretap" : "ip6gre",
 		                                dev,
-		                                nm_utils_inet6_ntop (&lnk->local, NULL),
-		                                nm_utils_inet6_ntop (&lnk->remote, buffer),
+		                                nm_utils_inet6_ntop (&lnk->local, b1),
+		                                nm_utils_inet6_ntop (&lnk->remote, b2),
 		                                lnk->ttl,
 		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
 		                                lnk->flow_label);
@@ -1385,7 +1393,7 @@ nmtstp_link_ip6gre_add (NMPlatform *platform,
 			                                      100);
 		}
 	} else
-		success = nm_platform_link_ip6gre_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_ip6gre_add (platform, name, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, lnk->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE);
 
@@ -1400,7 +1408,8 @@ nmtstp_link_ipip_add (NMPlatform *platform,
 {
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
-	char buffer[INET_ADDRSTRLEN];
+	char b1[INET_ADDRSTRLEN];
+	char b2[INET_ADDRSTRLEN];
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 
@@ -1417,15 +1426,15 @@ nmtstp_link_ipip_add (NMPlatform *platform,
 		success = !nmtstp_run_command ("ip tunnel add %s mode ipip %s local %s remote %s ttl %u tos %02x %s",
 		                                name,
 		                                dev,
-		                                nm_utils_inet4_ntop (lnk->local, NULL),
-		                                nm_utils_inet4_ntop (lnk->remote, buffer),
+		                                nm_utils_inet4_ntop (lnk->local, b1),
+		                                nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_IPIP, 100);
 	} else
-		success = nm_platform_link_ipip_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_ipip_add (platform, name, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_IPIP);
 
@@ -1473,7 +1482,7 @@ nmtstp_link_macvlan_add (NMPlatform *platform,
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, link_type, 100);
 	} else
-		success = nm_platform_link_macvlan_add (platform, name, parent, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_macvlan_add (platform, name, parent, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, link_type);
 
@@ -1488,7 +1497,8 @@ nmtstp_link_sit_add (NMPlatform *platform,
 {
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
-	char buffer[INET_ADDRSTRLEN];
+	char b1[INET_ADDRSTRLEN];
+	char b2[INET_ADDRSTRLEN];
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 
@@ -1510,15 +1520,15 @@ nmtstp_link_sit_add (NMPlatform *platform,
 		success = !nmtstp_run_command ("ip tunnel add %s mode sit%s local %s remote %s ttl %u tos %02x %s",
 		                                name,
 		                                dev,
-		                                nm_utils_inet4_ntop (lnk->local, NULL),
-		                                nm_utils_inet4_ntop (lnk->remote, buffer),
+		                                nm_utils_inet4_ntop (lnk->local, b1),
+		                                nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_SIT, 100);
 	} else
-		success = nm_platform_link_sit_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_sit_add (platform, name, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_SIT);
 
@@ -1533,8 +1543,8 @@ nmtstp_link_tun_add (NMPlatform *platform,
                      int *out_fd)
 {
 	const NMPlatformLink *pllink = NULL;
-	NMPlatformError plerr;
 	int err;
+	int r;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 	g_assert (lnk);
@@ -1579,8 +1589,8 @@ nmtstp_link_tun_add (NMPlatform *platform,
 			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);
+		r = nm_platform_link_tun_add (platform, name, lnk, &pllink, out_fd);
+		g_assert_cmpint (r, ==, 0);
 	}
 
 	g_assert (pllink);
@@ -1596,8 +1606,8 @@ nmtstp_link_vxlan_add (NMPlatform *platform,
                        const NMPlatformLnkVxlan *lnk)
 {
 	const NMPlatformLink *pllink = NULL;
-	NMPlatformError plerr;
 	int err;
+	int r;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 
@@ -1607,27 +1617,32 @@ nmtstp_link_vxlan_add (NMPlatform *platform,
 
 	if (external_command) {
 		gs_free char *dev = NULL;
-		gs_free char *local = NULL, *remote = NULL;
+		char local[NM_UTILS_INET_ADDRSTRLEN];
+		char group[NM_UTILS_INET_ADDRSTRLEN];
 
 		if (lnk->parent_ifindex)
 			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (platform, lnk->parent_ifindex));
 
 		if (lnk->local)
-			local = g_strdup_printf ("%s", nm_utils_inet4_ntop (lnk->local, NULL));
+			nm_utils_inet4_ntop (lnk->local, local);
 		else if (memcmp (&lnk->local6, &in6addr_any, sizeof (in6addr_any)))
-			local = g_strdup_printf ("%s", nm_utils_inet6_ntop (&lnk->local6, NULL));
+			nm_utils_inet6_ntop (&lnk->local6, local);
+		else
+			local[0] = '\0';
 
 		if (lnk->group)
-			remote = g_strdup_printf ("%s", nm_utils_inet4_ntop (lnk->group, NULL));
+			nm_utils_inet4_ntop (lnk->group, group);
 		else if (memcmp (&lnk->group6, &in6addr_any, sizeof (in6addr_any)))
-			remote = g_strdup_printf ("%s", nm_utils_inet6_ntop (&lnk->group6, NULL));
+			nm_utils_inet6_ntop (&lnk->group6, group);
+		else
+			group[0] = '\0';
 
 		err = nmtstp_run_command ("ip link add %s type vxlan id %u %s local %s group %s ttl %u tos %02x dstport %u srcport %u %u ageing %u",
 		                          name,
 		                          lnk->id,
 		                          dev ?: "",
 		                          local,
-		                          remote,
+		                          group,
 		                          lnk->ttl,
 		                          lnk->tos,
 		                          lnk->dst_port,
@@ -1641,8 +1656,8 @@ nmtstp_link_vxlan_add (NMPlatform *platform,
 			_LOGI ("Adding vxlan device via iproute2 failed. Assume iproute2 is not up to the task.");
 	}
 	if (!pllink) {
-		plerr = nm_platform_link_vxlan_add (platform, name, lnk, &pllink);
-		g_assert_cmpint (plerr, ==, NM_PLATFORM_ERROR_SUCCESS);
+		r = nm_platform_link_vxlan_add (platform, name, lnk, &pllink);
+		g_assert (NMTST_NM_ERR_SUCCESS (r));
 		g_assert (pllink);
 	}
 
@@ -1702,10 +1717,11 @@ nmtstp_link_get (NMPlatform *platform,
 /*****************************************************************************/
 
 void
-nmtstp_link_del (NMPlatform *platform,
-                 gboolean external_command,
-                 int ifindex,
-                 const char *name)
+nmtstp_link_delete (NMPlatform *platform,
+                    gboolean external_command,
+                    int ifindex,
+                    const char *name,
+                    gboolean require_exist)
 {
 	gint64 end_time;
 	const NMPlatformLink *pllink;
@@ -1718,7 +1734,10 @@ nmtstp_link_del (NMPlatform *platform,
 
 	pllink = nmtstp_link_get (platform, ifindex, name);
 
-	g_assert (pllink);
+	if (!pllink) {
+		g_assert (!require_exist);
+		return;
+	}
 
 	name = name_copy = g_strdup (pllink->name);
 	ifindex = pllink->ifindex;
@@ -1821,7 +1840,7 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 	if (e != 0) {
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "pipe() failed with %d (%s)", errsv, strerror (errsv));
+		             "pipe() failed with %d (%s)", errsv, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -1829,7 +1848,7 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 	if (e != 0) {
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "pipe() failed with %d (%s)", errsv, strerror (errsv));
+		             "pipe() failed with %d (%s)", errsv, nm_strerror_native (errsv));
 		nm_close (pipefd_c2p[0]);
 		nm_close (pipefd_c2p[1]);
 		return FALSE;
@@ -1839,7 +1858,7 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 	if (pid < 0) {
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "fork() failed with %d (%s)", errsv, strerror (errsv));
+		             "fork() failed with %d (%s)", errsv, nm_strerror_native (errsv));
 		nm_close (pipefd_c2p[0]);
 		nm_close (pipefd_c2p[1]);
 		nm_close (pipefd_p2c[0]);
@@ -1900,7 +1919,7 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 			             "child process failed for unknown reason");
 		} else {
 			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-			             "child process signaled failure %d (%s)", errsv, strerror (errsv));
+			             "child process signaled failure %d (%s)", errsv, nm_strerror_native (errsv));
 		}
 		nm_close (pipefd_p2c[1]);
 		kill (pid, SIGKILL);
@@ -2055,14 +2074,14 @@ main (int argc, char **argv)
 
 		if (unshare (CLONE_NEWNET | CLONE_NEWNS) != 0) {
 			errsv = errno;
-			g_error ("unshare(CLONE_NEWNET|CLONE_NEWNS) failed with %s (%d)", strerror (errsv), errsv);
+			g_error ("unshare(CLONE_NEWNET|CLONE_NEWNS) failed with %s (%d)", nm_strerror_native (errsv), errsv);
 		}
 
 		/* We need a read-only /sys so that the platform knows there's no udev. */
 		mount (NULL, "/sys", "sysfs", MS_SLAVE, NULL);
 		if (mount ("sys", "/sys", "sysfs", MS_RDONLY, NULL) != 0) {
 			errsv = errno;
-			g_error ("mount(\"/sys\") failed with %s (%d)", strerror (errsv), errsv);
+			g_error ("mount(\"/sys\") failed with %s (%d)", nm_strerror_native (errsv), errsv);
 		}
 	}
 
@@ -2072,7 +2091,7 @@ main (int argc, char **argv)
 
 	result = g_test_run ();
 
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE);
 
 	g_object_unref (NM_PLATFORM_GET);
 	return result;
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index 7e81baea..aa1f5460 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -19,7 +19,6 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <syslog.h>
-#include <string.h>
 #include <arpa/inet.h>
 #include <linux/if.h>
 #include <linux/if_link.h>
@@ -327,10 +326,11 @@ const NMPlatformLink *nmtstp_link_vxlan_add (NMPlatform *platform,
                                              const char *name,
                                              const NMPlatformLnkVxlan *lnk);
 
-void nmtstp_link_del (NMPlatform *platform,
-                      gboolean external_command,
-                      int ifindex,
-                      const char *name);
+void nmtstp_link_delete (NMPlatform *platform,
+                         gboolean external_command,
+                         int ifindex,
+                         const char *name,
+                         gboolean require_exist);
 
 /*****************************************************************************/
 
@@ -349,9 +349,9 @@ _nmtstp_env1_wrapper_setup (const NmtstTestData *test_data)
 
 	_LOGT ("TEST[%s]: setup", test_data->testpath);
 
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert_cmpint (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL), ==, NM_PLATFORM_ERROR_SUCCESS);
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE);
+
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL)));
 
 	*p_ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	g_assert_cmpint (*p_ifindex, >, 0);
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 057490c4..71324301 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -47,7 +47,7 @@
 #define MTU 1357
 
 #define _ADD_DUMMY(platform, name) \
-	g_assert_cmpint (nm_platform_link_dummy_add ((platform), (name), NULL), ==, NM_PLATFORM_ERROR_SUCCESS)
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add ((platform), (name), NULL)))
 
 static void
 test_bogus(void)
@@ -77,7 +77,7 @@ test_bogus(void)
 	g_assert (!addrlen);
 	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
 
-	g_assert (nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU) != NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (!NMTST_NM_ERR_SUCCESS (nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU)));
 
 	g_assert (!nm_platform_link_get_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
@@ -107,30 +107,30 @@ software_add (NMLinkType link_type, const char *name)
 {
 	switch (link_type) {
 	case NM_LINK_TYPE_DUMMY:
-		return nm_platform_link_dummy_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add (NM_PLATFORM_GET, name, NULL));
 	case NM_LINK_TYPE_BRIDGE:
-		return nm_platform_link_bridge_add (NM_PLATFORM_GET, name, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return NMTST_NM_ERR_SUCCESS (nm_platform_link_bridge_add (NM_PLATFORM_GET, name, NULL, 0, NULL));
 	case NM_LINK_TYPE_BOND:
 		{
 			gboolean bond0_exists = !!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "bond0");
-			NMPlatformError plerr;
+			int r;
 
-			plerr = nm_platform_link_bond_add (NM_PLATFORM_GET, name, NULL);
+			r = nm_platform_link_bond_add (NM_PLATFORM_GET, name, NULL);
 
 			/* Check that bond0 is *not* automatically created. */
 			if (!bond0_exists)
 				g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "bond0"));
-			return plerr == NM_PLATFORM_ERROR_SUCCESS;
+			return r >= 0;
 		}
 	case NM_LINK_TYPE_TEAM:
-		return nm_platform_link_team_add (NM_PLATFORM_GET, name, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+		return NMTST_NM_ERR_SUCCESS (nm_platform_link_team_add (NM_PLATFORM_GET, name, NULL));
 	case NM_LINK_TYPE_VLAN: {
 		SignalData *parent_added;
 		SignalData *parent_changed;
 
 		/* Don't call link_callback for the bridge interface */
 		parent_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, PARENT_NAME);
-		if (nm_platform_link_bridge_add (NM_PLATFORM_GET, PARENT_NAME, NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS)
+		if (NMTST_NM_ERR_SUCCESS (nm_platform_link_bridge_add (NM_PLATFORM_GET, PARENT_NAME, NULL, 0, NULL)))
 			accept_signal (parent_added);
 		free_signal (parent_added);
 
@@ -147,7 +147,7 @@ software_add (NMLinkType link_type, const char *name)
 				accept_signals (parent_changed, 1, 2);
 			free_signal (parent_changed);
 
-			return nm_platform_link_vlan_add (NM_PLATFORM_GET, name, parent_ifindex, VLAN_ID, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+			return NMTST_NM_ERR_SUCCESS (nm_platform_link_vlan_add (NM_PLATFORM_GET, name, parent_ifindex, VLAN_ID, 0, NULL));
 		}
 	}
 	default:
@@ -343,7 +343,7 @@ test_slave (int master, int type, SignalData *master_changed)
 	ensure_no_signal (link_added);
 	ensure_no_signal (link_changed);
 	ensure_no_signal (link_removed);
-	nmtstp_link_del (NULL, -1, ifindex, NULL);
+	nmtstp_link_delete (NULL, -1, ifindex, NULL, TRUE);
 	accept_signals (master_changed, 0, 1);
 	accept_signals (link_changed, 0, 1);
 	accept_signal (link_removed);
@@ -439,18 +439,18 @@ test_software (NMLinkType link_type, const char *link_typename)
 	free_signal (link_changed);
 
 	/* Delete */
-	nmtstp_link_del (NULL, -1, ifindex, DEVICE_NAME);
+	nmtstp_link_delete (NULL, -1, ifindex, DEVICE_NAME, TRUE);
 	accept_signal (link_removed);
 
 	/* Delete again */
-	g_assert (!nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME)));
+	g_assert (nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME) <= 0);
 	g_assert (!nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
 
 	/* VLAN: Delete parent */
 	if (link_type == NM_LINK_TYPE_VLAN) {
 		SignalData *link_removed_parent = add_signal_ifindex (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, link_callback, vlan_parent);
 
-		nmtstp_link_del (NULL, -1, vlan_parent, NULL);
+		nmtstp_link_delete (NULL, -1, vlan_parent, NULL, TRUE);
 		accept_signal (link_removed_parent);
 		free_signal (link_removed_parent);
 	}
@@ -466,12 +466,20 @@ test_bridge (void)
 	test_software (NM_LINK_TYPE_BRIDGE, "bridge");
 }
 
+static int
+_system (const char *cmd)
+{
+	/* some gcc version really want to warn on -Werror=unused-result. Add a bogus wrapper
+	 * function. */
+	return system (cmd);
+}
+
 static void
 test_bond (void)
 {
 	if (nmtstp_is_root_test () &&
 	    !g_file_test ("/proc/1/net/bonding", G_FILE_TEST_IS_DIR) &&
-	    system("modprobe --show bonding") != 0) {
+	    _system("modprobe --show bonding") != 0) {
 		g_test_skip ("Skipping test for bonding: bonding module not available");
 		return;
 	}
@@ -502,7 +510,7 @@ test_bridge_addr (void)
 
 	nm_utils_hwaddr_aton ("de:ad:be:ef:00:11", addr, sizeof (addr));
 
-	g_assert_cmpint (nm_platform_link_bridge_add (NM_PLATFORM_GET, DEVICE_NAME, addr, sizeof (addr), &plink), ==, NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_bridge_add (NM_PLATFORM_GET, DEVICE_NAME, addr, sizeof (addr), &plink)));
 	g_assert (plink);
 	link = *plink;
 	g_assert_cmpstr (link.name, ==, DEVICE_NAME);
@@ -518,13 +526,13 @@ test_bridge_addr (void)
 		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_EUI64);
 
-		g_assert (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, TRUE) == NM_PLATFORM_ERROR_SUCCESS);
+		g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, TRUE)));
 		g_assert (nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 		g_assert (plink);
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_NONE);
 
-		g_assert (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, FALSE) == NM_PLATFORM_ERROR_SUCCESS);
+		g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex, FALSE)));
 		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 		g_assert (plink);
@@ -534,7 +542,7 @@ test_bridge_addr (void)
 	g_assert_cmpint (plink->addr.len, ==, sizeof (addr));
 	g_assert (!memcmp (plink->addr.data, addr, sizeof (addr)));
 
-	nmtstp_link_del (NULL, -1, link.ifindex, link.name);
+	nmtstp_link_delete (NULL, -1, link.ifindex, link.name, TRUE);
 }
 
 /*****************************************************************************/
@@ -554,11 +562,11 @@ test_internal (void)
 	g_assert (!nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
 
 	/* Add device */
-	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL)));
 	accept_signal (link_added);
 
 	/* Try to add again */
-	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_EXISTS);
+	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == -NME_PL_EXISTS);
 
 	/* Check device index, name and type */
 	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
@@ -595,7 +603,7 @@ test_internal (void)
 	g_assert (nm_platform_link_supports_vlans (NM_PLATFORM_GET, ifindex));
 
 	/* Set MAC address */
-	g_assert (nm_platform_link_set_address (NM_PLATFORM_GET, ifindex, mac, sizeof (mac)) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_set_address (NM_PLATFORM_GET, ifindex, mac, sizeof (mac))));
 	address = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, &addrlen);
 	g_assert (addrlen == sizeof(mac));
 	g_assert (!memcmp (address, mac, addrlen));
@@ -604,12 +612,12 @@ test_internal (void)
 	accept_signal (link_changed);
 
 	/* Set MTU */
-	g_assert (nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, MTU) == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, MTU)));
 	g_assert_cmpint (nm_platform_link_get_mtu (NM_PLATFORM_GET, ifindex), ==, MTU);
 	accept_signal (link_changed);
 
 	/* Delete device */
-	nmtstp_link_del (NULL, -1, ifindex, DEVICE_NAME);
+	nmtstp_link_delete (NULL, -1, ifindex, DEVICE_NAME, TRUE);
 	accept_signal (link_removed);
 
 	/* Try to delete again */
@@ -684,6 +692,237 @@ test_external (void)
 
 /*****************************************************************************/
 
+static guint8 *
+_copy_base64 (guint8 *dst, gsize dst_len, const char *base64_src)
+{
+	g_assert (dst);
+	g_assert (dst_len > 0);
+
+	if (!base64_src)
+		memset (dst, 0, dst_len);
+	else {
+		gs_free guint8 *b = NULL;
+		gsize l;
+
+		b = g_base64_decode (base64_src, &l);
+		g_assert (b);
+		g_assert (l == dst_len);
+
+		memcpy (dst, b, dst_len);
+	}
+	return dst;
+}
+
+typedef struct {
+	const char *pri;
+	const char *pub;
+	const char *pre;
+} KeyPair;
+
+static void
+_test_wireguard_change (NMPlatform *platform,
+                        int ifindex,
+                        int test_mode)
+{
+	const KeyPair self_key =
+		{ "yOWEsaXFxX9/DOkQPzqB9RufZOpfSP4LZZCErP0N0Xo=", "s6pVT2xPwktor9O5bVOSzcPqBu9uzQOUzPQHXLU2jmk=" };
+	const KeyPair keys[100] = {
+		{ "+BDHMh11bkheGfvlQpqt8P/H7N1sPXtVi05XraZS0E8=", "QItu7PJadBVXFXGv55CMtVnbRHdrI6E2CGlu2N5oGx4=", "2IvZnKTzbF1UlChznWSsEYtGbPjhYSTT41GXO6zLxvk=" },
+		{ "qGZyV2BO1nyY/FGYd6elBPirwJC9QyZwqbm2OJAgLkY=", "v8L1FEitO0xo+wW/CVVUnALlw0zGveApSFdlITi/5lI=", "/R2c0JmBNGJzT594NQ0mBJ2XJjxt2QUSo+ZiqeY0EQA=" },
+		{ "YDgsIb0oe+9NcxIx2r0HEEPQpRMxmRN0ALoLm9Sh40Q=", "nFPs1HaU7uFBvE9xZCMF8oOAjzLpZ49AzDHOluY1O2E=", "zsYED2Ef7zIHJoRBPcen+w4ktrRsLPEfYwZhWIuXfds=" },
+		{ "kHkosM503LWu43tdYXbNwVOpRrtPgd9XFqcN7k4t6G4=", "b8e092WT+eNmnxCr5WE2QC/MXAjDagfG1g03cs2mBC0=", "VXz0ShGWT7H0CBCg2awfatmOJF15ZtaSMPhMsp+hc3A=" },
+		{ "4C2w5CEnxH59Y2aa6CJXgLdDtWoNMS2UJounRCM1Jkk=", "gC/R9umlnEQL+Qsz/Y64AlsdMge4ECe5/u/JHZCMWSs=", "2bmL5ISr+V5a7l7xFJ695BLIJyBgx8xnxzybkxiRHOg=" },
+		{ "KHJSzFGkXcZf/wbH2rr99SYtGKWbL680wA2AcDE94lo=", "BsN23h4aOi458Q3EgMQsodsWQxd9h/RxqskAUpsXfgg=", "nK4Zv34YKEhjuexhq1SgK4oTd4MZJT5gcpYvEuPjc7Q=" },
+		{ "QGMulXJ9e3AVxtpi8+UUVqPOr/YBWvCNFVsWS9IUnUA=", "kjVclP5Ifi6og2BBCEHKS/aa/WktArB4+ig06lYaVlg=", "0+mmceDPcSRK3vFnYqHd9iAfY+Nyjzf/1KgDeYGlRkQ=" },
+		{ "AOJiDD4y6GA7P7gugjjQG9Cctvc5Y27fajHz6aU3gU4=", "gEnHn6euHtcMEwZBlX6HANPeN9Of+voBDtltS38xDUw=", "wIH1OxgX6GLxx/bnR+t3fbmjGZDTU3WMxp7t1XGezqM=" },
+		{ "COsls2BlCltaIrrq1+FU51cWddlmoPPppSeIDunOxGA=", "+n6WuV8Tb1/iZArTrHsyNqkRHABbavBQt9Me72K2KEc=", "t4baiprSO9ZbKD2/RutOY9cr+yCajQWZGCTnQdrFQj0=" },
+		{ "uHawQq2BRyJlsTPoCa+MfBVnv4MwtRoS+S9FEpFOEVg=", "8lcmr27afeb6iI3BQCaDtvalF2Cl7gxRZgs+nyJ/fEg=", "Eh9o/6W60iujBLIHuRfNrPhOWAn7PambT2JRln9iwA0=" },
+		{ "yL7hmoE/JfRGAotRzx9xOpjfrDA3BFlPEemFiQw40Wk=", "BHK0PHi5kp7rOfQ46oc9xlVpB+pZeZYXTtH7EXr5TwU=", "BS2h2ZZyW0BlYMmLR29xyHEcZ4jtO7rgj1jkz/EEaxU=" },
+		{ "ON8YrTHQgoC5e0mAR9FakZ8hZ/9I7ysuE21sG546J1Y=", "Bm3l5I6iH1tDrv6EgdZU9PzHqp0H26Z6ggmmIypwxy8=", "qKVfbCnK1EI3eC28SsL+jyBnEnIF/nRJQJzDevBFdNQ=" },
+		{ "KGLO0RI0VlQFCG+ETUqk25HdaKUnUPXKOKkTh/w8BXU=", "sBDBwQFC7k8UMdMzuavKBkBjYYKigzvYmGF5rm6muQc=", "BNfZF9d7540pUGGOQCh3iDxAZcPBqX4qqM00GSLPBek=" },
+		{ "KGdWyEodIn7KKI2e4EFK9tT6Bt3bNMFBRFVTKjjJm2E=", "lrbjU/Xn9wDCZQiU8E5fY6igTSozghvo47QeVIIWaUk=", "LczqW48MW8qDQIZYRELsz/VCzVDc5niROvk7OqrTxR0=" },
+		{ "wO3xinUGABgEmX+RJZbBbOtAmBuPPFG6oVdimvoo90w=", "dCIvTzR6EerOOsRKnWly1a9WGzbJ4qc+6t3SSzLgWFk=", "wFj0zpr5PadBoBy0couLuZ1qudZbXLbV/j3UT+AyKeo=" },
+		{ "+JNOBlO4tp9vvQk6UO4r3sILyEgjl+BBoWketZufyn0=", "Q6LSv9y7YQkJEzQ/1mpjJEgOrO8GYPUTgcizjh7Cm34=", "kg7AG9MuN04xPJ5Z0IcNZ4a8d1b/n4GsGIeyA4FaaSE=" },
+		{ "+EJcThLRwjZ+h1CNNFu15HWzznf4u/lPVw8hifTm2Ec=", "Kkn2jFqwBzyIFQfD0OpmePFSmBYmhKagv4pGgkqsWgE=", "jYpxojj8WKYe/XIXMP+uv1Fv0+TKrs83tqfzP0AGdcI=" },
+		{ "+DKFqSMNFmxriEFj3qatuzYeTJ9+xWYspZ4ydL3eC0Q=", "3o37bsg6HhRg/M9+fTlLcFYc2w/Bz9rLQySvvYCKbRE=", "Jb9qoDIBat1EexlgfpRbXa7OflptME8/zt93bldkiVE=" },
+		{ "EH3MjFOMqRoDFQz+hSlJpntWBeH3lTk6WPjTIQjr42o=", "PbPewED/nxSBLdM7AXMj7uS3bCgAAg8M6F4iPLd0b1U=", "pj4+UgOGkpJwlRvX5BRXZzmzAUnDtUtJsS7LzbcJWzw=" },
+		{ "kL6M2KvO+vPBLEc/a0DpEHTibQ1bwaMRT9b9SkzeP0Y=", "pS3G2bHHlkOE6UHP0qVitDuxXgjEaZTviTjNc55RbVs=", "ZVZpWOtYqhX3CpF1kATg/38J6pvUJo8AS1sVYjs3rUE=" },
+		{ "sFlcRLDn36fnew2Ld92IHJwnKifdS3aF1MWRPs6K3Wg=", "OpjUOTiWExaDYULTINB4yQFqc3mnU3RjQzGRV+KtdFY=", "of0V/uoFRNljv/XTt/tXgoquLRH93Ty0KNiaPUpEi2w=" },
+		{ "UJ8hjDsg3jfsnnfPH8Gw9FnCb6taTuviurAfZu+kEFg=", "3byjHksUOv8CNjGGKvHvvrJDURBhCIL5UtfZbgyVWCE=", "9f7dbWif51gGrE7R9LeuewQSrvGFGTOB3ceJC67jSkI=" },
+		{ "iFFPKGIfqeUKY/w72KAZSjd/PGTqCakHYBV10xMDfnI=", "ehneHATNSXtsJTOiPjVSc0QARkihgcfcvoXKFWKfQnI=", "yKdqDBcRwA7RCg4GiY/b5IsWcExPleOBde/hjxc36a4=" },
+		{ "GDGocdPJTPUAllxQo7SpXZKqMPn7lpxQELQUX9ETHmE=", "n0ScNEou4ekfrXRRXvcADLu2Afj8g5D3TuDP/I4KrnY=", "8QhswqAhi/ehhcmCwQF5aSh80TvIGC/gRL5jBn5wOH8=" },
+		{ "UCcrlN8fX2ZdNdhaEBNwktwL+H0ZO7fhaj7rgdUutmw=", "LF8J728ilXs4TphnrgR6r0p7W3912DYnsXkGMEPQnRE=", "cYfMjxl2REYir7frGeB0u+NHAaFYF02ysgpBhOL5ygc=" },
+		{ "SH0657XuIiHidVmViXZF30RUWtkuWXcWWHmKZHTiOG8=", "7k6j49W1u5qgLE5MQUc1osPVW1oPPhzjrGvJ7o9YamY=", "dV2+7rNk/3LR2IcwYg/c+Wvzep1yjY7/u1I+nnlTQ00=" },
+		{ "qFQWs6jzrscV42pGISQyA7JDvFFAvEQCWJi584VHD2g=", "AT63nHKLC17yUvkR4lOVPxCr4DD3QhXmcmecOTn+Amc=", "2Qe2fwJbFcu1CmKpktElOFkSQMqlyvlV3ZUIAd/Dcts=" },
+		{ "6J+yLxgPwWtUbk9I3zbeD8RuK6XkQjJ0wTJ1zSVhflQ=", "NJzMBYyPZjk3eLmgdKaOeWyNER5YZF1mR8Umeiu9f28=", "pp+4+XHw5ZmHGJ7WbZ1xLYRsnTI17QIbb0bzHzYZrBs=" },
+		{ "KJGoWYNVDUWcEMg+E4tljv1LiWAbdRw2QVapYqdFa1Y=", "M2SGk9WVnzYNGnT777G/JE8uUsY2f7mszTwlue73UDE=", "Jg8N7GbhbYB400foFP+OH0v+hCaL1jW61bajSA6EZqc=" },
+		{ "uAgrgppPyIvk8S1CHUmaCaORsgFFfBreB8pxXmbSdXw=", "dJ22bER4fD3qF2/yIGWQ7SgmZ990sy/SbANjmUMkzws=", "mKkd0OoAR9sClmD+k3fL9weBsoCy5GQGz9BP0kAQIjc=" },
+		{ "EI3Q8gePNPrtFyoMcv7hOihQgroF/dfjzPn8yvpGPWQ=", "Y9QhJFeiyuuIZNPU56B6U//ZK+XTTe4EP7h/p3Q+dE8=", "qBVUYw9rTWaxn55nUd55NpCWtxOUSWLt4WJGusiDS8Q=" },
+		{ "oEQvdRm/yHkx+JvlhHGT5RUFrFEUleKb9DCT55EqZ30=", "8hsh/UHwWADTHntOJq4dy0o7ahcNAAlo2rDpjzzrVXk=", "/GF2inW2mPtA26IgFdgOEBbBEerT740wWuP/8NyANdc=" },
+		{ "6OWrGKZKNsgfRezSUw29EnHymcgKyEKvX5/pZQlLmWs=", "oaJeO6YSS2dodNEf97DvgWrYnFFelG5daEdN84jVVGw=", "T5wvTdyVxK0LY96kouLjs06oUfhGChfty8OUL1Mddro=" },
+		{ "mPmzQbh2R+r1DC5hSquzKM1SDrxUdfnBPRPJrpqrgkE=", "BtUHAnYWjDjBI42qBf9dJqezTUikYsF96o6PKEWPrVo=", "MxU8EMmq+vVHpuK/AkFZrZDF2b+VbSqukZLPbNsCcgo=" },
+		{ "8GVxuoo1Veyr+nqxr5Q4vmsMf5qfiXwSlQ4q3+BU60g=", "uSOLe/E9/OjIgUOk0NMBHB45E0+q4Rd+IUO2UOxmKlM=", "+30F+56OE0Sr3wY2clKw4kgE+2XiucMg7xjK6EemXuk=" },
+		{ "qORPKb+qFuU/9TpFbRUupHsqm9iyk9pa6cpik+EVDkg=", "bMZxxd+Z9I0XA1h9U8JEY+/mRxWGnvbXDZ5Dxz7YzS0=", "SmkkqOz4OhHuSL6cxuRm9+Mlt50Sfd0sMDFTC78gqOE=" },
+		{ "2Ko3IYhXKcdOMIJGNpASk9saNSZsI64lyJPOoxpQ2kI=", "xVOc9PxY1VFaZfemmKi+Ei2liHhmeTu+JMa+rS00gnA=", "+398DlW8kWeI2aRaC4QfcrEjwqPKCohyDeWdaI1wvv0=" },
+		{ "CPioGCVpxnym62nH/QoCt1RiiaaxUcuFjvh5kRhjqHA=", "W0XxlBLrZgFKhggMvvv6oFf/RJbfs92qv8JK9e+5i2o=", "bsK2U6CRAUv1uVgYQ7NpqjWWswFIDiDPDEtU1XQygSA=" },
+		{ "AF17siKaeiO85hikYN0IWCMGWqPm1UOoCkXMltJGUVk=", "B+PFos9aN2S5bLxzGZHljRZj41j3rIx8RWu0vDUzq1w=", "Qb8d6iDYv3m1h7PE8j0Exl2cSwpHkim/fJ1S4P7MYvY=" },
+		{ "wMFDBTJzx5tDCBhMkrptYJ8w9EeURjc4xeDQpevxAWo=", "4ec439EXE5WQzvtV9reSX+aMmdq5k7o9Ayt8oQp+RhQ=", "jwQlvdNH5WtSSU10H+fh/JisOlaBaohDPEp/BYnTt1Q=" },
+		{ "4GaJpIFigNDwd31O84pLIMM/o2qhp0ydlI/ydD/2a2Q=", "r/LdkCoK5/BPGdq2+XJO8sCRhI+8ULFmg0887V43PAI=", "Da+3ZZvEJdx4TYFMIDUlbkmytILnSTNxTKX+sQdjMd4=" },
+		{ "gMnojGqCLmMfGp2m31xlKZ/rIV2b8ockw9DPahRyu0c=", "H6tKCTosnM4BXKqflXrkTdJyNlCIZhQ3ZRxfrvSdrDk=", "4Z6K3LKIMV89plcjMb9CzSzJl03SWRe/++geBMZcOtY=" },
+		{ "wKCg22aNNoHnDJ0oAKE46FcSSsREW4AaGn5WxCSeXUs=", "9NDTFC0iPt4HbbWepLHhN5poNTN2fdxJKNadsNT7qzY=", "GSVTOCnfLpJ1VCOLHaKSjCMv7/OlcnQiP5+5woqkud8=" },
+		{ "oCoykq7pcJcg2X2V5TBRzGwn8hzzHC05WUreuotdznY=", "DxfwnbMqr5Wn5SAyFolfEmNQT6l84Oq69ngpg6H6Iio=", "p1RHBuqhuDa1MAQ2lbqmUQFu0CTwYlf73fWZSj9tQhA=" },
+		{ "UO7YVyRUVkcKr7c63VWWV2zj36XD3HyDfLZCqvrZmFc=", "360lzYtIyHq5lv/QXSCe4bL4G2J1jBXFJ8yS+Ycr7Bc=", "RRPQ1XWF1HN8Us4dtfn2eemdjgtWm7U8r7mM3y00NOk=" },
+		{ "eCGFYV/NuGP4H552E8Of1xU+3IvZxGyX+p5UFGW8iHI=", "LqhZ4AS9dQ/MhsQnE5Oy7Q8INXY+P+mrfGY5dtg9SlE=", "SICfqs8T5wP6IzATDCT4ovamBKPdkZ7JP4Cfsb3izec=" },
+		{ "oI3HBZknoIMMZw1BuYMkTBylt25reX7AbCqtWQv8cno=", "B7dUgLgvQhi0RGmvaMrmf26WdjEVrhaiBclVkCKd4AA=", "5O9K9pLXwxFAt5lfMWh4qGbwX1BM7sz0QGYxAnR77dk=" },
+		{ "sBfYn14EFVIS2M/E1aahP7mOmRNbNtyDChDMS1s6aGs=", "FLYv0ZvzxMkc9A7OzhC4P1ZRu1aKIQd7u6gqfdekC3c=", "kaYLcNCXnCLgiB8fleMQuboUJsj5u3YAmXL9x3ywV0M=" },
+		{ "qFwZESU/XYZXUtxwrGsFU9qPAFTzjm7EhTS1Q6ajGlM=", "hraQQaqJCkS2yQXv+ccMOVh9V9a/qgSZJgdMAhrt8ms=", "72oDfnWOn39gdk/ncw8Lv267I0I+m73SwxrpYojpWYk=" },
+		{ "YHhp6Zf/miuc2QXeI2lTezy0lL0pTv2b+nWNkmjYQWI=", "UhNO5arLzF0WlZSgNOx7+IjWN+GSxDdQxZRp8uIwsyI=", "1Q39Nzv2NGI9zWKWMpYLURAMZUg+FP+OboHHzFU8Anc=" },
+		{ "mLBBXKaCJ+7qeBZpS3wxGi/SQ4kLzun+K+QwwdwfJVQ=", "gIq/nh7NwCJ36MvRnyrWHaRWu8lTmwfN2NvsjVl6SXQ=", "AahcNR91GDyJBIP+vC8ZuIV8ukqjSGtd8s+cmjVC2Ao=" },
+		{ "OJG4LZlNNngFtAEQdbVVWVm6QAjOOauGcMZGbQrb40M=", "pFHAC6HaWAOtvTRRVfSHvzG05mp4SJZXKsN/tkSF0kM=", "IoXT3wIqWNxQhYuHWl12ODq/P7RM9LwaqglhmjKg+0g=" },
+		{ "OOwBFOQNhepiqDf04DehQLh1gpBNOluDF1ia752Yfng=", "u715uJ/XhdjXjThCTJ9w6zzXnIhp3VCxhtso1wk+oBQ=", "5x1Ip3Ym0KzDjGhiYjmpeWWr+dgrZlYwfr02GngPOTM=" },
+		{ "eNPFnwkQy1qw+IjFAlrDA6+sIsxbWDlzbNSsBW8R1UA=", "OaOXaAfPb1MRpWadawFje8YZ0oxJgdCIDIP8c5X+r1U=", "NtfaRRD0GqujnaQQoNoBbtovgO4dfVwEmEQx/YgnDpw=" },
+		{ "OLdaZItbtxH3mGqItkibIJp7KV27FrQavjhd5zq6s3Q=", "SLSmAYxkMCGj0DO35cMLkC3NVAqK2VmVFndbOZEdA24=", "SnBO68XQTDjxYbmYaAeEHgLwD2u4D+BPT86raRuUQZM=" },
+		{ "UBQOEz08izwr4eEK/SnQUpkt+TxCjo6Sya/XOGMLOE0=", "wQwrwezI9LzKevGsJJCBHDG8noR0yIEtOK5Rig97SSo=", "DpyS+0d7lrlFWkztsniG2v/j44vcuvWz3sPeghRyb5A=" },
+		{ "mN98iuqUKh67ggUdq9ZIQNZCZM90fgycTVqYKEo+DkY=", "GYdXVW1jpS0dN1q9zMehubP7LfYqs34kszN0bXQqxxA=", "AJPIHffB4uvvJJki4xCG0VORVBbF6bc2mZQqUx+idPc=" },
+		{ "OEd/1it8C3o+NOWxDI3DfLMXVBHJQg15N3E8F8d99l8=", "QL1NcuUkoXxDy7M9VjGslCejcUlnUDHRghFVnr+8fmA=", "nven9Dicl8U6QXuDO8rRNtjd4NYaa90SU+Gmv435XKY=" },
+		{ "AFMCGDu2oAP68miucsi4fmYX2KeRZnsEGv8tQm8JEng=", "1sNxvk8uZhFsBUgxOXmuCMjDAgBbjVeWe9oaFk5Osy0=", "t5iI5XXd56S5q0Y9HC91gzgF9uGjL9FIy6NUaKqkydo=" },
+		{ "CIAwfJghQHHr4YlztN9at6/iWkrEVCGFAxNVuQCuT3I=", "zpUOF1h17g7RpBzrVlN7oTRz6e+dxcDL8OsAtHwgLC8=", "kOSwC1p6Uoti9E9Eg7ViPZwCytuvp5Fr5Buw677aogU=" },
+		{ "sC8vrAVBU0zvhWDRfzfySjvopXm2/cTMkTLmioyO3Es=", "p6H7GWm8NfgyO5OCX/COjvVT4MAnTs9ZUj4uZMK8XHA=", "9Tzqo57V/h7+6nSNAHSBKdmU7ultlvZbAnNKSRlrLi4=" },
+		{ "eI63gjxCZGnzqZxPEi/ifYphXhxIRI2ZxK8jzqo3mmU=", "XyNzEuU9x37fxFCnrZH89Krs5/UqGVx5wNkGfQCAYz0=", "vZ2fTlRPnJQ+q33YdS5p1aweqPGj/kTMc4Uq80FtFjI=" },
+		{ "aNJlGtm79/RS4SQ/PC4YM6LFo9zAqDr2/RjLqk/z/1A=", "lgZ9akPrABmfHQMlfNFnnpAJzGtcsaU9mUjEYKfzZHc=", "d0Xt1Bcgphd1HMI0RneA4VdBbMZL1qNGJAvFhb080eA=" },
+		{ "oONSnHirNh3cuH93Ty0C9AXKebGY+cdF3R0DtPzIQlo=", "TuREKfA8EVQiYWsPx8veUzjN2cz/b72limSLWlrCWxw=", "vEqqKbpZf0EM6EApMUaUH65r3Zr81Y/DSODhE4H7U3Y=" },
+		{ "+D6RyLEaHJ9YF9WDyOlwh87KaNJcc6lqX8Arp6yqHF0=", "EpecjfIo1/EEbmsgUtzEDqLu2ut+SMmzqaBL9Z/MlCA=", "oYfO6/7XQgEYT9zmr4sqFrk0muK/fEv3FfD8MzZzjkE=" },
+		{ "OCmW5KQql2PRMJnsMYQjXlr6TSYUbxJBknqZtXJPSHM=", "ZZR2ghHlCwAJu/XlsEZuNS6XiGPwuXzyMPPywYFapVM=", "fqSCXq+pKJJ6yNvlOi+tyQ9E4Y6kc4kblGrVqN2WuXA=" },
+		{ "APWXDAe8d2ia1CUbf/IzSPXOUjR8TVuJgmISiWw0/EY=", "jrT5P5YCkG+U7cfNTvCKy0GSEgsjwmJtHg+8HBP6ZCA=", "t75aUjZXMPir8Ao0yhVClh9/BdWxSL+11CjK3iELNWk=" },
+		{ "8Nw4sRis7M/6Om+3w5YHXthyMzLGuP48teqdzbHNPlA=", "lj3q3ZYij3ZJ/QunK8n9I00cv/Z+O1TU1kFFl8x3DTE=", "adqB79P7gbXEYYnSd1/UPCwFffTAPXa9kHWynRBYcGo=" },
+		{ "yOupps5XbjV0fIZKnGhrpcxB7yDQzbBILC0UMJyVS1c=", "+MDV/t9UCIdgm3IkH3BZlxaRPJ3lejRmrm4UPApq4mk=", "AOJPmQxsU6hjOd+9mHnF0VL7Afih3P1Fr625xFT4FtY=" },
+		{ "cD2DEl4MBwONuTV0db5XreoVjQAUZFNXqIeFEU3KFkE=", "2CeHrjN7tBX48k4Sgv5fIHG06e57q/ucCL+8DIRmfXw=", "3EUo6MRzs6rSoY/7AFs8wiBiTXPcHzerLh6Xp3aMGKo=" },
+		{ "EA+S3a9ZeOLiRbhTxaT2wkpyDheAmai+UJa6SFGzSm8=", "GGByUKZx/FPa2OkJoqVTHXx+6jrIpIw5rf0rp43MHko=", "BXoDA3yn0JcMV7hHVzEqhlwAORvhToFO1qG00nas92A=" },
+		{ "eBeJi/imBiV52WEqrwAprUQggqdQmvTTmWtLq8pDDkM=", "zCX26ZTOZHLpq5x5aIUL1XhIVoXJLp/zcXwnmFA3jBo=", "Dm/DCxXWYXEsmQgxAD3KREK2PF0bUSnV5WRAaya8s1I=" },
+		{ "8C7p+EQO+CnWUSjHVu3PpeWpUIbLy48zpftZu021plM=", "DxpnF/IbKAh6kmWC5Jpj8iw387EDkrvjsjOb9fbTSng=", "bGrk0OshJB+0oQOK0QGKU8+lotnIDz3oeUnMZGienyM=" },
+		{ "COIez7YcBJiOJCLxxWV5UGLW5/o009YI0aszlD/PiUc=", "eD9USWV37LFIOxlDSHyOmfFqNJFpORRlzEI+HoF/czI=", "n+/ra86gUSF2pNZS51nt2JgrzXnQJl+dWswOq/Ahs94=" },
+		{ "iBBSTG9VLC+T9+ahNaQ4umZoig8o7w1DaeOw+cD2BGc=", "XnAxqDlvGnQ6aakv8ABGHVj07qVQfk4NChZbstTMBxg=", "7KKSwu/4yWr1UzFmNMGtiaSwdYMhP/HKbrQLlABL4UE=" },
+		{ "eNmwattflehr9+KsVqTuwt1YaAc5ONkaIaTQt9Gkhn8=", "1NNVvm++YTTGMKyAXfGOCZ4aDDdFFH5Um3vAg4XimDo=", "bXNrnDTP0pBay9ytZe7xpiKoSi12F7WUXqoIeI++Xvo=" },
+		{ "QJotpmZINx9eptKpkh9j3JlEDcHdWnjEbicdBS7gPXM=", "3gLYKeoruVZ/AYjym0gciDvRHj45UIWyHhNjWj2Wj28=", "NwuUkkE5yOWT7wed7bltgAk71miz3cSooiIDAdv6kKw=" },
+		{ "OAYGuxH70OPQvhVIX4BhSCWUyzAI5H2IkYxKgC/AO0M=", "Rj9iNF/FagkXfdLPqc9LHfaoGR8GlvY3gun2FilE508=", "hkNXLVMlBRsMEaQKkSzevcEK2sMu0AShGKQJMNqdzWM=" },
+		{ "uBrgZ7wLHrOV/0dNiEqo7FjY9VnJqL5eUDHJWAc9QXg=", "3Hnln8ZHfSaK4OzESJe5U6NcLaW56wzfZICzvzefnSo=", "DhXsehe5FAmbUidXT5ZpZIAuu1eF9rkU6cF3FBoKwOE=" },
+		{ "sJknn3CHvx/812EWU3ddLdLLZFBKsc+wx35GXyiRsHY=", "qqa2dNSt0jWozGyqpokP392H5/DOAUUZmUpyZDaUEEE=", "1Dyz8CvmF17oKT/wG2fu3vRzPzgQv8/OY9GJYew4FG8=" },
+		{ "yOumS9HN68ZwIm+5hZol3jFQ0DB4SKuW/ld3y8wioGk=", "6PowsbKj/fKSzXZMAfaSkP3fE+4AThL9xm6ysQzMDxg=", "vF8cKu9X9FxgCjyVZ5RG7nuue8RelF5Qsb8Efme4M4A=" },
+		{ "mISm5vQfPdK72SsaHh6O1/ARvaWCtm+KZNcpTsyt500=", "YCORDQDpb1U8vADdstBgXkg0N7QaAc5VoXJ4QFuA/UY=", "JlrBmfaCgbfEVD9YQq3c03WwwsHWc1nBwp1JkFORC3A=" },
+		{ "cPyu3Qry6qbsiOJKFGRziZ9LJWJ47k3ZSXiGkQXuQm0=", "/cmBZTbqEp8sababPAxGb3OvDAEE7MlwPOwEFHE+7yM=", "lp0Hhc/rVtpT5FtLLccChqDl3El48XtP6Wm6JwjI7jo=" },
+		{ "YKsMYU0SINbPwWw4RDCJV6GnzDlSp1ZNwUw2euGWi0A=", "/KmBReATQbFnLg8YKV0jwhqKeishRoWvlVtMX3550Vk=", "7fXpPSMo1Fw2sOXtjTtvFU+DbZvS/FWB9wAsywWx6R4=" },
+		{ "WD0YI3y71eIp/GXw9i+7scEiQKSBkGihZWE+s6fGmWo=", "RdthAL/qPnAZFb3xBgRMiAtGHNjgokzoKX9iO2K5qhc=", "4dk4HGkT9dBmomGNorDE/hLr/HEFhljtl4zz3M4sG58=" },
+		{ "oD8KWhJYZVhutJRb0kZlZnB22QUXzi2FfPRD0ll65UM=", "MYTBGHh4Ukj97pKj6qcfWmxGNQzmU3/aBOX2f1tfhG0=", "VT1gC+a9nRJzYMi/TPvRVnn3IQlaop/jKmmxZePEME0=" },
+		{ "0Ns/1SOiqR2CpHRG03QNzJJd5gxTm1XJmSkFlugjQ3k=", "KvQAI+ekNOa2xfEvfyc9JGcS+CTUrnnhsKrlyJGJixg=", "J8LmSX6zElX3S9q4PNvh2NKUtAiQ3oHiYjSJ7yErPlY=" },
+		{ "mD6TeF4ezSPXN/csN1OhoAREFSXllI+zl4DUOInVq2Q=", "WmLJ9ep2EqFcSftnYFJsmWyUxqL0zzuSzVEv94PcISM=", "U2+ILy2NDmmfgSW78C8dl8GyHESUc1lXPHPpg5F+gr8=" },
+		{ "SMJoXOYgHz8HSzY+ByeWLcSP5qFwv7YjRe0bcKesRnM=", "DEsNSOY3TEs9J2YgqroQ4xKq8T8xNJQjvvE4UrTItQw=", "Bws1Hk2+lO+JQ7ME16EbwAdsBkWsGvti0Gb6LY2Lrms=" },
+		{ "iGhXF0Hg0tqZmpwAMiolxvbvTPClQ7LlBAspSSyFEHE=", "7Xxzpwl7yRWehHNWTYVtFkdChJdXhtY4Mtw1fA9QcCg=", "Swjfv0PjuaE8Oq3a17BVno5I+q49dZlPwKK1bPUoKNI=" },
+		{ "MIazjx3qTi6Qz3WzhtCPw3i4Q2uZBHcuMoh++ZGFYUk=", "oni8pbFqk9Ya+Fx+911Nl1SN0FD/hR1jwb2RH3t/pRk=", "ZYAFcj67LkbNURYbSnCCWGxAG8QLDGWwbl968mA6ZA4=" },
+		{ "MAadYdiFM2cPuJF19q20Yoo5KJabuR9TUQ9jG5nvA1w=", "5OcE8XV+UPoBVbgqBQdVF62GZCW9DOQEdxrQsktPPBA=", "FCZsEFXouy+xtxv8X7VroXtvPG1Z1HFHL724tz1jcUI=" },
+		{ "+GwxMmD2dee5+QmvXNI0NdP+rNWoSXTN42otbp0aZ24=", "Y0N44baz9ihclCUnv6rRbDqCYu4BxQlBfNnTz3NNe2A=", "/LqSgkVQNkQ/oBiZSgpM9Rw7BJv0RvRpEQpvlizvHy0=" },
+		{ "8FREpCtncOcT7+W2nW4aYSjmSbADtVSH9rIliQZZUH4=", "fTNSd0JeREhXmPfjrmrAu6Lu/yHkB9GyxR3SyO4kZ28=", "262KN/iG/iJEaZeerFm1yVtvhFVGgQFwSvtxTcjZzeg=" },
+		{ "EDhaRQGtscjoSE9wJOnSXoQVtVruIqyzknty+x/vDWo=", "eMmMgws6ZxDIxZ6QSwGjZO1Mx/r5T+fJjSTKGMBk/BU=", "0CyaJV6AG9bZ0C4yeZ/RDsOs9BdNqZpUxAsD30WmJO4=" },
+		{ "CJV0UB2YdvVDG1cs4oiJgHAS+f1FocGr/vGCfiovsWQ=", "9/O9GWZEOXVm7On8lftL27PffRORju8OKl6gZd/74CA=", "A+kXRVNOwIrA5DUa+3v7dpRC+Gbxm23LTiYmOUAXUyY=" },
+		{ "gCjDsJUwZGA7BjYVoCQsvdIgN9Q4lBHlSyKwUrl751A=", "HRwS8T9y2qPYk7JVU/8Y+6cS+Bk8XCLCXxwN/ttbQiI=", "iFotjA6rhUfkDv4S/wspJgEWunEmrlGSGsXcJ0+8laQ=" },
+		{ "6N5pL4gsuK+shHpDxirTnAGdyKXIlYHyfIhtB0njJGA=", "CVZvW7NaN2XMEEKHodghBA9hLCwee/jrmttiWh/CmEg=", "OpPEd3Sp8r6KdjNDTN4bVHETlGJ92BCK74FCdEaDe9g=" },
+		{ "UIPPTUdvhlg8qEDv6JRxM4/8F5ORjJz4ud82QZrgeEY=", "7Nd13z5EpB3ChytvQC1CxvDY7n0H8r2Y7lzLEY8hdEk=", "b22PvgU0M2QfNC7ZGN+RXNe5fjOzMsY32IcHTwLNIqw=" },
+		{ "oBn53Q5fmxKX02PgI6F47Rb+XoLeFQO07ok2tYhk0lE=", "e0gtPDKXCZSoNW1uHqBPQXLfiYgyeqPMU2zZJgPXACI=", "wmjW2wDT2EzFkyaGui7YWNLTRu8Q4eD/GVKM2utZkEs=" },
+	};
+	gs_unref_ptrarray GPtrArray *allowed_ips_keep_alive = NULL;
+	gs_unref_array GArray *peers = NULL;
+	NMPlatformLnkWireGuard lnk_wireguard;
+	int r;
+	guint i;
+
+	allowed_ips_keep_alive = g_ptr_array_new_with_free_func (g_free);
+
+	peers = g_array_new (FALSE, TRUE, sizeof (NMPWireGuardPeer));
+
+	lnk_wireguard = (NMPlatformLnkWireGuard) {
+		.listen_port = 50754,
+		.fwmark = 0x1102,
+	};
+	_copy_base64 (lnk_wireguard.private_key, sizeof (lnk_wireguard.private_key), self_key.pri);
+	_copy_base64 (lnk_wireguard.public_key,  sizeof (lnk_wireguard.public_key),  self_key.pub);
+
+	if (test_mode == 0) {
+		/* no peers. */
+	} else if (NM_IN_SET (test_mode, 1, 2)) {
+		guint num_peers = (test_mode == 1) ? 1 : G_N_ELEMENTS (keys);
+
+		for (i = 0; i < num_peers; i++) {
+			NMPWireGuardPeer peer;
+			char s_addr[NM_UTILS_INET_ADDRSTRLEN];
+			NMSockAddrUnion endpoint;
+			guint i_allowed_ips, n_allowed_ips;
+			NMPWireGuardAllowedIP *allowed_ips;
+
+			if ((i % 2) == 1) {
+				endpoint = (NMSockAddrUnion) {
+					.in = {
+						.sin_family      = AF_INET,
+						.sin_addr.s_addr = nmtst_inet4_from_string (nm_sprintf_buf (s_addr, "192.168.7.%d", i)),
+						.sin_port        = htons (14000 + i),
+					},
+				};
+			} else {
+				endpoint = (NMSockAddrUnion) {
+					.in6 = {
+						.sin6_family = AF_INET6,
+						.sin6_addr   = *nmtst_inet6_from_string (nm_sprintf_buf (s_addr, "a:b:c:e::1:%d", i)),
+						.sin6_port   = htons (16000 + i),
+					},
+				};
+			}
+
+			if (test_mode == 1)
+				n_allowed_ips = 1;
+			else
+				n_allowed_ips = i % 10;
+			allowed_ips = g_new0 (NMPWireGuardAllowedIP, n_allowed_ips);
+			g_ptr_array_add (allowed_ips_keep_alive, allowed_ips);
+			for (i_allowed_ips = 0; i_allowed_ips < n_allowed_ips; i_allowed_ips++) {
+				NMPWireGuardAllowedIP *aip = &allowed_ips[i_allowed_ips];
+
+				aip->family = (i_allowed_ips % 2) ? AF_INET : AF_INET6;
+				if (aip->family == AF_INET) {
+					aip->addr.addr4 = nmtst_inet4_from_string (nm_sprintf_buf (s_addr, "10.%u.%u.0", i, i_allowed_ips));
+					aip->mask = 32 - (i_allowed_ips % 8);
+				} else {
+					aip->addr.addr6 = *nmtst_inet6_from_string (nm_sprintf_buf (s_addr, "a:d:f:%02x:%02x::", i, i_allowed_ips));
+					aip->mask = 128 - (i_allowed_ips % 10);
+				}
+			}
+
+			peer = (NMPWireGuardPeer) {
+				.persistent_keepalive_interval = 60+i,
+				.endpoint                      = endpoint,
+				.allowed_ips                   = n_allowed_ips > 0 ? allowed_ips : NULL,
+				.allowed_ips_len               = n_allowed_ips,
+			};
+			_copy_base64 (peer.public_key, sizeof (peer.public_key), keys[i].pub);
+			_copy_base64 (peer.preshared_key, sizeof (peer.preshared_key), (i % 3) ? NULL : keys[i].pre);
+
+			g_array_append_val (peers, peer);
+		}
+	} else
+		g_assert_not_reached ();
+
+	r = nm_platform_link_wireguard_change (platform,
+	                                       ifindex,
+	                                       &lnk_wireguard,
+	                                       (const NMPWireGuardPeer *) peers->data,
+	                                       NULL,
+	                                       peers->len,
+	                                         NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY
+	                                       | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT
+	                                       | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK
+	                                       | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS);
+	g_assert (NMTST_NM_ERR_SUCCESS (r));
+}
+
+/*****************************************************************************/
+
 typedef struct {
 	NMLinkType link_type;
 	int test_mode;
@@ -697,6 +936,7 @@ test_software_detect (gconstpointer user_data)
 	int ifindex, ifindex_parent;
 	const NMPlatformLink *plink;
 	const NMPObject *lnk;
+	int r;
 	guint i_step;
 	const gboolean ext = test_data->external_command;
 	NMPlatformLnkTun lnk_tun;
@@ -897,7 +1137,7 @@ test_software_detect (gconstpointer user_data)
 
 			dummy = nmtstp_link_dummy_add (NM_PLATFORM_GET, FALSE, "dummy-tmp");
 			g_assert_cmpint (dummy->ifindex, ==, i);
-			nmtstp_link_del (NM_PLATFORM_GET, FALSE, dummy->ifindex, NULL);
+			nmtstp_link_delete (NM_PLATFORM_GET, FALSE, dummy->ifindex, NULL, TRUE);
 		}
 
 		if (!nmtstp_link_macvlan_add (NULL, ext, DEVICE_NAME, ifindex_parent, &lnk_macvlan))
@@ -1005,6 +1245,19 @@ test_software_detect (gconstpointer user_data)
 		                                 : NULL));
 		break;
 	}
+	case NM_LINK_TYPE_WIREGUARD: {
+		const NMPlatformLink *link;
+
+		r = nm_platform_link_wireguard_add (NM_PLATFORM_GET, DEVICE_NAME, &link);
+		if (r == -EOPNOTSUPP) {
+			g_test_skip ("wireguard not supported (modprobe wireguard?)");
+			goto out_delete_parent;
+		}
+
+		g_assert (NMTST_NM_ERR_SUCCESS (r));
+		g_assert (NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST (link)) == NMP_OBJECT_TYPE_LINK);
+		break;
+	}
 	default:
 		g_assert_not_reached ();
 	}
@@ -1217,7 +1470,7 @@ test_software_detect (gconstpointer user_data)
 				g_assert_cmpint (plnk->dst_port, ==, 4789);
 				if (   plnk->src_port_min != 0
 				    || plnk->src_port_max != 0) {
-					/* on some kernels, omiting the port range results in setting
+					/* on some kernels, omitting the port range results in setting
 					 * following default port range. */
 					g_assert_cmpint (plnk->src_port_min, ==, 32768);
 					g_assert_cmpint (plnk->src_port_max, ==, 61000);
@@ -1238,14 +1491,26 @@ test_software_detect (gconstpointer user_data)
 			}
 			break;
 		}
+		case NM_LINK_TYPE_WIREGUARD: {
+			const NMPlatformLnkWireGuard *plnk = &lnk->lnk_wireguard;
+
+			g_assert (plnk == nm_platform_link_get_lnk_wireguard (NM_PLATFORM_GET, ifindex, NULL));
+
+			if (plink->n_ifi_flags & IFF_UP) {
+				_test_wireguard_change (NM_PLATFORM_GET, plink->ifindex, test_data->test_mode);
+				if (_LOGD_ENABLED ())
+					_system ("WG_HIDE_KEYS=never wg show all");
+			}
+			break;
+		}
 		default:
 			g_assert_not_reached ();
 		}
 	}
 
-	nmtstp_link_del (NULL, -1, ifindex, DEVICE_NAME);
+	nmtstp_link_delete (NULL, -1, ifindex, DEVICE_NAME, TRUE);
 out_delete_parent:
-	nmtstp_link_del (NULL, -1, ifindex_parent, PARENT_NAME);
+	nmtstp_link_delete (NULL, -1, ifindex_parent, PARENT_NAME, TRUE);
 }
 
 static void
@@ -1822,8 +2087,8 @@ test_vlan_set_xgress (void)
 		_assert_vlan_flags (ifindex, NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP);
 	}
 
-	nmtstp_link_del (NULL, -1, ifindex, DEVICE_NAME);
-	nmtstp_link_del (NULL, -1, ifindex_parent, PARENT_NAME);
+	nmtstp_link_delete (NULL, -1, ifindex, DEVICE_NAME, TRUE);
+	nmtstp_link_delete (NULL, -1, ifindex_parent, PARENT_NAME, TRUE);
 }
 
 /*****************************************************************************/
@@ -1879,7 +2144,7 @@ test_create_many_links_do (guint n_devices)
 		if (EX == 2)
 			nmtstp_run_command_check ("ip link delete %s", name);
 		else
-			nmtstp_link_del (NULL, EX, g_array_index (ifindexes, int, i), name);
+			nmtstp_link_delete (NULL, EX, g_array_index (ifindexes, int, i), name, TRUE);
 	}
 
 	_LOGI (">>> process events after deleting devices...");
@@ -1965,7 +2230,7 @@ test_nl_bugs_veth (void)
 	});
 
 out:
-	nmtstp_link_del (NULL, -1, ifindex_veth0, IFACE_VETH0);
+	nmtstp_link_delete (NULL, -1, ifindex_veth0, IFACE_VETH0, TRUE);
 	g_assert (!nmtstp_link_get (NM_PLATFORM_GET, ifindex_veth0, IFACE_VETH0));
 	g_assert (!nmtstp_link_get (NM_PLATFORM_GET, ifindex_veth1, IFACE_VETH1));
 	nmtstp_namespace_handle_release (ns_handle);
@@ -2018,7 +2283,7 @@ again:
 	}
 
 	g_assert (!nmtstp_link_get (NM_PLATFORM_GET, ifindex_bond0, IFACE_BOND0));
-	nmtstp_link_del (NULL, -1, ifindex_dummy0, IFACE_DUMMY0);
+	nmtstp_link_delete (NULL, -1, ifindex_dummy0, IFACE_DUMMY0, TRUE);
 }
 
 /*****************************************************************************/
@@ -2072,8 +2337,8 @@ again:
 		goto again;
 	}
 
-	nmtstp_link_del (NULL, -1, ifindex_bridge0, IFACE_BRIDGE0);
-	nmtstp_link_del (NULL, -1, ifindex_dummy0, IFACE_DUMMY0);
+	nmtstp_link_delete (NULL, -1, ifindex_bridge0, IFACE_BRIDGE0, TRUE);
+	nmtstp_link_delete (NULL, -1, ifindex_dummy0, IFACE_DUMMY0, TRUE);
 }
 
 /*****************************************************************************/
@@ -2132,7 +2397,7 @@ _test_netns_check_skip (void)
 			support_errsv = errno;
 	}
 	if (!support) {
-		_LOGD ("setns() failed with \"%s\". This indicates missing support (valgrind?)", g_strerror (support_errsv));
+		_LOGD ("setns() failed with \"%s\". This indicates missing support (valgrind?)", nm_strerror_native (support_errsv));
 		g_test_skip ("No netns support (setns failed)");
 		return TRUE;
 	}
@@ -2637,8 +2902,8 @@ test_sysctl_rename (void)
 	}
 
 	nm_platform_process_events (PL);
-	nmtstp_link_del (PL, -1, ifindex[0], NULL);
-	nmtstp_link_del (PL, -1, ifindex[1], NULL);
+	nmtstp_link_delete (PL, -1, ifindex[0], NULL, TRUE);
+	nmtstp_link_delete (PL, -1, ifindex[1], NULL, TRUE);
 }
 
 /*****************************************************************************/
@@ -2717,7 +2982,70 @@ test_sysctl_netns_switch (void)
 	else
 		g_assert_cmpint (ifindex_tmp, ==, -1);
 
-	nmtstp_link_del (PL, FALSE, ifindex, NULL);
+	nmtstp_link_delete (PL, FALSE, ifindex, NULL, TRUE);
+}
+
+/*****************************************************************************/
+
+static gpointer
+_test_netns_mt_thread (gpointer data)
+{
+	NMPNetns *netns1 = data;
+	gs_unref_object NMPNetns *netns2 = NULL;
+	NMPNetns *netns_bottom;
+	NMPNetns *initial;
+
+	netns_bottom = nmp_netns_get_initial ();
+	g_assert (netns_bottom);
+
+	/* I don't know why, but we need to create a new netns here at least once.
+	 * Otherwise, setns(, CLONE_NEWNS) below fails with EINVAL (???).
+	 *
+	 * Something is not right here, but what?  */
+	netns2 = nmp_netns_new ();
+	nmp_netns_pop (netns2);
+	g_clear_object (&netns2);
+
+	nmp_netns_push (netns1);
+	nmp_netns_push_type (netns_bottom, CLONE_NEWNET);
+	nmp_netns_push_type (netns_bottom, CLONE_NEWNS);
+	nmp_netns_push_type (netns1, CLONE_NEWNS);
+	nmp_netns_pop (netns1);
+	nmp_netns_pop (netns_bottom);
+	nmp_netns_pop (netns_bottom);
+	nmp_netns_pop (netns1);
+
+	initial = nmp_netns_get_initial ();
+	g_assert (NMP_IS_NETNS (initial));
+	return g_object_ref (initial);
+}
+
+static void
+test_netns_mt (void)
+{
+	gs_unref_object NMPNetns *netns1 = NULL;
+	NMPNetns *initial_from_other_thread;
+	GThread *th;
+
+	if (_test_netns_check_skip ())
+		return;
+
+	netns1 = nmp_netns_new ();
+	g_assert (NMP_NETNS (netns1));
+	nmp_netns_pop (netns1);
+
+	th = g_thread_new ("nm-test-netns-mt", _test_netns_mt_thread, netns1);
+	initial_from_other_thread = g_thread_join (th);
+	g_assert (NMP_IS_NETNS (initial_from_other_thread));
+
+	if (nmtst_get_rand_bool ()) {
+		nmp_netns_push (initial_from_other_thread);
+		nmp_netns_pop (initial_from_other_thread);
+	}
+
+	g_object_add_weak_pointer (G_OBJECT (initial_from_other_thread), (gpointer *) &initial_from_other_thread);
+	g_object_unref (initial_from_other_thread);
+	g_assert (initial_from_other_thread == NULL);
 }
 
 /*****************************************************************************/
@@ -2800,12 +3128,8 @@ test_ethtool_features_get (void)
 
 		ethtool_features_dump (features);
 
-		if (_LOGT_ENABLED ()) {
-			int ignore;
-
-			ignore = system ("ethtool -k lo");
-			(void) ignore;
-		}
+		if (_LOGT_ENABLED ())
+			_system ("ethtool -k lo");
 
 		if (!do_set) {
 			requested = gfree_keeper->pdata[i_run * 2 - 2];
@@ -2829,9 +3153,9 @@ _nmtstp_init_tests (int *argc, char ***argv)
 void
 _nmtstp_setup_tests (void)
 {
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, SLAVE_NAME));
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, PARENT_NAME));
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE);
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, SLAVE_NAME, FALSE);
+	nmtstp_link_delete (NM_PLATFORM_GET, -1, -1, PARENT_NAME, FALSE);
 	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
 	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, SLAVE_NAME));
 	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, PARENT_NAME));
@@ -2862,6 +3186,9 @@ _nmtstp_setup_tests (void)
 		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);
+		test_software_detect_add ("/link/software/detect/wireguard/0", NM_LINK_TYPE_WIREGUARD, 0);
+		test_software_detect_add ("/link/software/detect/wireguard/1", NM_LINK_TYPE_WIREGUARD, 1);
+		test_software_detect_add ("/link/software/detect/wireguard/2", NM_LINK_TYPE_WIREGUARD, 2);
 
 		g_test_add_func ("/link/software/vlan/set-xgress", test_vlan_set_xgress);
 
@@ -2877,6 +3204,8 @@ _nmtstp_setup_tests (void)
 		g_test_add_vtable ("/general/netns/push", 0, NULL, _test_netns_setup, test_netns_push, _test_netns_teardown);
 		g_test_add_vtable ("/general/netns/bind-to-path", 0, NULL, _test_netns_setup, test_netns_bind_to_path, _test_netns_teardown);
 
+		g_test_add_func ("/general/netns/mt", test_netns_mt);
+
 		g_test_add_func ("/general/sysctl/rename", test_sysctl_rename);
 		g_test_add_func ("/general/sysctl/netns-switch", test_sysctl_netns_switch);
 
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 85b14b57..2619ec52 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -427,7 +427,7 @@ test_ip4_route_get (void)
 {
 	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	in_addr_t a;
-	NMPlatformError result;
+	int result;
 	nm_auto_nmpobj NMPObject *route = NULL;
 	const NMPlatformIP4Route *r;
 
@@ -446,7 +446,7 @@ test_ip4_route_get (void)
 	                                   nmtst_get_rand_int () % 2 ? 0 : ifindex,
 	                                   &route);
 
-	g_assert (result == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (result));
 	g_assert (NMP_OBJECT_GET_TYPE (route) == NMP_OBJECT_TYPE_IP4_ROUTE);
 	g_assert (!NMP_OBJECT_IS_STACKINIT (route));
 	g_assert (route->parent._ref_count == 1);
@@ -565,7 +565,7 @@ test_ip4_route_options (gconstpointer test_data)
 	}
 
 	for (i = 0; i < rts_n; i++)
-		g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]) == NM_PLATFORM_ERROR_SUCCESS);
+		g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_ip4_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i])));
 
 	for (i = 0; i < rts_n; i++) {
 		rts_cmp[i] = rts_add[i];
@@ -589,7 +589,7 @@ test_ip6_route_get (void)
 {
 	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	const struct in6_addr *a;
-	NMPlatformError result;
+	int result;
 	nm_auto_nmpobj NMPObject *route = NULL;
 	const NMPlatformIP6Route *r;
 
@@ -608,7 +608,7 @@ test_ip6_route_get (void)
 	                                   nmtst_get_rand_int () % 2 ? 0 : ifindex,
 	                                   &route);
 
-	g_assert (result == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMTST_NM_ERR_SUCCESS (result));
 	g_assert (NMP_OBJECT_GET_TYPE (route) == NMP_OBJECT_TYPE_IP6_ROUTE);
 	g_assert (!NMP_OBJECT_IS_STACKINIT (route));
 	g_assert (route->parent._ref_count == 1);
@@ -724,7 +724,7 @@ test_ip6_route_options (gconstpointer test_data)
 	_wait_for_ipv6_addr_non_tentative (NM_PLATFORM_GET, 400, IFINDEX, addr_n, addr_in6);
 
 	for (i = 0; i < rts_n; i++)
-		g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]) == NM_PLATFORM_ERROR_SUCCESS);
+		g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_ip6_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i])));
 
 	for (i = 0; i < rts_n; i++) {
 		rts_cmp[i] = rts_add[i];
@@ -767,7 +767,7 @@ test_ip (gconstpointer test_data)
 	const int EX_ = -1;
 	struct {
 		int ifindex;
-	} iface_data[10] = { 0 };
+	} iface_data[10] = { { 0 }, };
 	int order_idx[G_N_ELEMENTS (iface_data)] = { 0 };
 	guint order_len;
 	guint try;
@@ -823,7 +823,7 @@ again_find_idx:
 			order_idx[order_len++] = idx;
 
 			r->ifindex = iface_data[idx].ifindex;
-			g_assert (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_APPEND, r) == NM_PLATFORM_ERROR_SUCCESS);
+			g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_APPEND, r)));
 		} else {
 			i = nmtst_get_rand_int () % order_len;
 			idx = order_idx[i];
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.c b/src/platform/wifi/nm-wifi-utils-nl80211.c
index 39e3f971..4f7ede97 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.c
@@ -24,8 +24,6 @@
 
 #include "nm-wifi-utils-nl80211.h"
 
-#include <errno.h>
-#include <string.h>
 #include <sys/ioctl.h>
 #include <net/ethernet.h>
 #include <unistd.h>
@@ -39,11 +37,16 @@
 #include "nm-utils.h"
 
 #define _NMLOG_PREFIX_NAME      "wifi-nl80211"
-#define _NMLOG(level, domain, ...) \
+#define _NMLOG_DOMAIN           LOGD_PLATFORM | LOGD_WIFI
+#define _NMLOG(level, ...) \
 	G_STMT_START { \
-		nm_log ((level), (domain), NULL, NULL, \
-		        "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-		        _NMLOG_PREFIX_NAME \
+		char _ifname_buf[IFNAMSIZ]; \
+		const char *_ifname = self ? nmp_utils_if_indextoname (self->parent.ifindex, _ifname_buf) : NULL; \
+		\
+		nm_log ((level), _NMLOG_DOMAIN, _ifname ?: NULL, NULL, \
+		        "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+		        _NMLOG_PREFIX_NAME, \
+		        NM_PRINT_FMT_QUOTED (_ifname, " (", _ifname, ")", "") \
 		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
 	} G_STMT_END
 
@@ -104,16 +107,16 @@ nla_put_failure:
 }
 
 static struct nl_msg *
-nl80211_alloc_msg (NMWifiUtilsNl80211 *nl80211, guint32 cmd, guint32 flags)
+nl80211_alloc_msg (NMWifiUtilsNl80211 *self, guint32 cmd, guint32 flags)
 {
-	return _nl80211_alloc_msg (nl80211->id, nl80211->parent.ifindex, nl80211->phy, cmd, flags);
+	return _nl80211_alloc_msg (self->id, self->parent.ifindex, self->phy, cmd, flags);
 }
 
 static int
-_nl80211_send_and_recv (struct nl_sock *nl_sock,
-                        struct nl_msg *msg,
-                        int (*valid_handler) (struct nl_msg *, void *),
-                        void *valid_data)
+nl80211_send_and_recv (NMWifiUtilsNl80211 *self,
+                       struct nl_msg *msg,
+                       int (*valid_handler) (struct nl_msg *, void *),
+                       void *valid_data)
 {
 	int err;
 	int done = 0;
@@ -130,7 +133,7 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock,
 
 	g_return_val_if_fail (msg != NULL, -ENOMEM);
 
-	err = nl_send_auto (nl_sock, msg);
+	err = nl_send_auto (self->nl_sock, msg);
 	if (err < 0)
 		return err;
 
@@ -138,19 +141,18 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock,
 	 * done will be 1, on error it will be < 0.
 	 */
 	while (!done) {
-		err = nl_recvmsgs (nl_sock, &cb);
+		err = nl_recvmsgs (self->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
 			 * not warn on DUMP_INTR error for get scan command.
 			 */
-			if (err == -NLE_DUMP_INTR &&
+			if (err == -NME_NL_DUMP_INTR &&
 			    genlmsg_hdr (nlmsg_hdr (msg))->cmd == NL80211_CMD_GET_SCAN)
 				break;
 
-			_LOGW (LOGD_WIFI, "nl_recvmsgs() error: (%d) %s",
-			       err, nl_geterror (err));
+			_LOGW ("nl_recvmsgs() error: (%d) %s", err, nm_strerror (err));
 			break;
 		}
 	}
@@ -160,22 +162,12 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock,
 	return err;
 }
 
-static int
-nl80211_send_and_recv (NMWifiUtilsNl80211 *nl80211,
-                       struct nl_msg *msg,
-                       int (*valid_handler) (struct nl_msg *, void *),
-                       void *valid_data)
-{
-	return _nl80211_send_and_recv (nl80211->nl_sock, msg,
-	                               valid_handler, valid_data);
-}
-
 static void
 dispose (GObject *object)
 {
-	NMWifiUtilsNl80211 *nl80211 = NM_WIFI_UTILS_NL80211 (object);
+	NMWifiUtilsNl80211 *self = NM_WIFI_UTILS_NL80211 (object);
 
-	g_clear_pointer (&nl80211->freqs, g_free);
+	g_clear_pointer (&self->freqs, g_free);
 }
 
 struct nl80211_iface_info {
@@ -189,8 +181,10 @@ nl80211_iface_info_handler (struct nl_msg *msg, void *arg)
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
 
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
+	if (nla_parse_arr (tb,
+	                   genlmsg_attrdata (gnlh, 0),
+	                   genlmsg_attrlen (gnlh, 0),
+	                   NULL) < 0)
 		return NL_SKIP;
 
 	if (!tb[NL80211_ATTR_IFTYPE])
@@ -214,15 +208,15 @@ nl80211_iface_info_handler (struct nl_msg *msg, void *arg)
 static NM80211Mode
 wifi_nl80211_get_mode (NMWifiUtils *data)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_iface_info iface_info = {
 		.mode = NM_802_11_MODE_UNKNOWN,
 	};
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_INTERFACE, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_GET_INTERFACE, 0);
 
-	if (nl80211_send_and_recv (nl80211, msg, nl80211_iface_info_handler,
+	if (nl80211_send_and_recv (self, msg, nl80211_iface_info_handler,
 	                           &iface_info) < 0)
 		return NM_802_11_MODE_UNKNOWN;
 
@@ -232,11 +226,11 @@ wifi_nl80211_get_mode (NMWifiUtils *data)
 static gboolean
 wifi_nl80211_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_INTERFACE, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_SET_INTERFACE, 0);
 
 	switch (mode) {
 	case NM_802_11_MODE_INFRA:
@@ -252,7 +246,7 @@ wifi_nl80211_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 		g_assert_not_reached ();
 	}
 
-	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
+	err = nl80211_send_and_recv (self, msg, NULL, NULL);
 	return err >= 0;
 
 nla_put_failure:
@@ -262,14 +256,14 @@ nla_put_failure:
 static gboolean
 wifi_nl80211_set_powersave (NMWifiUtils *data, guint32 powersave)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_POWER_SAVE, 0);
+	msg = nl80211_alloc_msg (self, 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);
+	err = nl80211_send_and_recv (self, msg, NULL, NULL);
 	return err >= 0;
 
 nla_put_failure:
@@ -284,16 +278,18 @@ nl80211_get_wake_on_wlan_handler (struct nl_msg *msg, void *arg)
 	struct nlattr *trig[NUM_NL80211_WOWLAN_TRIG];
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 
-	nla_parse (attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
-	           genlmsg_attrlen(gnlh, 0), NULL);
+	nla_parse_arr (attrs,
+	               genlmsg_attrdata(gnlh, 0),
+	               genlmsg_attrlen(gnlh, 0),
+	               NULL);
 
 	if (!attrs[NL80211_ATTR_WOWLAN_TRIGGERS])
 		return NL_SKIP;
 
-	nla_parse (trig, MAX_NL80211_WOWLAN_TRIG,
-	           nla_data (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
-	           nla_len (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
-	           NULL);
+	nla_parse_arr (trig,
+	               nla_data (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
+	               nla_len (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
+	               NULL);
 
 	*wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE;
 	if (trig[NL80211_WOWLAN_TRIG_ANY])
@@ -319,13 +315,13 @@ nl80211_get_wake_on_wlan_handler (struct nl_msg *msg, void *arg)
 static NMSettingWirelessWakeOnWLan
 wifi_nl80211_get_wake_on_wlan (NMWifiUtils *data)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	NMSettingWirelessWakeOnWLan wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WOWLAN, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_GET_WOWLAN, 0);
 
-	nl80211_send_and_recv (nl80211, msg, nl80211_get_wake_on_wlan_handler, &wowl);
+	nl80211_send_and_recv (self, msg, nl80211_get_wake_on_wlan_handler, &wowl);
 
 	return wowl;
 }
@@ -333,7 +329,7 @@ wifi_nl80211_get_wake_on_wlan (NMWifiUtils *data)
 static gboolean
 wifi_nl80211_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct nlattr *triggers;
 	int err;
@@ -341,7 +337,7 @@ wifi_nl80211_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wo
 	if (wowl == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE)
 		return TRUE;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_WOWLAN, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_SET_WOWLAN, 0);
 	if (!msg)
 		return FALSE;
 
@@ -364,7 +360,7 @@ wifi_nl80211_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wo
 
 	nla_nest_end(msg, triggers);
 
-	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
+	err = nl80211_send_and_recv (self, msg, NULL, NULL);
 
 	return err >= 0;
 
@@ -420,33 +416,35 @@ find_ssid (guint8 *ies, guint32 ies_len,
 static int
 nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 {
+	static const struct nla_policy bss_policy[] = {
+		[NL80211_BSS_TSF]                  = { .type = NLA_U64 },
+		[NL80211_BSS_FREQUENCY]            = { .type = NLA_U32 },
+		[NL80211_BSS_BSSID]                = { .minlen = ETH_ALEN },
+		[NL80211_BSS_BEACON_INTERVAL]      = { .type = NLA_U16 },
+		[NL80211_BSS_CAPABILITY]           = { .type = NLA_U16 },
+		[NL80211_BSS_INFORMATION_ELEMENTS] = { },
+		[NL80211_BSS_SIGNAL_MBM]           = { .type = NLA_U32 },
+		[NL80211_BSS_SIGNAL_UNSPEC]        = { .type = NLA_U8 },
+		[NL80211_BSS_STATUS]               = { .type = NLA_U32 },
+	};
 	struct nl80211_bss_info *info = arg;
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
-	struct nlattr *bss[NL80211_BSS_MAX + 1];
-	static const struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
-		[NL80211_BSS_TSF] = { .type = NLA_U64 },
-		[NL80211_BSS_FREQUENCY] = { .type = NLA_U32 },
-		[NL80211_BSS_BSSID] = { },
-		[NL80211_BSS_BEACON_INTERVAL] = { .type = NLA_U16 },
-		[NL80211_BSS_CAPABILITY] = { .type = NLA_U16 },
-		[NL80211_BSS_INFORMATION_ELEMENTS] = { },
-		[NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 },
-		[NL80211_BSS_SIGNAL_UNSPEC] = { .type = NLA_U8 },
-		[NL80211_BSS_STATUS] = { .type = NLA_U32 },
-	};
+	struct nlattr *bss[G_N_ELEMENTS (bss_policy)];
 	guint32 status;
 
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
+	if (nla_parse_arr (tb,
+	                   genlmsg_attrdata (gnlh, 0),
+	                   genlmsg_attrlen (gnlh, 0),
+	                   NULL) < 0)
 		return NL_SKIP;
 
 	if (tb[NL80211_ATTR_BSS] == NULL)
 		return NL_SKIP;
 
-	if (nla_parse_nested (bss, NL80211_BSS_MAX,
-	                      tb[NL80211_ATTR_BSS],
-	                      bss_policy))
+	if (nla_parse_nested_arr (bss,
+	                          tb[NL80211_ATTR_BSS],
+	                          bss_policy))
 		return NL_SKIP;
 
 	if (bss[NL80211_BSS_STATUS] == NULL)
@@ -466,21 +464,22 @@ nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 		info->freq = nla_get_u32 (bss[NL80211_BSS_FREQUENCY]);
 
 	if (bss[NL80211_BSS_SIGNAL_UNSPEC])
-		info->beacon_signal =
-			nla_get_u8 (bss[NL80211_BSS_SIGNAL_UNSPEC]);
+		info->beacon_signal = nla_get_u8 (bss[NL80211_BSS_SIGNAL_UNSPEC]);
 
 	if (bss[NL80211_BSS_SIGNAL_MBM])
-		info->beacon_signal =
-			nl80211_xbm_to_percent (nla_get_u32 (bss[NL80211_BSS_SIGNAL_MBM]), 100);
+		info->beacon_signal = nl80211_xbm_to_percent (nla_get_u32 (bss[NL80211_BSS_SIGNAL_MBM]), 100);
 
 	if (bss[NL80211_BSS_INFORMATION_ELEMENTS]) {
 		guint8 *ssid;
 		guint32 ssid_len;
 
 		find_ssid (nla_data (bss[NL80211_BSS_INFORMATION_ELEMENTS]),
-			  nla_len (bss[NL80211_BSS_INFORMATION_ELEMENTS]),
-			  &ssid, &ssid_len);
-		if (ssid && ssid_len && ssid_len <= sizeof (info->ssid)) {
+		           nla_len (bss[NL80211_BSS_INFORMATION_ELEMENTS]),
+		           &ssid,
+		           &ssid_len);
+		if (   ssid
+		    && ssid_len
+		    && ssid_len <= sizeof (info->ssid)) {
 			memcpy (info->ssid, ssid, ssid_len);
 			info->ssid_len = ssid_len;
 		}
@@ -492,25 +491,25 @@ nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 }
 
 static void
-nl80211_get_bss_info (NMWifiUtilsNl80211 *nl80211,
+nl80211_get_bss_info (NMWifiUtilsNl80211 *self,
                       struct nl80211_bss_info *bss_info)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
 	memset (bss_info, 0, sizeof (*bss_info));
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_SCAN, NLM_F_DUMP);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_GET_SCAN, NLM_F_DUMP);
 
-	nl80211_send_and_recv (nl80211, msg, nl80211_bss_dump_handler, bss_info);
+	nl80211_send_and_recv (self, msg, nl80211_bss_dump_handler, bss_info);
 }
 
 static guint32
 wifi_nl80211_get_freq (NMWifiUtils *data)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_bss_info bss_info;
 
-	nl80211_get_bss_info (nl80211, &bss_info);
+	nl80211_get_bss_info (self, &bss_info);
 
 	return bss_info.freq;
 }
@@ -518,12 +517,12 @@ wifi_nl80211_get_freq (NMWifiUtils *data)
 static guint32
 wifi_nl80211_find_freq (NMWifiUtils *data, const guint32 *freqs)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	int i;
 
-	for (i = 0; i < nl80211->num_freqs; i++) {
+	for (i = 0; i < self->num_freqs; i++) {
 		while (*freqs) {
-			if (nl80211->freqs[i] == *freqs)
+			if (self->freqs[i] == *freqs)
 				return *freqs;
 			freqs++;
 		}
@@ -534,10 +533,10 @@ wifi_nl80211_find_freq (NMWifiUtils *data, const guint32 *freqs)
 static gboolean
 wifi_nl80211_get_bssid (NMWifiUtils *data, guint8 *out_bssid)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_bss_info bss_info;
 
-	nl80211_get_bss_info (nl80211, &bss_info);
+	nl80211_get_bss_info (self, &bss_info);
 
 	if (bss_info.valid)
 		memcpy (out_bssid, bss_info.bssid, ETH_ALEN);
@@ -555,49 +554,50 @@ struct nl80211_station_info {
 static int
 nl80211_station_handler (struct nl_msg *msg, void *arg)
 {
-	struct nl80211_station_info *info = arg;
-	struct nlattr *tb[NL80211_ATTR_MAX + 1];
-	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
-	struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1];
-	struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1];
-	static const struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = {
+	static const struct nla_policy stats_policy[] = {
 		[NL80211_STA_INFO_INACTIVE_TIME] = { .type = NLA_U32 },
-		[NL80211_STA_INFO_RX_BYTES] = { .type = NLA_U32 },
-		[NL80211_STA_INFO_TX_BYTES] = { .type = NLA_U32 },
-		[NL80211_STA_INFO_RX_PACKETS] = { .type = NLA_U32 },
-		[NL80211_STA_INFO_TX_PACKETS] = { .type = NLA_U32 },
-		[NL80211_STA_INFO_SIGNAL] = { .type = NLA_U8 },
-		[NL80211_STA_INFO_TX_BITRATE] = { .type = NLA_NESTED },
-		[NL80211_STA_INFO_LLID] = { .type = NLA_U16 },
-		[NL80211_STA_INFO_PLID] = { .type = NLA_U16 },
-		[NL80211_STA_INFO_PLINK_STATE] = { .type = NLA_U8 },
+		[NL80211_STA_INFO_RX_BYTES]      = { .type = NLA_U32 },
+		[NL80211_STA_INFO_TX_BYTES]      = { .type = NLA_U32 },
+		[NL80211_STA_INFO_RX_PACKETS]    = { .type = NLA_U32 },
+		[NL80211_STA_INFO_TX_PACKETS]    = { .type = NLA_U32 },
+		[NL80211_STA_INFO_SIGNAL]        = { .type = NLA_U8 },
+		[NL80211_STA_INFO_TX_BITRATE]    = { .type = NLA_NESTED },
+		[NL80211_STA_INFO_LLID]          = { .type = NLA_U16 },
+		[NL80211_STA_INFO_PLID]          = { .type = NLA_U16 },
+		[NL80211_STA_INFO_PLINK_STATE]   = { .type = NLA_U8 },
 	};
-
-	static const struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {
-		[NL80211_RATE_INFO_BITRATE] = { .type = NLA_U16 },
-		[NL80211_RATE_INFO_MCS] = { .type = NLA_U8 },
+	static const struct nla_policy rate_policy[] = {
+		[NL80211_RATE_INFO_BITRATE]      = { .type = NLA_U16 },
+		[NL80211_RATE_INFO_MCS]          = { .type = NLA_U8 },
 		[NL80211_RATE_INFO_40_MHZ_WIDTH] = { .type = NLA_FLAG },
-		[NL80211_RATE_INFO_SHORT_GI] = { .type = NLA_FLAG },
+		[NL80211_RATE_INFO_SHORT_GI]     = { .type = NLA_FLAG },
 	};
+	struct nlattr *rinfo[G_N_ELEMENTS (rate_policy)];
+	struct nlattr *sinfo[G_N_ELEMENTS (stats_policy)];
+	struct nl80211_station_info *info = arg;
+	struct nlattr *tb[NL80211_ATTR_MAX + 1];
+	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
+	if (nla_parse_arr (tb,
+	                   genlmsg_attrdata (gnlh, 0),
+	                   genlmsg_attrlen (gnlh, 0),
+	                   NULL) < 0)
 		return NL_SKIP;
 
 	if (tb[NL80211_ATTR_STA_INFO] == NULL)
 		return NL_SKIP;
 
-	if (nla_parse_nested (sinfo, NL80211_STA_INFO_MAX,
-	                      tb[NL80211_ATTR_STA_INFO],
-	                      stats_policy))
+	if (nla_parse_nested_arr (sinfo,
+	                          tb[NL80211_ATTR_STA_INFO],
+	                          stats_policy))
 		return NL_SKIP;
 
 	if (sinfo[NL80211_STA_INFO_TX_BITRATE] == NULL)
 		return NL_SKIP;
 
-	if (nla_parse_nested (rinfo, NL80211_RATE_INFO_MAX,
-	                      sinfo[NL80211_STA_INFO_TX_BITRATE],
-	                      rate_policy))
+	if (nla_parse_nested_arr (rinfo,
+	                          sinfo[NL80211_STA_INFO_TX_BITRATE],
+	                          rate_policy))
 		return NL_SKIP;
 
 	if (rinfo[NL80211_RATE_INFO_BITRATE] == NULL)
@@ -616,7 +616,7 @@ nl80211_station_handler (struct nl_msg *msg, void *arg)
 }
 
 static void
-nl80211_get_ap_info (NMWifiUtilsNl80211 *nl80211,
+nl80211_get_ap_info (NMWifiUtilsNl80211 *self,
                      struct nl80211_station_info *sta_info)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -624,15 +624,15 @@ nl80211_get_ap_info (NMWifiUtilsNl80211 *nl80211,
 
 	memset (sta_info, 0, sizeof (*sta_info));
 
-	nl80211_get_bss_info (nl80211, &bss_info);
+	nl80211_get_bss_info (self, &bss_info);
 	if (!bss_info.valid)
 		return;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_STATION, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_GET_STATION, 0);
 	if (msg) {
 		NLA_PUT (msg, NL80211_ATTR_MAC, ETH_ALEN, bss_info.bssid);
 
-		nl80211_send_and_recv (nl80211, msg, nl80211_station_handler, sta_info);
+		nl80211_send_and_recv (self, msg, nl80211_station_handler, sta_info);
 		if (!sta_info->signal_valid) {
 			/* Fall back to bss_info signal quality (both are in percent) */
 			sta_info->signal = bss_info.beacon_signal;
@@ -648,10 +648,10 @@ nla_put_failure:
 static guint32
 wifi_nl80211_get_rate (NMWifiUtils *data)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_station_info sta_info;
 
-	nl80211_get_ap_info (nl80211, &sta_info);
+	nl80211_get_ap_info (self, &sta_info);
 
 	return sta_info.txrate;
 }
@@ -659,21 +659,21 @@ wifi_nl80211_get_rate (NMWifiUtils *data)
 static int
 wifi_nl80211_get_qual (NMWifiUtils *data)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_station_info sta_info;
 
-	nl80211_get_ap_info (nl80211, &sta_info);
+	nl80211_get_ap_info (self, &sta_info);
 	return sta_info.signal;
 }
 
 static gboolean
 wifi_nl80211_indicate_addressing_running (NMWifiUtils *data, gboolean running)
 {
-	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
-	msg = nl80211_alloc_msg (nl80211,
+	msg = nl80211_alloc_msg (self,
 	                         running
 	                           ? 98 /* NL80211_CMD_CRIT_PROTOCOL_START */
 	                           : 99 /* NL80211_CMD_CRIT_PROTOCOL_STOP */,
@@ -691,7 +691,7 @@ wifi_nl80211_indicate_addressing_running (NMWifiUtils *data, gboolean running)
 		             5000);
 	}
 
-	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
+	err = nl80211_send_and_recv (self, msg, NULL, NULL);
 	return err >= 0;
 
 nla_put_failure:
@@ -699,6 +699,7 @@ nla_put_failure:
 }
 
 struct nl80211_device_info {
+	NMWifiUtilsNl80211 *self;
 	int phy;
 	guint32 *freqs;
 	int num_freqs;
@@ -721,36 +722,40 @@ struct nl80211_device_info {
 
 static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 {
+	static const struct nla_policy freq_policy[] = {
+		[NL80211_FREQUENCY_ATTR_FREQ]         = { .type = NLA_U32 },
+		[NL80211_FREQUENCY_ATTR_DISABLED]     = { .type = NLA_FLAG },
+#ifdef NL80211_FREQUENCY_ATTR_NO_IR
+		[NL80211_FREQUENCY_ATTR_NO_IR]        = { .type = NLA_FLAG },
+#else
+		[NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] = { .type = NLA_FLAG },
+		[NL80211_FREQUENCY_ATTR_NO_IBSS]      = { .type = NLA_FLAG },
+#endif
+		[NL80211_FREQUENCY_ATTR_RADAR]        = { .type = NLA_FLAG },
+		[NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = { .type = NLA_U32 },
+	};
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 	struct nl80211_device_info *info = arg;
+	NMWifiUtilsNl80211 *self = info->self;
 	struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1];
-	struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1];
+	struct nlattr *tb_freq[G_N_ELEMENTS (freq_policy)];
 	struct nlattr *nl_band;
 	struct nlattr *nl_freq;
 	int rem_freq;
 	int rem_band;
 	int freq_idx;
-	static const struct nla_policy freq_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = {
-		[NL80211_FREQUENCY_ATTR_FREQ] = { .type = NLA_U32 },
-		[NL80211_FREQUENCY_ATTR_DISABLED] = { .type = NLA_FLAG },
-#ifdef NL80211_FREQUENCY_ATTR_NO_IR
-		[NL80211_FREQUENCY_ATTR_NO_IR] = { .type = NLA_FLAG },
-#else
-		[NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] = { .type = NLA_FLAG },
-		[NL80211_FREQUENCY_ATTR_NO_IBSS] = { .type = NLA_FLAG },
-#endif
-		[NL80211_FREQUENCY_ATTR_RADAR] = { .type = NLA_FLAG },
-		[NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = { .type = NLA_U32 },
-	};
+
 #ifdef NL80211_FREQUENCY_ATTR_NO_IR
-	G_STATIC_ASSERT (NL80211_FREQUENCY_ATTR_PASSIVE_SCAN == NL80211_FREQUENCY_ATTR_NO_IR && NL80211_FREQUENCY_ATTR_NO_IBSS == NL80211_FREQUENCY_ATTR_NO_IR);
+	G_STATIC_ASSERT_EXPR (NL80211_FREQUENCY_ATTR_PASSIVE_SCAN == NL80211_FREQUENCY_ATTR_NO_IR && NL80211_FREQUENCY_ATTR_NO_IBSS == NL80211_FREQUENCY_ATTR_NO_IR);
 #else
-	G_STATIC_ASSERT (NL80211_FREQUENCY_ATTR_PASSIVE_SCAN != NL80211_FREQUENCY_ATTR_NO_IBSS);
+	G_STATIC_ASSERT_EXPR (NL80211_FREQUENCY_ATTR_PASSIVE_SCAN != NL80211_FREQUENCY_ATTR_NO_IBSS);
 #endif
 
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
+	if (nla_parse_arr (tb,
+	                   genlmsg_attrdata (gnlh, 0),
+	                   genlmsg_attrlen (gnlh, 0),
+	                   NULL) < 0)
 		return NL_SKIP;
 
 	if (   tb[NL80211_ATTR_WIPHY] == NULL
@@ -796,14 +801,17 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 	info->num_freqs = 0;
 
 	nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
-		if (nla_parse_nested (tb_band, NL80211_BAND_ATTR_MAX, nl_band,
-		                      NULL) < 0)
+		if (nla_parse_nested_arr (tb_band,
+		                          nl_band,
+		                          NULL) < 0)
 			return NL_SKIP;
 
-		nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS],
+		nla_for_each_nested (nl_freq,
+		                     tb_band[NL80211_BAND_ATTR_FREQS],
 		                     rem_freq) {
-			if (nla_parse_nested (tb_freq, NL80211_FREQUENCY_ATTR_MAX,
-			                      nl_freq, freq_policy) < 0)
+			if (nla_parse_nested_arr (tb_freq,
+			                          nl_freq,
+			                          freq_policy) < 0)
 				continue;
 
 			if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
@@ -818,21 +826,22 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 
 	freq_idx = 0;
 	nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
-		if (nla_parse_nested (tb_band, NL80211_BAND_ATTR_MAX, nl_band,
-		                      NULL) < 0)
+		if (nla_parse_nested_arr (tb_band,
+		                          nl_band,
+		                          NULL) < 0)
 			return NL_SKIP;
 
 		nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS],
 		                    rem_freq) {
-			if (nla_parse_nested (tb_freq, NL80211_FREQUENCY_ATTR_MAX,
-			                      nl_freq, freq_policy) < 0)
+			if (nla_parse_nested_arr (tb_freq,
+			                          nl_freq,
+			                          freq_policy) < 0)
 				continue;
 
 			if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
 				continue;
 
-			info->freqs[freq_idx] =
-				nla_get_u32 (tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
+			info->freqs[freq_idx] = nla_get_u32 (tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
 
 			info->caps |= NM_WIFI_DEVICE_CAP_FREQ_VALID;
 
@@ -847,11 +856,10 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 
 	/* Read security/encryption support */
 	if (tb[NL80211_ATTR_CIPHER_SUITES]) {
-		int num;
-		int i;
-		__u32 *ciphers = nla_data (tb[NL80211_ATTR_CIPHER_SUITES]);
+		guint32 *ciphers = nla_data (tb[NL80211_ATTR_CIPHER_SUITES]);
+		guint i, num;
 
-		num = nla_len (tb[NL80211_ATTR_CIPHER_SUITES]) / sizeof (__u32);
+		num = nla_len (tb[NL80211_ATTR_CIPHER_SUITES]) / sizeof (guint32);
 		for (i = 0; i < num; i++) {
 			switch (ciphers[i]) {
 			case WLAN_CIPHER_SUITE_WEP40:
@@ -873,8 +881,7 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 			case WLAN_CIPHER_SUITE_SMS4:
 				break;
 			default:
-				_LOGD (LOGD_PLATFORM | LOGD_WIFI,
-				       "don't know the meaning of NL80211_ATTR_CIPHER_SUITE %#8.8x.",
+				_LOGD ("don't know the meaning of NL80211_ATTR_CIPHER_SUITE %#8.8x.",
 				       ciphers[i]);
 				break;
 			}
@@ -930,86 +937,66 @@ nm_wifi_utils_nl80211_class_init (NMWifiUtilsNl80211Class *klass)
 NMWifiUtils *
 nm_wifi_utils_nl80211_new (int ifindex, struct nl_sock *genl)
 {
-	gs_unref_object NMWifiUtilsNl80211 *nl80211 = NULL;
+	gs_unref_object NMWifiUtilsNl80211 *self = NULL;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
-	struct nl80211_device_info device_info = {};
-	char ifname[IFNAMSIZ];
+	struct nl80211_device_info device_info = { };
 
 	if (!genl)
 		return NULL;
 
-	if (!nmp_utils_if_indextoname (ifindex, ifname)) {
-		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
-		       "can't determine interface name for ifindex %d", ifindex);
-		nm_sprintf_buf (ifname, "if %d", ifindex);
-	}
-
-	nl80211 = g_object_new (NM_TYPE_WIFI_UTILS_NL80211, NULL);
+	self = g_object_new (NM_TYPE_WIFI_UTILS_NL80211, NULL);
 
-	nl80211->parent.ifindex = ifindex;
-	nl80211->nl_sock = genl;
+	self->parent.ifindex = ifindex;
+	self->nl_sock = genl;
 
-	nl80211->id = genl_ctrl_resolve (nl80211->nl_sock, "nl80211");
-	if (nl80211->id < 0) {
-		_LOGD (LOGD_WIFI, "genl_ctrl_resolve: failed to resolve \"nl80211\"");
+	self->id = genl_ctrl_resolve (self->nl_sock, "nl80211");
+	if (self->id < 0) {
+		_LOGD ("genl_ctrl_resolve: failed to resolve \"nl80211\"");
 		return NULL;
 	}
 
-	nl80211->phy = -1;
+	self->phy = -1;
 
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WIPHY, 0);
+	msg = nl80211_alloc_msg (self, NL80211_CMD_GET_WIPHY, 0);
 
-	if (nl80211_send_and_recv (nl80211, msg, nl80211_wiphy_info_handler,
+	device_info.self = self;
+	if (nl80211_send_and_recv (self, msg, nl80211_wiphy_info_handler,
 	                           &device_info) < 0) {
-		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
-		       "(%s): NL80211_CMD_GET_WIPHY request failed",
-		       ifname);
+		_LOGD ("NL80211_CMD_GET_WIPHY request failed");
 		return NULL;
 	}
 
 	if (!device_info.success) {
-		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
-		       "(%s): NL80211_CMD_GET_WIPHY request indicated failure",
-		       ifname);
+		_LOGD ("NL80211_CMD_GET_WIPHY request indicated failure");
 		return NULL;
 	}
 
 	if (!device_info.supported) {
-		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
-		       "(%s): driver does not fully support nl80211, falling back to WEXT",
-		       ifname);
+		_LOGD ("driver does not fully support nl80211, falling back to WEXT");
 		return NULL;
 	}
 
 	if (!device_info.can_scan_ssid) {
-		_LOGE (LOGD_PLATFORM | LOGD_WIFI,
-		       "(%s): driver does not support SSID scans",
-		       ifname);
+		_LOGE ("driver does not support SSID scans");
 		return NULL;
 	}
 
 	if (device_info.num_freqs == 0 || device_info.freqs == NULL) {
-		nm_log_err (LOGD_PLATFORM | LOGD_WIFI,
-		            "(%s): driver reports no supported frequencies",
-		            ifname);
+		_LOGE ("driver reports no supported frequencies");
 		return NULL;
 	}
 
 	if (device_info.caps == 0) {
-		_LOGE (LOGD_PLATFORM | LOGD_WIFI,
-		       "(%s): driver doesn't report support of any encryption",
-		       ifname);
+		_LOGE ("driver doesn't report support of any encryption");
 		return NULL;
 	}
 
-	nl80211->phy = device_info.phy;
-	nl80211->freqs = device_info.freqs;
-	nl80211->num_freqs = device_info.num_freqs;
-	nl80211->parent.caps = device_info.caps;
-	nl80211->can_wowlan = device_info.can_wowlan;
+	self->phy = device_info.phy;
+	self->freqs = device_info.freqs;
+	self->num_freqs = device_info.num_freqs;
+	self->parent.caps = device_info.caps;
+	self->can_wowlan = device_info.can_wowlan;
 
-	_LOGI (LOGD_PLATFORM | LOGD_WIFI,
-	       "(%s): using nl80211 for WiFi device control",
-	       ifname);
-	return (NMWifiUtils *) g_steal_pointer (&nl80211);
+	_LOGD ("using nl80211 for Wi-Fi device control");
+	return (NMWifiUtils *) g_steal_pointer (&self);
 }
diff --git a/src/platform/wifi/nm-wifi-utils-wext.c b/src/platform/wifi/nm-wifi-utils-wext.c
index e52ae5a3..3aa1720a 100644
--- a/src/platform/wifi/nm-wifi-utils-wext.c
+++ b/src/platform/wifi/nm-wifi-utils-wext.c
@@ -23,8 +23,6 @@
 
 #include "nm-wifi-utils-wext.h"
 
-#include <errno.h>
-#include <string.h>
 #include <sys/ioctl.h>
 #include <net/ethernet.h>
 #include <unistd.h>
@@ -117,7 +115,7 @@ get_ifname (int ifindex, char *buffer, const char *op)
 		errsv = errno;
 		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 		       "error getting interface name for ifindex %d, operation '%s': %s (%d)",
-		       ifindex, op, g_strerror (errsv), errsv);
+		       ifindex, op, nm_strerror_native (errsv), errsv);
 		return FALSE;
 	}
 
@@ -129,15 +127,17 @@ wifi_wext_get_mode_ifname (NMWifiUtils *data, const char *ifname)
 {
 	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
+	int errsv;
 
 	memset (&wrq, 0, sizeof (struct iwreq));
 	nm_utils_ifname_cpy (wrq.ifr_name, ifname);
 
 	if (ioctl (wext->fd, SIOCGIWMODE, &wrq) < 0) {
-		if (errno != ENODEV) {
+		errsv = errno;
+		if (errsv != ENODEV) {
 			_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 			       "(%s): error %d getting card mode",
-			       ifname, errno);
+			       ifname, errsv);
 		}
 		return NM_802_11_MODE_UNKNOWN;
 	}
@@ -253,7 +253,7 @@ wifi_wext_get_freq (NMWifiUtils *data)
 	if (ioctl (wext->fd, SIOCGIWFREQ, &wrq) < 0) {
 		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): error getting frequency: %s",
-		       ifname, strerror (errno));
+		       ifname, nm_strerror_native (errno));
 		return 0;
 	}
 
@@ -291,7 +291,7 @@ wifi_wext_get_bssid (NMWifiUtils *data, guint8 *out_bssid)
 	if (ioctl (wext->fd, SIOCGIWAP, &wrq) < 0) {
 		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): error getting associated BSSID: %s",
-		       ifname, strerror (errno));
+		       ifname, nm_strerror_native (errno));
 		return FALSE;
 	}
 	memcpy (out_bssid, &(wrq.u.ap_addr.sa_data), ETH_ALEN);
@@ -429,7 +429,7 @@ wifi_wext_get_qual (NMWifiUtils *data)
 	if (ioctl (wext->fd, SIOCGIWSTATS, &wrq) < 0) {
 		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): error getting signal strength: %s",
-		       ifname, strerror (errno));
+		       ifname, nm_strerror_native (errno));
 		return -1;
 	}
 
@@ -476,7 +476,7 @@ wifi_wext_set_mesh_channel (NMWifiUtils *data, guint32 channel)
 	if (ioctl (wext->fd, SIOCSIWFREQ, &wrq) < 0) {
 		_LOGE (LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC,
 		       "(%s): error setting channel to %d: %s",
-		       ifname, channel, strerror (errno));
+		       ifname, channel, nm_strerror_native (errno));
 		return FALSE;
 	}
 
@@ -506,15 +506,15 @@ wifi_wext_set_mesh_ssid (NMWifiUtils *data, const guint8 *ssid, gsize len)
 	if (ioctl (wext->fd, SIOCSIWESSID, &wrq) == 0)
 		return TRUE;
 
-	if (errno != ENODEV) {
+	errsv = errno;
+	if (errsv != ENODEV) {
 		gs_free char *ssid_str = NULL;
 
-		errsv = errno;
 		_LOGE (LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC,
-		       "(%s): error setting SSID to '%s': %s",
+		       "(%s): error setting SSID to %s: %s",
 		       ifname,
 		       (ssid_str = _nm_utils_ssid_to_string_arr (ssid, len)),
-		       strerror (errsv));
+		       nm_strerror_native (errsv));
 	}
 
 	return FALSE;
@@ -545,6 +545,7 @@ wext_get_range_ifname (NMWifiUtilsWext *wext,
 	int i = 26;
 	gboolean success = FALSE;
 	struct iwreq wrq;
+	int errsv;
 
 	memset (&wrq, 0, sizeof (struct iwreq));
 	nm_utils_ifname_cpy (wrq.ifr_name, ifname);
@@ -561,11 +562,14 @@ wext_get_range_ifname (NMWifiUtilsWext *wext,
 				*response_len = wrq.u.data.length;
 			success = TRUE;
 			break;
-		} else if (errno != EAGAIN) {
-			_LOGE (LOGD_PLATFORM | LOGD_WIFI,
-			       "(%s): couldn't get driver range information (%d).",
-			       ifname, errno);
-			break;
+		} else {
+			errsv = errno;
+			if (errsv != EAGAIN) {
+				_LOGE (LOGD_PLATFORM | LOGD_WIFI,
+				       "(%s): couldn't get driver range information (%d).",
+				       ifname, errsv);
+				break;
+			}
 		}
 
 		g_usleep (G_USEC_PER_SEC / 4);
@@ -751,7 +755,7 @@ nm_wifi_utils_wext_new (int ifindex, gboolean check_scan)
 		wext->parent.caps |= NM_WIFI_DEVICE_CAP_FREQ_5GHZ;
 
 	_LOGI (LOGD_PLATFORM | LOGD_WIFI,
-	       "(%s): using WEXT for WiFi device control",
+	       "(%s): using WEXT for Wi-Fi device control",
 	       ifname);
 
 	return (NMWifiUtils *) wext;
@@ -771,7 +775,7 @@ nm_wifi_utils_wext_is_wifi (const char *iface)
 	/* performing an ioctl on a non-existing name may cause the automatic
 	 * loading of kernel modules, which should be avoided.
 	 *
-	 * Usually, we should thus make sure that an inteface with this name
+	 * Usually, we should thus make sure that an interface with this name
 	 * exists.
 	 *
 	 * Note that wifi_wext_is_wifi() has only one caller which just verified
diff --git a/src/platform/wifi/nm-wifi-utils.c b/src/platform/wifi/nm-wifi-utils.c
index 25d71c6a..96071faa 100644
--- a/src/platform/wifi/nm-wifi-utils.c
+++ b/src/platform/wifi/nm-wifi-utils.c
@@ -25,7 +25,6 @@
 
 #include <sys/stat.h>
 #include <stdio.h>
-#include <string.h>
 #include <fcntl.h>
 
 #include "nm-wifi-utils-private.h"
diff --git a/src/platform/wifi/nm-wifi-utils.h b/src/platform/wifi/nm-wifi-utils.h
index 6cd178bd..36148b5a 100644
--- a/src/platform/wifi/nm-wifi-utils.h
+++ b/src/platform/wifi/nm-wifi-utils.h
@@ -62,7 +62,7 @@ gboolean nm_wifi_utils_get_bssid (NMWifiUtils *data, guint8 *out_bssid);
 /* Returns current bitrate in Kbps */
 guint32 nm_wifi_utils_get_rate (NMWifiUtils *data);
 
-/* Returns quality 0 - 100% on succes, or -1 on error */
+/* Returns quality 0 - 100% on success, or -1 on error */
 int nm_wifi_utils_get_qual (NMWifiUtils *data);
 
 /* Tells the driver DHCP or SLAAC is running */
diff --git a/src/platform/wpan/nm-wpan-utils.c b/src/platform/wpan/nm-wpan-utils.c
index 882c4eec..b7a51e9b 100644
--- a/src/platform/wpan/nm-wpan-utils.c
+++ b/src/platform/wpan/nm-wpan-utils.c
@@ -25,13 +25,18 @@
 
 #include "platform/linux/nl802154.h"
 #include "platform/nm-netlink.h"
+#include "platform/nm-platform-utils.h"
 
 #define _NMLOG_PREFIX_NAME "wpan-nl802154"
 #define _NMLOG(level, domain, ...) \
 	G_STMT_START { \
-		nm_log ((level), (domain), NULL, NULL, \
-		        "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-		        _NMLOG_PREFIX_NAME \
+		char _ifname_buf[IFNAMSIZ]; \
+		const char *_ifname = self ? nmp_utils_if_indextoname (self->ifindex, _ifname_buf) : NULL; \
+		\
+		nm_log ((level), (domain), _ifname ?: NULL, NULL, \
+		        "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+		        _NMLOG_PREFIX_NAME, \
+		        NM_PRINT_FMT_QUOTED (_ifname, " (", _ifname, ")", "") \
 		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
 	} G_STMT_END
 
@@ -97,10 +102,10 @@ nl802154_alloc_msg (NMWpanUtils *self, guint32 cmd, guint32 flags)
 }
 
 static int
-_nl802154_send_and_recv (struct nl_sock *nl_sock,
-                         struct nl_msg *msg,
-                         int (*valid_handler) (struct nl_msg *, void *),
-                         void *valid_data)
+nl802154_send_and_recv (NMWpanUtils *self,
+                        struct nl_msg *msg,
+                        int (*valid_handler) (struct nl_msg *, void *),
+                        void *valid_data)
 {
 	int err;
 	int done = 0;
@@ -117,7 +122,7 @@ _nl802154_send_and_recv (struct nl_sock *nl_sock,
 
 	g_return_val_if_fail (msg != NULL, -ENOMEM);
 
-	err = nl_send_auto (nl_sock, msg);
+	err = nl_send_auto (self->nl_sock, msg);
 	if (err < 0)
 		return err;
 
@@ -125,10 +130,10 @@ _nl802154_send_and_recv (struct nl_sock *nl_sock,
 	 * done will be 1, on error it will be < 0.
 	 */
 	while (!done) {
-		err = nl_recvmsgs (nl_sock, &cb);
+		err = nl_recvmsgs (self->nl_sock, &cb);
 		if (err < 0 && err != -EAGAIN) {
 			_LOGW (LOGD_PLATFORM, "nl_recvmsgs() error: (%d) %s",
-			       err, nl_geterror (err));
+			       err, nm_strerror (err));
 			break;
 		}
 	}
@@ -138,16 +143,6 @@ _nl802154_send_and_recv (struct nl_sock *nl_sock,
 	return err;
 }
 
-static int
-nl802154_send_and_recv (NMWpanUtils *self,
-                        struct nl_msg *msg,
-                        int (*valid_handler) (struct nl_msg *, void *),
-                        void *valid_data)
-{
-	return _nl802154_send_and_recv (self->nl_sock, msg,
-	                                valid_handler, valid_data);
-}
-
 struct nl802154_interface {
 	guint16 pan_id;
 	guint16 short_addr;
@@ -158,17 +153,19 @@ struct nl802154_interface {
 static int
 nl802154_get_interface_handler (struct nl_msg *msg, void *arg)
 {
+	static const struct nla_policy nl802154_policy[] = {
+		[NL802154_ATTR_PAN_ID]     = { .type = NLA_U16 },
+		[NL802154_ATTR_SHORT_ADDR] = { .type = NLA_U16 },
+	};
+	struct nlattr *tb[G_N_ELEMENTS (nl802154_policy)];
 	struct nl802154_interface *info = arg;
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
-	struct nlattr *tb[NL802154_ATTR_MAX + 1] = { 0, };
-	static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX + 1] = {
-		[NL802154_ATTR_PAN_ID] =            { .type = NLA_U16 },
-		[NL802154_ATTR_SHORT_ADDR] =        { .type = NLA_U16 },
-	};
 
-	if (nla_parse (tb, NL802154_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), nl802154_policy) < 0)
-	return NL_SKIP;
+	if (nla_parse_arr (tb,
+	                   genlmsg_attrdata (gnlh, 0),
+	                   genlmsg_attrlen (gnlh, 0),
+	                   nl802154_policy) < 0)
+		return NL_SKIP;
 
 	if (tb[NL802154_ATTR_PAN_ID])
 		info->pan_id = le16toh (nla_get_u16 (tb[NL802154_ATTR_PAN_ID]));
@@ -250,6 +247,24 @@ nla_put_failure:
 	return FALSE;
 }
 
+gboolean
+nm_wpan_utils_set_channel (NMWpanUtils *self, guint8 page, guint8 channel)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	int err;
+
+	g_return_val_if_fail (self != NULL, FALSE);
+
+	msg = nl802154_alloc_msg (self, NL802154_CMD_SET_CHANNEL, 0);
+	NLA_PUT_U8 (msg, NL802154_ATTR_PAGE, page);
+	NLA_PUT_U8 (msg, NL802154_ATTR_CHANNEL, channel);
+	err = nl802154_send_and_recv (self, msg, NULL, NULL);
+	return err >= 0;
+
+nla_put_failure:
+	return FALSE;
+}
+
 /*****************************************************************************/
 
 static void
@@ -266,23 +281,22 @@ NMWpanUtils *
 nm_wpan_utils_new (int ifindex, struct nl_sock *genl, gboolean check_scan)
 {
 	NMWpanUtils *self;
-	int id;
 
 	g_return_val_if_fail (ifindex > 0, NULL);
 
 	if (!genl)
 		return NULL;
 
-	id = genl_ctrl_resolve (genl, "nl802154");
-	if (id < 0) {
-		_LOGD (LOGD_PLATFORM, "genl_ctrl_resolve: failed to resolve \"nl802154\"");
-		return NULL;
-	}
-
 	self = g_object_new (NM_TYPE_WPAN_UTILS, NULL);
 	self->ifindex = ifindex;
 	self->nl_sock = genl;
-	self->id = id;
+	self->id = genl_ctrl_resolve (genl, "nl802154");
+
+	if (self->id < 0) {
+		_LOGD (LOGD_PLATFORM, "genl_ctrl_resolve: failed to resolve \"nl802154\"");
+		g_object_unref (self);
+		return NULL;
+	}
 
 	return self;
 }
diff --git a/src/platform/wpan/nm-wpan-utils.h b/src/platform/wpan/nm-wpan-utils.h
index f7d0c03e..1b54ec49 100644
--- a/src/platform/wpan/nm-wpan-utils.h
+++ b/src/platform/wpan/nm-wpan-utils.h
@@ -44,4 +44,6 @@ gboolean nm_wpan_utils_set_pan_id (NMWpanUtils *self, guint16 pan_id);
 guint16 nm_wpan_utils_get_short_addr (NMWpanUtils *self);
 gboolean nm_wpan_utils_set_short_addr (NMWpanUtils *self, guint16 short_addr);
 
+gboolean nm_wpan_utils_set_channel (NMWpanUtils *self, guint8 page, guint8 channel);
+
 #endif  /* __WPAN_UTILS_H__ */
diff --git a/src/ppp/meson.build b/src/ppp/meson.build
index 20ed64ed..a0f2df50 100644
--- a/src/ppp/meson.build
+++ b/src/ppp/meson.build
@@ -2,7 +2,7 @@ name = 'nm-pppd-plugin'
 
 deps = [
   dl_dep,
-  nm_core_dep
+  nm_core_dep,
 ]
 
 nm_pppd_plugin = shared_module(
@@ -16,13 +16,13 @@ nm_pppd_plugin = shared_module(
     '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB',
   ],
   install: true,
-  install_dir: pppd_plugin_dir
+  install_dir: pppd_plugin_dir,
 )
 
 name = 'nm-ppp-plugin'
 
 deps = [
-  nm_dep
+  nm_dep,
 ]
 
 linker_script = join_paths(meson.current_source_dir(), 'nm-ppp-plugin.ver')
@@ -36,5 +36,5 @@ core_plugins += shared_module(
   ],
   link_depends: linker_script,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
diff --git a/src/ppp/nm-ppp-manager-call.c b/src/ppp/nm-ppp-manager-call.c
index 3d6fee49..d951e4fd 100644
--- a/src/ppp/nm-ppp-manager-call.c
+++ b/src/ppp/nm-ppp-manager-call.c
@@ -24,7 +24,6 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <errno.h>
 
 #include "nm-manager.h"
 #include "nm-core-utils.h"
@@ -126,12 +125,13 @@ nm_ppp_manager_start (NMPPPManager *self,
 
 NMPPPManagerStopHandle *
 nm_ppp_manager_stop (NMPPPManager *self,
+                     GCancellable *cancellable,
                      NMPPPManagerStopCallback callback,
                      gpointer user_data)
 {
 	g_return_val_if_fail (ppp_ops, NULL);
 
-	return ppp_ops->stop (self, callback, user_data);
+	return ppp_ops->stop (self, cancellable, callback, user_data);
 }
 
 void
diff --git a/src/ppp/nm-ppp-manager-call.h b/src/ppp/nm-ppp-manager-call.h
index daf8a82e..a93ed11f 100644
--- a/src/ppp/nm-ppp-manager-call.h
+++ b/src/ppp/nm-ppp-manager-call.h
@@ -40,6 +40,7 @@ gboolean            nm_ppp_manager_start       (NMPPPManager *self,
                                                 GError **error);
 
 NMPPPManagerStopHandle *nm_ppp_manager_stop (NMPPPManager *self,
+                                             GCancellable *cancellable,
                                              NMPPPManagerStopCallback callback,
                                              gpointer user_data);
 
diff --git a/src/ppp/nm-ppp-manager.c b/src/ppp/nm-ppp-manager.c
index b231ff20..04c14dfb 100644
--- a/src/ppp/nm-ppp-manager.c
+++ b/src/ppp/nm-ppp-manager.c
@@ -26,11 +26,9 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <signal.h>
-#include <string.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
 #include <asm/types.h>
@@ -137,9 +135,12 @@ G_DEFINE_TYPE (NMPPPManager, nm_ppp_manager, NM_TYPE_DBUS_OBJECT)
 static void _ppp_cleanup  (NMPPPManager *self);
 
 static NMPPPManagerStopHandle *_ppp_manager_stop (NMPPPManager *self,
+                                                  GCancellable *cancellable,
                                                   NMPPPManagerStopCallback callback,
                                                   gpointer user_data);
 
+static void _ppp_manager_stop_cancel (NMPPPManagerStopHandle *handle);
+
 /*****************************************************************************/
 
 static void
@@ -179,6 +180,7 @@ monitor_cb (gpointer user_data)
 	NMPPPManager *self = NM_PPP_MANAGER (user_data);
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (self);
 	const char *ifname;
+	int errsv;
 
 	ifname = nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex);
 
@@ -190,8 +192,9 @@ monitor_cb (gpointer user_data)
 
 		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));
+			errsv = errno;
+			if (errsv != ENODEV)
+				_LOGW ("could not read ppp stats: %s", nm_strerror_native (errsv));
 		} else {
 			g_signal_emit (self, signals[STATS], 0,
 			               (guint) stats.p.ppp_ibytes,
@@ -206,19 +209,23 @@ static void
 monitor_stats (NMPPPManager *self)
 {
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (self);
+	int errsv;
 
 	/* already monitoring */
 	if (priv->monitor_fd >= 0)
 		return;
 
 	priv->monitor_fd = socket (AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-	if (priv->monitor_fd >= 0) {
-		g_warn_if_fail (priv->monitor_id == 0);
-		if (priv->monitor_id)
-			g_source_remove (priv->monitor_id);
-		priv->monitor_id = g_timeout_add_seconds (5, monitor_cb, self);
-	} else
-		_LOGW ("could not monitor PPP stats: %s", strerror (errno));
+	if (priv->monitor_fd < 0) {
+		errsv = errno;
+		_LOGW ("could not monitor PPP stats: %s", nm_strerror_native (errsv));
+		return;
+	}
+
+	g_warn_if_fail (priv->monitor_id == 0);
+	if (priv->monitor_id)
+		g_source_remove (priv->monitor_id);
+	priv->monitor_id = g_timeout_add_seconds (5, monitor_cb, self);
 }
 
 /*****************************************************************************/
@@ -360,7 +367,7 @@ impl_ppp_manager_need_secrets (NMDBusObject *obj,
 	const char *username = NULL;
 	const char *password = NULL;
 	guint32 tries;
-	GPtrArray *hints = NULL;
+	gs_unref_ptrarray GPtrArray *hints = NULL;
 	GError *error = NULL;
 	NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
 
@@ -390,18 +397,18 @@ impl_ppp_manager_need_secrets (NMDBusObject *obj,
 	if (tries > 1)
 		flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
 
+	if (hints)
+		g_ptr_array_add (hints, NULL);
+
 	priv->secrets_id = nm_act_request_get_secrets (priv->act_req,
 	                                               FALSE,
 	                                               priv->secrets_setting_name,
 	                                               flags,
-	                                               hints ? g_ptr_array_index (hints, 0) : NULL,
+	                                               hints ? (const char *const*) hints->pdata : NULL,
 	                                               ppp_secrets_cb,
 	                                               self);
 	g_object_set_qdata (G_OBJECT (applied_connection), ppp_manager_secret_tries_quark (), GUINT_TO_POINTER (++tries));
 	priv->pending_secrets_context = invocation;
-
-	if (hints)
-		g_ptr_array_free (hints, TRUE);
 }
 
 static void
@@ -672,61 +679,6 @@ out:
 
 /*****************************************************************************/
 
-typedef struct {
-	GPtrArray *array;
-	GStringChunk *chunk;
-} NMCmdLine;
-
-static NMCmdLine *
-nm_cmd_line_new (void)
-{
-	NMCmdLine *cmd;
-
-	cmd = g_slice_new (NMCmdLine);
-	cmd->array = g_ptr_array_new ();
-	cmd->chunk = g_string_chunk_new (1024);
-
-	return cmd;
-}
-
-static void
-nm_cmd_line_destroy (NMCmdLine *cmd)
-{
-	g_ptr_array_free (cmd->array, TRUE);
-	g_string_chunk_free (cmd->chunk);
-	g_slice_free (NMCmdLine, cmd);
-}
-
-static char *
-nm_cmd_line_to_str (NMCmdLine *cmd)
-{
-	char *str;
-
-	g_ptr_array_add (cmd->array, NULL);
-	str = g_strjoinv (" ", (char **) cmd->array->pdata);
-	g_ptr_array_remove_index (cmd->array, cmd->array->len - 1);
-
-	return str;
-}
-
-static void
-nm_cmd_line_add_string (NMCmdLine *cmd, const char *str)
-{
-	g_ptr_array_add (cmd->array, g_string_chunk_insert (cmd->chunk, str));
-}
-
-static void
-nm_cmd_line_add_int (NMCmdLine *cmd, int i)
-{
-	char *str;
-
-	str = g_strdup_printf ("%d", i);
-	nm_cmd_line_add_string (cmd, str);
-	g_free (str);
-}
-
-/*****************************************************************************/
-
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (pppd_exit_code_to_str, int,
 	NM_UTILS_LOOKUP_DEFAULT ("Unknown error"),
 	NM_UTILS_LOOKUP_STR_ITEM ( 1, "Fatal pppd error");
@@ -791,14 +743,14 @@ pppd_timed_out (gpointer data)
 	NMPPPManager *self = NM_PPP_MANAGER (data);
 
 	_LOGW ("pppd timed out or didn't initialize our dbus module");
-	_ppp_manager_stop (self, NULL, NULL);
+	_ppp_manager_stop (self, NULL, NULL, NULL);
 
 	g_signal_emit (self, signals[STATE_CHANGED], 0, (guint) NM_PPP_STATUS_DEAD);
 
 	return FALSE;
 }
 
-static NMCmdLine *
+static GPtrArray *
 create_pppd_cmd_line (NMPPPManager *self,
                       NMSettingPpp *setting,
                       NMSettingPppoe *pppoe,
@@ -811,9 +763,8 @@ create_pppd_cmd_line (NMPPPManager *self,
 {
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (self);
 	const char *pppd_binary = NULL;
-	NMCmdLine *cmd;
+	gs_unref_ptrarray GPtrArray *cmd = NULL;
 	gboolean ppp_debug;
-	static int unit;
 
 	g_return_val_if_fail (setting != NULL, NULL);
 
@@ -833,53 +784,50 @@ create_pppd_cmd_line (NMPPPManager *self,
 		return NULL;
 	}
 
-	/* Create pppd command line */
-	cmd = nm_cmd_line_new ();
-	nm_cmd_line_add_string (cmd, pppd_binary);
+	cmd = g_ptr_array_new_with_free_func (g_free);
+
+	nm_strv_ptrarray_add_string_dup (cmd, pppd_binary);
 
-	nm_cmd_line_add_string (cmd, "nodetach");
-	nm_cmd_line_add_string (cmd, "lock");
+	nm_strv_ptrarray_add_string_dup (cmd, "nodetach");
+	nm_strv_ptrarray_add_string_dup (cmd, "lock");
 
 	/* NM handles setting the default route */
-	nm_cmd_line_add_string (cmd, "nodefaultroute");
+	nm_strv_ptrarray_add_string_dup (cmd, "nodefaultroute");
 
 	if (!ip4_enabled)
-		nm_cmd_line_add_string (cmd, "noip");
+		nm_strv_ptrarray_add_string_dup (cmd, "noip");
 
 	if (ip6_enabled) {
 		/* Allow IPv6 to be configured by IPV6CP */
-		nm_cmd_line_add_string (cmd, "ipv6");
-		nm_cmd_line_add_string (cmd, ",");
+		nm_strv_ptrarray_add_string_dup (cmd, "ipv6");
+		nm_strv_ptrarray_add_string_dup (cmd, ",");
 	} else
-		nm_cmd_line_add_string (cmd, "noipv6");
+		nm_strv_ptrarray_add_string_dup (cmd, "noipv6");
 
 	ppp_debug = !!getenv ("NM_PPP_DEBUG");
 	if (nm_logging_enabled (LOGL_DEBUG, LOGD_PPP))
 		ppp_debug = TRUE;
 
 	if (ppp_debug)
-		nm_cmd_line_add_string (cmd, "debug");
+		nm_strv_ptrarray_add_string_dup (cmd, "debug");
 
 	if (ppp_name) {
-		nm_cmd_line_add_string (cmd, "user");
-		nm_cmd_line_add_string (cmd, ppp_name);
+		nm_strv_ptrarray_add_string_dup (cmd, "user");
+		nm_strv_ptrarray_add_string_dup (cmd, ppp_name);
 	}
 
 	if (pppoe) {
-		char *dev_str;
 		const char *pppoe_service;
 
-		nm_cmd_line_add_string (cmd, "plugin");
-		nm_cmd_line_add_string (cmd, "rp-pppoe.so");
+		nm_strv_ptrarray_add_string_dup (cmd, "plugin");
+		nm_strv_ptrarray_add_string_dup (cmd, "rp-pppoe.so");
 
-		dev_str = g_strdup_printf ("nic-%s", priv->parent_iface);
-		nm_cmd_line_add_string (cmd, dev_str);
-		g_free (dev_str);
+		nm_strv_ptrarray_add_string_concat (cmd, "nic-", priv->parent_iface);
 
 		pppoe_service = nm_setting_pppoe_get_service (pppoe);
 		if (pppoe_service) {
-			nm_cmd_line_add_string (cmd, "rp_pppoe_service");
-			nm_cmd_line_add_string (cmd, pppoe_service);
+			nm_strv_ptrarray_add_string_dup (cmd, "rp_pppoe_service");
+			nm_strv_ptrarray_add_string_dup (cmd, pppoe_service);
 		}
 	} else if (adsl) {
 		const char *protocol = nm_setting_adsl_get_protocol (adsl);
@@ -888,110 +836,110 @@ create_pppd_cmd_line (NMPPPManager *self,
 			guint32 vpi = nm_setting_adsl_get_vpi (adsl);
 			guint32 vci = nm_setting_adsl_get_vci (adsl);
 			const char *encaps = nm_setting_adsl_get_encapsulation (adsl);
-			char *vpivci;
 
-			nm_cmd_line_add_string (cmd, "plugin");
-			nm_cmd_line_add_string (cmd, "pppoatm.so");
+			nm_strv_ptrarray_add_string_dup (cmd, "plugin");
+			nm_strv_ptrarray_add_string_dup (cmd, "pppoatm.so");
 
-			vpivci = g_strdup_printf("%d.%d", vpi, vci);
-			nm_cmd_line_add_string (cmd, vpivci);
-			g_free (vpivci);
+			nm_strv_ptrarray_add_string_printf (cmd, "%d.%d", vpi, vci);
 
 			if (g_strcmp0 (encaps, NM_SETTING_ADSL_ENCAPSULATION_LLC) == 0)
-				nm_cmd_line_add_string (cmd, "llc-encaps");
+				nm_strv_ptrarray_add_string_dup (cmd, "llc-encaps");
 			else /*if (g_strcmp0 (encaps, NM_SETTING_ADSL_ENCAPSULATION_VCMUX) == 0)*/
-				nm_cmd_line_add_string (cmd, "vc-encaps");
+				nm_strv_ptrarray_add_string_dup (cmd, "vc-encaps");
 
 		} else if (!strcmp (protocol, NM_SETTING_ADSL_PROTOCOL_PPPOE)) {
-			nm_cmd_line_add_string (cmd, "plugin");
-			nm_cmd_line_add_string (cmd, "rp-pppoe.so");
-			nm_cmd_line_add_string (cmd, priv->parent_iface);
+			nm_strv_ptrarray_add_string_dup (cmd, "plugin");
+			nm_strv_ptrarray_add_string_dup (cmd, "rp-pppoe.so");
+			nm_strv_ptrarray_add_string_dup (cmd, priv->parent_iface);
 		}
 
-		nm_cmd_line_add_string (cmd, "noipdefault");
+		nm_strv_ptrarray_add_string_dup (cmd, "noipdefault");
 	} else {
-		nm_cmd_line_add_string (cmd, priv->parent_iface);
+		nm_strv_ptrarray_add_string_dup (cmd, priv->parent_iface);
 		/* Don't send some random address as the local address */
-		nm_cmd_line_add_string (cmd, "noipdefault");
+		nm_strv_ptrarray_add_string_dup (cmd, "noipdefault");
 	}
 
 	if (nm_setting_ppp_get_baud (setting))
-		nm_cmd_line_add_int (cmd, nm_setting_ppp_get_baud (setting));
+		nm_strv_ptrarray_add_int (cmd, nm_setting_ppp_get_baud (setting));
 	else if (baud_override)
-		nm_cmd_line_add_int (cmd, (int) baud_override);
+		nm_strv_ptrarray_add_int (cmd, baud_override);
 
 	/* noauth by default, because we certainly don't have any information
 	 * with which to verify anything the peer gives us if we ask it to
 	 * authenticate itself, which is what 'auth' really means.
 	 */
-	nm_cmd_line_add_string (cmd, "noauth");
+	nm_strv_ptrarray_add_string_dup (cmd, "noauth");
 
 	if (nm_setting_ppp_get_refuse_eap (setting))
-		nm_cmd_line_add_string (cmd, "refuse-eap");
+		nm_strv_ptrarray_add_string_dup (cmd, "refuse-eap");
 	if (nm_setting_ppp_get_refuse_pap (setting))
-		nm_cmd_line_add_string (cmd, "refuse-pap");
+		nm_strv_ptrarray_add_string_dup (cmd, "refuse-pap");
 	if (nm_setting_ppp_get_refuse_chap (setting))
-		nm_cmd_line_add_string (cmd, "refuse-chap");
+		nm_strv_ptrarray_add_string_dup (cmd, "refuse-chap");
 	if (nm_setting_ppp_get_refuse_mschap (setting))
-		nm_cmd_line_add_string (cmd, "refuse-mschap");
+		nm_strv_ptrarray_add_string_dup (cmd, "refuse-mschap");
 	if (nm_setting_ppp_get_refuse_mschapv2 (setting))
-		nm_cmd_line_add_string (cmd, "refuse-mschap-v2");
+		nm_strv_ptrarray_add_string_dup (cmd, "refuse-mschap-v2");
 	if (nm_setting_ppp_get_nobsdcomp (setting))
-		nm_cmd_line_add_string (cmd, "nobsdcomp");
+		nm_strv_ptrarray_add_string_dup (cmd, "nobsdcomp");
 	if (nm_setting_ppp_get_no_vj_comp (setting))
-		nm_cmd_line_add_string (cmd, "novj");
+		nm_strv_ptrarray_add_string_dup (cmd, "novj");
 	if (nm_setting_ppp_get_nodeflate (setting))
-		nm_cmd_line_add_string (cmd, "nodeflate");
+		nm_strv_ptrarray_add_string_dup (cmd, "nodeflate");
 	if (nm_setting_ppp_get_require_mppe (setting))
-		nm_cmd_line_add_string (cmd, "require-mppe");
+		nm_strv_ptrarray_add_string_dup (cmd, "require-mppe");
 	if (nm_setting_ppp_get_require_mppe_128 (setting))
-		nm_cmd_line_add_string (cmd, "require-mppe-128");
+		nm_strv_ptrarray_add_string_dup (cmd, "require-mppe-128");
 	if (nm_setting_ppp_get_mppe_stateful (setting))
-		nm_cmd_line_add_string (cmd, "mppe-stateful");
+		nm_strv_ptrarray_add_string_dup (cmd, "mppe-stateful");
 	if (nm_setting_ppp_get_crtscts (setting))
-		nm_cmd_line_add_string (cmd, "crtscts");
+		nm_strv_ptrarray_add_string_dup (cmd, "crtscts");
 
 	/* Always ask for DNS, we don't have to use them if the connection
 	 * overrides the returned servers.
 	 */
-	nm_cmd_line_add_string (cmd, "usepeerdns");
+	nm_strv_ptrarray_add_string_dup (cmd, "usepeerdns");
 
 	if (nm_setting_ppp_get_mru (setting)) {
-		nm_cmd_line_add_string (cmd, "mru");
-		nm_cmd_line_add_int (cmd, nm_setting_ppp_get_mru (setting));
+		nm_strv_ptrarray_add_string_dup (cmd, "mru");
+		nm_strv_ptrarray_add_int (cmd, nm_setting_ppp_get_mru (setting));
 	}
 
 	if (nm_setting_ppp_get_mtu (setting)) {
-		nm_cmd_line_add_string (cmd, "mtu");
-		nm_cmd_line_add_int (cmd, nm_setting_ppp_get_mtu (setting));
+		nm_strv_ptrarray_add_string_dup (cmd, "mtu");
+		nm_strv_ptrarray_add_int (cmd, nm_setting_ppp_get_mtu (setting));
 	}
 
-	nm_cmd_line_add_string (cmd, "lcp-echo-failure");
-	nm_cmd_line_add_int (cmd, nm_setting_ppp_get_lcp_echo_failure (setting));
+	nm_strv_ptrarray_add_string_dup (cmd, "lcp-echo-failure");
+	nm_strv_ptrarray_add_int (cmd, nm_setting_ppp_get_lcp_echo_failure (setting));
 
-	nm_cmd_line_add_string (cmd, "lcp-echo-interval");
-	nm_cmd_line_add_int (cmd, nm_setting_ppp_get_lcp_echo_interval (setting));
+	nm_strv_ptrarray_add_string_dup (cmd, "lcp-echo-interval");
+	nm_strv_ptrarray_add_int (cmd, nm_setting_ppp_get_lcp_echo_interval (setting));
 
 	/* Avoid pppd to exit if no traffic going through */
-	nm_cmd_line_add_string (cmd, "idle");
-	nm_cmd_line_add_int (cmd, 0);
+	nm_strv_ptrarray_add_string_dup (cmd, "idle");
+	nm_strv_ptrarray_add_string_dup (cmd, "0");
 
-	nm_cmd_line_add_string (cmd, "ipparam");
-	nm_cmd_line_add_string (cmd, nm_dbus_object_get_path (NM_DBUS_OBJECT (self)));
+	nm_strv_ptrarray_add_string_dup (cmd, "ipparam");
+	nm_strv_ptrarray_add_string_dup (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);
+	nm_strv_ptrarray_add_string_dup (cmd, "plugin");
+	nm_strv_ptrarray_add_string_dup (cmd, NM_PPPD_PLUGIN);
 
 	if (pppoe && nm_setting_pppoe_get_parent (pppoe)) {
+		static int unit;
+
 		/* The PPP interface is going to be renamed, so pass a
 		 * different unit each time so that activations don't
 		 * race with each others. */
-		nm_cmd_line_add_string (cmd, "unit");
-		nm_cmd_line_add_int (cmd, unit);
+		nm_strv_ptrarray_add_string_dup (cmd, "unit");
+		nm_strv_ptrarray_add_int (cmd, unit);
 		unit = unit < G_MAXINT ? unit + 1 : 0;
 	}
 
-	return cmd;
+	g_ptr_array_add (cmd, NULL);
+	return g_steal_pointer (&cmd);
 }
 
 static void
@@ -1035,8 +983,8 @@ _ppp_manager_start (NMPPPManager *self,
 	gs_unref_object NMSettingPpp *s_ppp_free = NULL;
 	NMSettingPppoe *pppoe_setting;
 	NMSettingAdsl *adsl_setting;
-	NMCmdLine *ppp_cmd;
-	char *cmd_str;
+	gs_unref_ptrarray GPtrArray *ppp_cmd = NULL;
+	gs_free char *cmd_str = NULL;
 	struct stat st;
 	const char *ip6_method, *ip4_method;
 	gboolean ip6_enabled = FALSE;
@@ -1086,10 +1034,10 @@ _ppp_manager_start (NMPPPManager *self,
 	adsl_setting = (NMSettingAdsl *) nm_connection_get_setting (connection, NM_TYPE_SETTING_ADSL);
 
 	/* Figure out what address methods should be enabled */
-	ip4_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	ip4_enabled = g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0;
-	ip6_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	ip6_enabled = g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0;
+	ip4_method = nm_utils_get_ip_config_method (connection, AF_INET);
+	ip4_enabled = nm_streq (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	ip6_method = nm_utils_get_ip_config_method (connection, AF_INET6);
+	ip6_enabled = nm_streq (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
 
 	ppp_cmd = create_pppd_cmd_line (self,
 	                                s_ppp,
@@ -1101,23 +1049,25 @@ _ppp_manager_start (NMPPPManager *self,
 	                                ip6_enabled,
 	                                err);
 	if (!ppp_cmd)
-		goto out;
-
-	g_ptr_array_add (ppp_cmd->array, NULL);
+		goto fail;
 
 	_LOGI ("starting PPP connection");
 
-	cmd_str = nm_cmd_line_to_str (ppp_cmd);
-	_LOGD ("command line: %s", cmd_str);
-	g_free (cmd_str);
+	_LOGD ("command line: %s",
+	       (cmd_str = g_strjoinv (" ", (char **) ppp_cmd->pdata)));
 
 	priv->pid = 0;
-	if (!g_spawn_async (NULL, (char **) ppp_cmd->array->pdata, NULL,
+	if (!g_spawn_async (NULL,
+	                    (char **) ppp_cmd->pdata,
+	                    NULL,
 	                    G_SPAWN_DO_NOT_REAP_CHILD,
-	                    nm_utils_setpgid, NULL,
-	                    &priv->pid, err)) {
-		goto out;
-	}
+	                    nm_utils_setpgid,
+	                    NULL,
+	                    &priv->pid,
+	                    err))
+		goto fail;
+
+	nm_assert (priv->pid > 0);
 
 	_LOGI ("pppd started with pid %lld", (long long) priv->pid);
 
@@ -1125,14 +1075,10 @@ _ppp_manager_start (NMPPPManager *self,
 	priv->ppp_timeout_handler = g_timeout_add_seconds (timeout_secs, pppd_timed_out, self);
 	priv->act_req = g_object_ref (req);
 
-out:
-	if (ppp_cmd)
-		nm_cmd_line_destroy (ppp_cmd);
-
-	if (priv->pid <= 0)
-		nm_dbus_object_unexport (NM_DBUS_OBJECT (self));
-
-	return priv->pid > 0;
+	return TRUE;
+fail:
+	nm_dbus_object_unexport (NM_DBUS_OBJECT (self));
+	return FALSE;
 }
 
 static void
@@ -1170,6 +1116,10 @@ struct _NMPPPManagerStopHandle {
 	 * pppd process terminated. */
 	GObject *shutdown_waitobj;
 
+	GCancellable *cancellable;
+
+	gulong cancellable_id;
+
 	guint idle_id;
 };
 
@@ -1179,6 +1129,13 @@ _stop_handle_complete (NMPPPManagerStopHandle *handle, gboolean was_cancelled)
 	gs_unref_object NMPPPManager *self = NULL;
 	NMPPPManagerStopCallback callback;
 
+	if (handle->cancellable_id) {
+		g_cancellable_disconnect (handle->cancellable,
+		                          nm_steal_int (&handle->cancellable_id));
+	}
+
+	g_clear_object (&handle->cancellable);
+
 	self = g_steal_pointer (&handle->self);
 	if (!self)
 		return;
@@ -1219,8 +1176,20 @@ _stop_idle_cb (gpointer user_data)
 	return G_SOURCE_REMOVE;
 }
 
+static void
+_stop_cancelled_cb (GCancellable *cancellable,
+                    gpointer user_data)
+{
+	NMPPPManagerStopHandle *handle = user_data;
+
+	nm_clear_g_signal_handler (handle->cancellable,
+	                           &handle->cancellable_id);
+	_ppp_manager_stop_cancel (handle);
+}
+
 static NMPPPManagerStopHandle *
 _ppp_manager_stop (NMPPPManager *self,
+                   GCancellable *cancellable,
                    NMPPPManagerStopCallback callback,
                    gpointer user_data)
 {
@@ -1246,6 +1215,13 @@ _ppp_manager_stop (NMPPPManager *self,
 	handle->self = g_object_ref (self);
 	handle->callback = callback;
 	handle->user_data = user_data;
+	if (cancellable) {
+		handle->cancellable = g_object_ref (cancellable);
+		handle->cancellable_id = g_cancellable_connect (cancellable,
+		                                                G_CALLBACK (_stop_cancelled_cb),
+		                                                handle,
+		                                                NULL);
+	}
 
 	if (!priv->pid) {
 		/* No PID. There is nothing to kill, however, invoke the callback in
@@ -1272,6 +1248,8 @@ _ppp_manager_stop (NMPPPManager *self,
 	return handle;
 }
 
+/*****************************************************************************/
+
 static void
 _ppp_manager_stop_cancel (NMPPPManagerStopHandle *handle)
 {
@@ -1360,7 +1338,7 @@ dispose (GObject *object)
 	 * still stop. */
 	g_warn_if_fail (!priv->pid);
 	g_warn_if_fail (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (self)));
-	_ppp_manager_stop (self, NULL, NULL);
+	_ppp_manager_stop (self, NULL, NULL, NULL);
 
 	g_clear_object (&priv->act_req);
 
diff --git a/src/ppp/nm-ppp-plugin-api.h b/src/ppp/nm-ppp-plugin-api.h
index 558de2c2..95ddd211 100644
--- a/src/ppp/nm-ppp-plugin-api.h
+++ b/src/ppp/nm-ppp-plugin-api.h
@@ -40,6 +40,7 @@ typedef const struct {
 	                        GError **err);
 
 	NMPPPManagerStopHandle *(*stop) (NMPPPManager *manager,
+	                                 GCancellable *cancellable,
 	                                 NMPPPManagerStopCallback callback,
 	                                 gpointer user_data);
 
diff --git a/src/ppp/nm-pppd-plugin.c b/src/ppp/nm-pppd-plugin.c
index 09196340..a8d6749a 100644
--- a/src/ppp/nm-pppd-plugin.c
+++ b/src/ppp/nm-pppd-plugin.c
@@ -22,7 +22,6 @@
 #include <config.h>
 #define ___CONFIG_H__
 
-#include <string.h>
 #include <pppd/pppd.h>
 #include <pppd/fsm.h>
 #include <pppd/ipcp.h>
@@ -132,7 +131,7 @@ nm_phasechange (void *data, int arg)
 		                   NULL, NULL);
 	}
 
-	if (ppp_status == PHASE_RUNNING) {
+	if (ppp_status == NM_PPP_STATUS_RUNNING) {
 		index = if_nametoindex (ifname);
 		/* Make a sync call to ensure that when the call
 		 * terminates the interface already has its final
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index 953d3ce4..8924c39f 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -22,7 +22,6 @@
 
 #include "nm-agent-manager.h"
 
-#include <string.h>
 #include <pwd.h>
 
 #include "nm-common-macros.h"
@@ -1059,49 +1058,6 @@ _con_get_request_start_validated (NMAuthChain *chain,
 }
 
 static void
-has_system_secrets_check (NMSetting *setting,
-                          const char *key,
-                          const GValue *value,
-                          GParamFlags flags,
-                          gpointer user_data)
-{
-	NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-	gboolean *has_system = user_data;
-
-	if (!(flags & NM_SETTING_PARAM_SECRET))
-		return;
-
-	/* Clear out system-owned or always-ask secrets */
-	if (NM_IS_SETTING_VPN (setting) && !strcmp (key, NM_SETTING_VPN_SECRETS)) {
-		GHashTableIter iter;
-		const char *secret_name = NULL;
-
-		/* VPNs are special; need to handle each secret separately */
-		g_hash_table_iter_init (&iter, (GHashTable *) g_value_get_boxed (value));
-		while (g_hash_table_iter_next (&iter, (gpointer *) &secret_name, NULL)) {
-			secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-			nm_setting_get_secret_flags (setting, secret_name, &secret_flags, NULL);
-			if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
-				*has_system = TRUE;
-		}
-	} else {
-		if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL))
-			g_return_if_reached ();
-		if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
-			*has_system = TRUE;
-	}
-}
-
-static gboolean
-has_system_secrets (NMConnection *connection)
-{
-	gboolean has_system = FALSE;
-
-	nm_connection_for_each_setting_value (connection, has_system_secrets_check, &has_system);
-	return has_system;
-}
-
-static void
 _con_get_request_start (Request *req)
 {
 	NMAgentManager *self;
@@ -1121,7 +1077,8 @@ _con_get_request_start (Request *req)
 	 * unprivileged users.
 	 */
 	if (   (req->con.get.flags != NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE)
-	    && (req->con.get.existing_secrets || has_system_secrets (req->con.connection))) {
+	    && (   req->con.get.existing_secrets
+	        || _nm_connection_aggregate (req->con.connection, NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, NULL))) {
 		_LOGD (NULL, "("LOG_REQ_FMT") request has system secrets; checking agent %s for MODIFY",
 		       LOG_REQ_ARG (req), agent_dbus_owner);
 
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 6f47e14d..8d1f9583 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -23,8 +23,6 @@
 
 #include "nm-settings-connection.h"
 
-#include <string.h>
-
 #include "c-list/src/c-list.h"
 
 #include "nm-common-macros.h"
@@ -208,141 +206,6 @@ nm_settings_connection_get_last_secret_agent_version_id (NMSettingsConnection *s
 
 /*****************************************************************************/
 
-/* Return TRUE to keep, FALSE to drop */
-typedef gboolean (*ForEachSecretFunc) (NMSettingSecretFlags flags,
-                                       gpointer user_data);
-
-/* Returns always a non-NULL, non-floating variant that must
- * be unrefed by the caller. */
-static GVariant *
-for_each_secret (NMConnection *self,
-                 GVariant *secrets,
-                 gboolean remove_non_secrets,
-                 ForEachSecretFunc callback,
-                 gpointer callback_data)
-{
-	GVariantBuilder secrets_builder, setting_builder;
-	GVariantIter secrets_iter, *setting_iter;
-	const char *setting_name;
-
-	/* This function, given a dict of dicts representing new secrets of
-	 * an NMConnection, walks through each toplevel dict (which represents a
-	 * NMSetting), and for each setting, walks through that setting dict's
-	 * properties.  For each property that's a secret, it will check that
-	 * secret's flags in the backing NMConnection object, and call a supplied
-	 * callback.
-	 *
-	 * The one complexity is that the VPN setting's 'secrets' property is
-	 * *also* a dict (since the key/value pairs are arbitrary and known
-	 * only to the VPN plugin itself).  That means we have three levels of
-	 * dicts that we potentially have to traverse here.  When we hit the
-	 * VPN setting's 'secrets' property, we special-case that and iterate over
-	 * each item in that 'secrets' dict, calling the supplied callback
-	 * each time.
-	 */
-
-	g_return_val_if_fail (callback, NULL);
-
-	g_variant_iter_init (&secrets_iter, secrets);
-	g_variant_builder_init (&secrets_builder, NM_VARIANT_TYPE_CONNECTION);
-	while (g_variant_iter_next (&secrets_iter, "{&sa{sv}}", &setting_name, &setting_iter)) {
-		NMSetting *setting;
-		const char *secret_name;
-		GVariant *val;
-
-		setting = nm_connection_get_setting_by_name (self, setting_name);
-		if (setting == NULL) {
-			g_variant_iter_free (setting_iter);
-			continue;
-		}
-
-		g_variant_builder_init (&setting_builder, NM_VARIANT_TYPE_SETTING);
-		while (g_variant_iter_next (setting_iter, "{&sv}", &secret_name, &val)) {
-			NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-
-			/* VPN secrets need slightly different treatment here since the
-			 * "secrets" property is actually a hash table of secrets.
-			 */
-			if (NM_IS_SETTING_VPN (setting) && !g_strcmp0 (secret_name, NM_SETTING_VPN_SECRETS)) {
-				GVariantBuilder vpn_secrets_builder;
-				GVariantIter vpn_secrets_iter;
-				const char *vpn_secret_name, *secret;
-
-				/* Iterate through each secret from the VPN dict in the overall secrets dict */
-				g_variant_builder_init (&vpn_secrets_builder, G_VARIANT_TYPE ("a{ss}"));
-				g_variant_iter_init (&vpn_secrets_iter, val);
-				while (g_variant_iter_next (&vpn_secrets_iter, "{&s&s}", &vpn_secret_name, &secret)) {
-					if (!nm_setting_get_secret_flags (setting, vpn_secret_name, &secret_flags, NULL)) {
-						if (!remove_non_secrets)
-							g_variant_builder_add (&vpn_secrets_builder, "{ss}", vpn_secret_name, secret);
-						continue;
-					}
-
-					if (callback (secret_flags, callback_data))
-						g_variant_builder_add (&vpn_secrets_builder, "{ss}", vpn_secret_name, secret);
-				}
-
-				g_variant_builder_add (&setting_builder, "{sv}",
-				                       secret_name, g_variant_builder_end (&vpn_secrets_builder));
-			} else {
-				if (!nm_setting_get_secret_flags (setting, secret_name, &secret_flags, NULL)) {
-					if (!remove_non_secrets)
-						g_variant_builder_add (&setting_builder, "{sv}", secret_name, val);
-					continue;
-				}
-				if (callback (secret_flags, callback_data))
-					g_variant_builder_add (&setting_builder, "{sv}", secret_name, val);
-			}
-			g_variant_unref (val);
-		}
-
-		g_variant_iter_free (setting_iter);
-		g_variant_builder_add (&secrets_builder, "{sa{sv}}", setting_name, &setting_builder);
-	}
-
-	return g_variant_ref_sink (g_variant_builder_end (&secrets_builder));
-}
-
-typedef gboolean (*FindSecretFunc) (NMSettingSecretFlags flags,
-                                    gpointer user_data);
-
-typedef struct {
-	FindSecretFunc find_func;
-	gpointer find_func_data;
-	gboolean found;
-} FindSecretData;
-
-static gboolean
-find_secret_for_each_func (NMSettingSecretFlags flags,
-                           gpointer user_data)
-{
-	FindSecretData *data = user_data;
-
-	if (!data->found)
-		data->found = data->find_func (flags, data->find_func_data);
-	return FALSE;
-}
-
-static gboolean
-find_secret (NMConnection *self,
-             GVariant *secrets,
-             FindSecretFunc callback,
-             gpointer callback_data)
-{
-	FindSecretData data;
-	GVariant *dummy;
-
-	data.find_func = callback;
-	data.find_func_data = callback_data;
-	data.found = FALSE;
-
-	dummy = for_each_secret (self, secrets, FALSE, find_secret_for_each_func, &data);
-	g_variant_unref (dummy);
-	return data.found;
-}
-
-/*****************************************************************************/
-
 static void
 set_visible (NMSettingsConnection *self, gboolean new_visible)
 {
@@ -790,7 +653,7 @@ out:
 		else if (new_connection)
 			_LOGI ("write: successfully updated (%s)", logmsg_change);
 		else
-			_LOGI ("write: successfully commited (%s)", logmsg_change);
+			_LOGI ("write: successfully committed (%s)", logmsg_change);
 	}
 	return TRUE;
 }
@@ -938,8 +801,8 @@ typedef struct {
 } ForEachSecretFlags;
 
 static gboolean
-validate_secret_flags (NMSettingSecretFlags flags,
-                       gpointer user_data)
+validate_secret_flags_cb (NMSettingSecretFlags flags,
+                          gpointer user_data)
 {
 	ForEachSecretFlags *cmp_flags = user_data;
 
@@ -950,6 +813,18 @@ validate_secret_flags (NMSettingSecretFlags flags,
 	return TRUE;
 }
 
+static GVariant *
+validate_secret_flags (NMConnection *connection,
+                       GVariant *secrets,
+                       ForEachSecretFlags *cmp_flags)
+{
+	return g_variant_ref_sink (_nm_connection_for_each_secret (connection,
+	                                                           secrets,
+	                                                           TRUE,
+	                                                           validate_secret_flags_cb,
+	                                                           cmp_flags));
+}
+
 static gboolean
 secret_is_system_owned (NMSettingSecretFlags flags,
                         gpointer user_data)
@@ -992,7 +867,7 @@ get_cmp_flags (NMSettingsConnection *self, /* only needed for logging */
 		 * save those system-owned secrets.  If not, discard them and use the
 		 * existing secrets, or fail the connection.
 		 */
-		*agent_had_system = find_secret (connection, secrets, secret_is_system_owned, NULL);
+		*agent_had_system = _nm_connection_find_secret (connection, secrets, secret_is_system_owned, NULL);
 		if (*agent_had_system) {
 			if (flags == NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE) {
 				/* No user interaction was allowed when requesting secrets; the
@@ -1151,14 +1026,14 @@ get_secrets_done_cb (NMAgentManager *manager,
 	/* Update the connection with our existing secrets from backing storage */
 	nm_connection_clear_secrets (nm_settings_connection_get_connection (self));
 	if (!dict || nm_connection_update_secrets (nm_settings_connection_get_connection (self), setting_name, dict, &local)) {
-		GVariant *filtered_secrets;
+		gs_unref_variant GVariant *filtered_secrets = NULL;
 
 		/* Update the connection with the agent's secrets; by this point if any
 		 * system-owned secrets exist in 'secrets' the agent that provided them
 		 * will have been authenticated, so those secrets can replace the existing
 		 * system secrets.
 		 */
-		filtered_secrets = for_each_secret (nm_settings_connection_get_connection (self), secrets, TRUE, validate_secret_flags, &cmp_flags);
+		filtered_secrets = validate_secret_flags (nm_settings_connection_get_connection (self), secrets, &cmp_flags);
 		if (nm_connection_update_secrets (nm_settings_connection_get_connection (self), setting_name, filtered_secrets, &local)) {
 			/* Now that all secrets are updated, copy and cache new secrets,
 			 * then save them to backing storage.
@@ -1194,7 +1069,6 @@ get_secrets_done_cb (NMAgentManager *manager,
 			       call_id,
 			       local->message);
 		}
-		g_variant_unref (filtered_secrets);
 	} else {
 		_LOGD ("(%s:%p) failed to update with existing secrets: %s",
 		       setting_name,
@@ -1218,11 +1092,10 @@ get_secrets_done_cb (NMAgentManager *manager,
 		nm_connection_clear_secrets (applied_connection);
 
 		if (!dict || nm_connection_update_secrets (applied_connection, setting_name, dict, NULL)) {
-			GVariant *filtered_secrets;
+			gs_unref_variant GVariant *filtered_secrets = NULL;
 
-			filtered_secrets = for_each_secret (applied_connection, secrets, TRUE, validate_secret_flags, &cmp_flags);
+			filtered_secrets = validate_secret_flags (applied_connection, secrets, &cmp_flags);
 			nm_connection_update_secrets (applied_connection, setting_name, filtered_secrets, NULL);
-			g_variant_unref (filtered_secrets);
 		}
 	}
 
@@ -1338,7 +1211,7 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
 	/* we remember the current version-id of the secret-agents. The version-id is strictly increasing,
 	 * as new agents register the number. We know hence, that this request was made against a certain
 	 * set of secret-agents.
-	 * If after making this request a new secret-agent registeres, the version-id increases.
+	 * If after making this request a new secret-agent registers, the version-id increases.
 	 * Then we know that the this request probably did not yet include the latest secret-agent. */
 	priv->last_secret_agent_version_id = nm_agent_manager_get_agent_version_id (priv->agent_mgr);
 
@@ -1660,38 +1533,6 @@ typedef struct {
 } UpdateInfo;
 
 static void
-has_some_secrets_cb (NMSetting *setting,
-                     const char *key,
-                     const GValue *value,
-                     GParamFlags flags,
-                     gpointer user_data)
-{
-	GParamSpec *pspec;
-
-	if (NM_IS_SETTING_VPN (setting)) {
-		if (nm_setting_vpn_get_num_secrets (NM_SETTING_VPN(setting)))
-			*((gboolean *) user_data) = TRUE;
-		return;
-	}
-
-	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), key);
-	if (pspec) {
-		if (   (flags & NM_SETTING_PARAM_SECRET)
-		    && !g_param_value_defaults (pspec, (GValue *)value))
-			*((gboolean *) user_data) = TRUE;
-	}
-}
-
-static gboolean
-any_secrets_present (NMConnection *self)
-{
-	gboolean has_secrets = FALSE;
-
-	nm_connection_for_each_setting_value (self, has_some_secrets_cb, &has_secrets);
-	return has_secrets;
-}
-
-static void
 cached_secrets_to_connection (NMSettingsConnection *self, NMConnection *connection)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
@@ -1758,7 +1599,7 @@ update_auth_cb (NMSettingsConnection *self,
 	}
 
 	if (info->new_settings) {
-		if (!any_secrets_present (info->new_settings)) {
+		if (!_nm_connection_aggregate (info->new_settings, NM_CONNECTION_AGGREGATE_ANY_SECRETS, NULL)) {
 			/* If the new connection has no secrets, we do not want to remove all
 			 * secrets, rather we keep all the existing ones. Do that by merging
 			 * them in to the new connection.
@@ -1910,6 +1751,9 @@ settings_connection_update (NMSettingsConnection *self,
 			                                           &error);
 			if (!tmp)
 				goto error;
+
+			if (!nm_connection_verify_secrets (tmp, &error))
+				goto error;
 		}
 	}
 
@@ -2809,7 +2653,7 @@ _autoconnect_retries_set (NMSettingsConnection *self,
 		/* 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). */
+		 * the next-timeout every time a connection gets blocked). */
 		priv->autoconnect_retries_blocked_until = nm_utils_get_monotonic_timestamp_s () + AUTOCONNECT_RESET_RETRIES_TIMER;
 	}
 }
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 2253d56a..fd1d316a 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -29,8 +29,6 @@
 
 #include <unistd.h>
 #include <sys/stat.h>
-#include <errno.h>
-#include <string.h>
 #include <gmodule.h>
 #include <pwd.h>
 
@@ -129,6 +127,8 @@ typedef struct {
 
 	NMHostnameManager *hostname_manager;
 
+	NMSettingsConnection *startup_complete_blocked_by;
+
 	guint connections_len;
 
 	bool started:1;
@@ -182,19 +182,23 @@ static void
 check_startup_complete (NMSettings *self)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	NMSettingsConnection *conn;
+	NMSettingsConnection *sett_conn;
 
 	if (priv->startup_complete)
 		return;
 
-	c_list_for_each_entry (conn, &priv->connections_lst_head, _connections_lst) {
-		if (!nm_settings_connection_get_ready (conn))
+	c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) {
+		if (!nm_settings_connection_get_ready (sett_conn)) {
+			nm_g_object_ref_set (&priv->startup_complete_blocked_by, sett_conn);
 			return;
+		}
 	}
 
+	g_clear_object (&priv->startup_complete_blocked_by);
+
 	/* the connection_ready_changed signal handler is no longer needed. */
-	c_list_for_each_entry (conn, &priv->connections_lst_head, _connections_lst)
-		g_signal_handlers_disconnect_by_func (conn, G_CALLBACK (connection_ready_changed), self);
+	c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst)
+		g_signal_handlers_disconnect_by_func (sett_conn, G_CALLBACK (connection_ready_changed), self);
 
 	priv->startup_complete = TRUE;
 	_notify (self, PROP_STARTUP_COMPLETE);
@@ -369,10 +373,10 @@ _clear_connections_cached_list (NMSettingsPrivate *priv)
 /**
  * nm_settings_get_connections:
  * @self: the #NMSettings
- * @out_len: (out): (allow-none): returns the number of returned
+ * @out_len: (out) (allow-none): returns the number of returned
  *   connections.
  *
- * Returns: (transfer-none): a list of NMSettingsConnections. The list is
+ * Returns: (transfer none): a list of NMSettingsConnections. The list is
  * unsorted and NULL terminated. The result is never %NULL, in case of no
  * connections, it returns an empty list.
  * The returned list is cached internally, only valid until the next
@@ -632,7 +636,7 @@ add_plugin_load_file (NMSettings *self, const char *pname, GError **error)
 
 	if (stat (path, &st) != 0) {
 		errsv = errno;
-		_LOGW ("could not load plugin '%s' from file '%s': %s", pname, path, strerror (errsv));
+		_LOGW ("could not load plugin '%s' from file '%s': %s", pname, path, nm_strerror_native (errsv));
 		return TRUE;
 	}
 	if (!S_ISREG (st.st_mode)) {
@@ -840,10 +844,10 @@ connection_removed (NMSettingsConnection *connection, gpointer user_data)
 	if (priv->connections_loaded)
 		g_signal_emit (self, signals[CONNECTION_REMOVED], 0, connection);
 
-	g_object_unref (connection);
-
 	check_startup_complete (self);
 
+	g_object_unref (connection);
+
 	g_object_unref (self);       /* Balanced by a ref in claim_connection() */
 }
 
@@ -1754,12 +1758,17 @@ nm_settings_device_removed (NMSettings *self, NMDevice *device, gboolean quittin
 
 /*****************************************************************************/
 
-gboolean
-nm_settings_get_startup_complete (NMSettings *self)
+const char *
+nm_settings_get_startup_complete_blocked_reason (NMSettings *self)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	const char *uuid = NULL;
 
-	return priv->startup_complete;
+	if (priv->startup_complete)
+		return NULL;
+	if (priv->startup_complete_blocked_by)
+		uuid = nm_settings_connection_get_uuid (priv->startup_complete_blocked_by);
+	return uuid ?: "unknown";
 }
 
 /*****************************************************************************/
@@ -1845,7 +1854,7 @@ get_property (GObject *object, guint prop_id,
 			g_value_set_boxed (value, NULL);
 		break;
 	case PROP_STARTUP_COMPLETE:
-		g_value_set_boolean (value, nm_settings_get_startup_complete (self));
+		g_value_set_boolean (value, !nm_settings_get_startup_complete_blocked_reason (self));
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1878,6 +1887,8 @@ dispose (GObject *object)
 	NMSettings *self = NM_SETTINGS (object);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 
+	g_clear_object (&priv->startup_complete_blocked_by);
+
 	g_slist_free_full (priv->auths, (GDestroyNotify) nm_auth_chain_destroy);
 	priv->auths = NULL;
 
diff --git a/src/settings/nm-settings.h b/src/settings/nm-settings.h
index 38d8ad4e..eb74c09c 100644
--- a/src/settings/nm-settings.h
+++ b/src/settings/nm-settings.h
@@ -112,6 +112,6 @@ void nm_settings_device_added (NMSettings *self, NMDevice *device);
 
 void nm_settings_device_removed (NMSettings *self, NMDevice *device, gboolean quitting);
 
-gboolean nm_settings_get_startup_complete (NMSettings *self);
+const char *nm_settings_get_startup_complete_blocked_reason (NMSettings *self);
 
 #endif  /* __NM_SETTINGS_H__ */
diff --git a/src/settings/plugins/ibft/meson.build b/src/settings/plugins/ibft/meson.build
index c7dbe459..c33b24cc 100644
--- a/src/settings/plugins/ibft/meson.build
+++ b/src/settings/plugins/ibft/meson.build
@@ -8,7 +8,7 @@ libnms_ibft_core = static_library(
 
 sources = files(
   'nms-ibft-connection.c',
-  'nms-ibft-plugin.c'
+  'nms-ibft-plugin.c',
 )
 
 libnm_settings_plugin_ibft = shared_module(
@@ -19,7 +19,7 @@ libnm_settings_plugin_ibft = shared_module(
   link_args: ldflags_linker_script_settings,
   link_depends: linker_script_settings,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_settings_plugin_ibft
@@ -29,7 +29,7 @@ core_plugins += libnm_settings_plugin_ibft
 run_target(
   'check-local-symbols-settings-ibft',
   command: [check_so_symbols, libnm_settings_plugin_ibft.full_path()],
-  depends: libnm_settings_plugin_ibft
+  depends: libnm_settings_plugin_ibft,
 )
 
 check-local-symbols-settings-ibft: src/settings/plugins/ibft/libnm-settings-plugin-ibft.la
diff --git a/src/settings/plugins/ibft/nms-ibft-connection.c b/src/settings/plugins/ibft/nms-ibft-connection.c
index fb7f18f8..a36d8a31 100644
--- a/src/settings/plugins/ibft/nms-ibft-connection.c
+++ b/src/settings/plugins/ibft/nms-ibft-connection.c
@@ -22,7 +22,6 @@
 
 #include "nms-ibft-connection.h"
 
-#include <string.h>
 #include <net/ethernet.h>
 #include <netinet/ether.h>
 #include <glib/gstdio.h>
diff --git a/src/settings/plugins/ibft/nms-ibft-plugin.c b/src/settings/plugins/ibft/nms-ibft-plugin.c
index 69dd3733..00b25068 100644
--- a/src/settings/plugins/ibft/nms-ibft-plugin.c
+++ b/src/settings/plugins/ibft/nms-ibft-plugin.c
@@ -22,9 +22,7 @@
 
 #include "nms-ibft-plugin.h"
 
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 #include <gmodule.h>
 
 #include "nm-setting-connection.h"
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.c b/src/settings/plugins/ibft/nms-ibft-reader.c
index ac5824a1..c6c14376 100644
--- a/src/settings/plugins/ibft/nms-ibft-reader.c
+++ b/src/settings/plugins/ibft/nms-ibft-reader.c
@@ -23,13 +23,11 @@
 #include "nms-ibft-reader.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <sys/wait.h>
 #include <sys/inotify.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 
diff --git a/src/settings/plugins/ibft/tests/meson.build b/src/settings/plugins/ibft/tests/meson.build
index 8b5e143a..e2f9ca7e 100644
--- a/src/settings/plugins/ibft/tests/meson.build
+++ b/src/settings/plugins/ibft/tests/meson.build
@@ -6,11 +6,11 @@ exe = executable(
   test_unit,
   test_unit + '.c',
   dependencies: test_nm_dep,
-  link_with: libnms_ibft_core
+  link_with: libnms_ibft_core,
 )
 
 test(
   'ibft/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  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 5e46be2e..4c45f574 100644
--- a/src/settings/plugins/ibft/tests/test-ibft.c
+++ b/src/settings/plugins/ibft/tests/test-ibft.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/ether.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
diff --git a/src/settings/plugins/ifcfg-rh/meson.build b/src/settings/plugins/ifcfg-rh/meson.build
index e84ae80c..9024782a 100644
--- a/src/settings/plugins/ifcfg-rh/meson.build
+++ b/src/settings/plugins/ifcfg-rh/meson.build
@@ -1,6 +1,6 @@
 install_data(
   'nm-ifcfg-rh.conf',
-  install_dir: dbus_conf_dir
+  install_dir: dbus_conf_dir,
 )
 
 name = 'nmdbus-ifcfg-rh'
@@ -9,7 +9,7 @@ dbus_sources = gnome.gdbus_codegen(
   name,
   'nm-ifcfg-rh.xml',
   interface_prefix: 'com.redhat',
-  namespace: 'NMDBus'
+  namespace: 'NMDBus',
 )
 
 libnmdbus_ifcfg_rh = static_library(
@@ -23,11 +23,11 @@ core_sources = files(
   'nms-ifcfg-rh-reader.c',
   'nms-ifcfg-rh-utils.c',
   'nms-ifcfg-rh-writer.c',
-  'shvar.c'
+  'shvar.c',
 )
 
 deps = [
-  nm_dep
+  nm_dep,
 ]
 
 libnms_ifcfg_rh_core = static_library(
@@ -46,7 +46,7 @@ libnm_settings_plugin_ifcfg_rh = shared_module(
   link_args: ldflags_linker_script_settings,
   link_depends: linker_script_settings,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_settings_plugin_ifcfg_rh
@@ -56,7 +56,7 @@ core_plugins += libnm_settings_plugin_ifcfg_rh
 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
+  depends: libnm_settings_plugin_ifcfg_rh,
 )
 
 check-local-symbols-settings-ifcfg-rh: src/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la
diff --git a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
index e7a74a1a..04cbb5bc 100644
--- a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
+++ b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
@@ -23,9 +23,7 @@
 #include "nm-inotify-helper.h"
 
 #include <unistd.h>
-#include <string.h>
 #include <sys/inotify.h>
-#include <errno.h>
 
 #include "NetworkManagerUtils.h"
 
@@ -143,12 +141,12 @@ init_inotify (NMInotifyHelper *self)
 {
 	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
 	GIOChannel *channel;
+	int errsv;
 
 	priv->ifd = inotify_init1 (IN_CLOEXEC);
 	if (priv->ifd == -1) {
-		int errsv = errno;
-
-		nm_log_warn (LOGD_SETTINGS, "couldn't initialize inotify: %s (%d)", strerror (errsv), errsv);
+		errsv = errno;
+		nm_log_warn (LOGD_SETTINGS, "couldn't initialize inotify: %s (%d)", nm_strerror_native (errsv), errsv);
 		return FALSE;
 	}
 
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
index ca319ddc..4f769c5f 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
@@ -22,7 +22,6 @@
 
 #include "nms-ifcfg-rh-connection.h"
 
-#include <string.h>
 #include <sys/inotify.h>
 #include <glib/gstdio.h>
 
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
index 6cac8cb6..89272edb 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -25,9 +25,7 @@
 
 #include "nms-ifcfg-rh-plugin.h"
 
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <gmodule.h>
@@ -539,7 +537,7 @@ read_connections (SettingsPluginIfcfg *plugin)
 	 * iterating over the files.
 	 *
 	 * To have sensible, reproducible behavior, sort the paths by last modification
-	 * time prefering older files.
+	 * time preferring older files.
 	 */
 	paths = _paths_from_connections (priv->connections);
 	g_ptr_array_sort_with_data (filenames, (GCompareDataFunc) _sort_paths, paths);
@@ -602,12 +600,9 @@ load_connection (NMSettingsPlugin *config,
 {
 	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (config);
 	NMIfcfgConnection *connection;
-	int dir_len = strlen (IFCFG_DIR);
 	char *ifcfg_path;
 
-	if (   strncmp (filename, IFCFG_DIR, dir_len) != 0
-	    || filename[dir_len] != '/'
-	    || strchr (filename + dir_len + 1, '/') != NULL)
+	if (!nm_utils_file_is_in_path (filename, IFCFG_DIR))
 		return FALSE;
 
 	/* get the real ifcfg-path. This allows us to properly
@@ -987,7 +982,7 @@ config_changed_cb (NMConfig *config,
 	 * 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
+	 * event that the D-Bus connection 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))
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 261dfea2..7c1db225 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -23,13 +23,11 @@
 #include "nms-ifcfg-rh-reader.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <sys/wait.h>
 #include <sys/inotify.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 
@@ -189,7 +187,7 @@ _secret_password_raw_to_bytes (const char *ifcfg_key,
 		password_raw += 2;
 
 	secret = nm_secret_buf_new (strlen (password_raw) / 2 + 3);
-	if (!_nm_utils_hexstr2bin_full (password_raw, FALSE, FALSE, ":", 0, secret->bin, secret->len, &len)) {
+	if (!nm_utils_hexstr2bin_full (password_raw, FALSE, FALSE, ":", 0, secret->bin, secret->len, &len)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Invalid hex password in %s",
 		             ifcfg_key);
@@ -670,7 +668,7 @@ read_full_ip4_address (shvarFile *ifcfg,
 		                       &has_key, &a, error))
 			return FALSE;
 		if (has_key)
-			*out_gateway = g_strdup (nm_utils_inet4_ntop (a, inet_buf));
+			*out_gateway = nm_utils_inet4_ntop_dup (a);
 	}
 
 	/* Prefix */
@@ -801,7 +799,7 @@ enum {
  * @options_route: (in-out): when line is from the OPTIONS setting, this is a pre-created
  *   route object that is completed with the settings from options. Otherwise,
  *   it shall point to %NULL and a new route is created and returned.
- * @out_route: (out): (transfer-full): (allow-none): the parsed %NMIPRoute instance.
+ * @out_route: (out) (transfer-full) (allow-none): the parsed %NMIPRoute instance.
  *   In case a @options_route is passed in, it returns the input route that was modified
  *   in-place. But the caller must unref the returned route in either case.
  * @error: the failure description.
@@ -810,7 +808,7 @@ enum {
  * setting, and one for initscript's handle_ip_file(), which takes the lines
  * and passes them to `ip route add`. The modes are similar, but certain properties
  * are not allowed for OPTIONS.
- * The mode is differenciated by having an @options_route argument.
+ * The mode is differentiated by having an @options_route argument.
  *
  * Returns: returns a negative errno on failure. On success, it returns 0
  *   and @out_route.
@@ -873,7 +871,7 @@ parse_route_line (const char *line,
 	};
 
 	nm_assert (line);
-	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	nm_assert_addr_family (addr_family);
 	nm_assert (!options_route || nm_ip_route_get_family (options_route) == addr_family);
 
 	/* initscripts read the legacy route file line-by-line and
@@ -1018,6 +1016,7 @@ parse_line_type_addr_with_prefix:
 			if (info->type == PARSE_LINE_TYPE_ADDR) {
 				if (!nm_utils_parse_inaddr_bin (addr_family,
 				                                s,
+				                                NULL,
 				                                &info->v.addr.addr)) {
 					if (   info == &infos[PARSE_LINE_ATTR_ROUTE_VIA]
 					    && nm_streq (s, "(null)")) {
@@ -1045,6 +1044,7 @@ parse_line_type_addr_with_prefix:
 					prefix = 0;
 				} else if (!nm_utils_parse_inaddr_prefix_bin (addr_family,
 				                                              s,
+				                                              NULL,
 				                                              &info->v.addr.addr,
 				                                              &prefix)) {
 					g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -1150,7 +1150,7 @@ next:
 			                                                    : ""));
 			break;
 		case PARSE_LINE_TYPE_FLAG:
-			/* NOTE: the flag (for "onlink") only allows to explictly set "TRUE".
+			/* NOTE: the flag (for "onlink") only allows to explicitly set "TRUE".
 			 * There is no way to express an explicit "FALSE" setting
 			 * of this attribute, hence, the file format cannot encode
 			 * that configuration. */
@@ -1425,8 +1425,8 @@ make_user_setting (shvarFile *ifcfg)
 			has_user_data = TRUE;
 	}
 
-	return has_user_data
-	       ? g_steal_pointer (&s_user)
+	return   has_user_data
+	       ? NM_SETTING (g_steal_pointer (&s_user))
 	       : NULL;
 }
 
@@ -1527,7 +1527,6 @@ make_ip4_setting (shvarFile *ifcfg,
 	gboolean never_default;
 	gint64 timeout;
 	int priority;
-	char inet_buf[NM_UTILS_INET_ADDRSTRLEN];
 	const char *const *item;
 	guint32 route_table;
 
@@ -1612,7 +1611,7 @@ make_ip4_setting (shvarFile *ifcfg,
 	              NULL);
 
 	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
-		return g_steal_pointer (&s_ip4);
+		return NM_SETTING (g_steal_pointer (&s_ip4));
 
 	/* Handle DHCP settings */
 	nm_clear_g_free (&value);
@@ -1679,7 +1678,7 @@ make_ip4_setting (shvarFile *ifcfg,
 					PARSE_WARNING ("ignoring GATEWAY (/etc/sysconfig/network) for %s "
 					               "because the connection has no static addresses", f);
 				} else
-					gateway = g_strdup (nm_utils_inet4_ntop (a, inet_buf));
+					gateway = nm_utils_inet4_ntop_dup (a);
 			}
 		}
 	}
@@ -1803,7 +1802,7 @@ make_ip4_setting (shvarFile *ifcfg,
 	}
 	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_DAD_TIMEOUT, (int) timeout, NULL);
 
-	return g_steal_pointer (&s_ip4);
+	return NM_SETTING (g_steal_pointer (&s_ip4));
 }
 
 static void
@@ -2945,7 +2944,7 @@ make_wep_setting (shvarFile *ifcfg,
 		return NULL;
 	}
 
-	return g_steal_pointer (&s_wsec);
+	return NM_SETTING (g_steal_pointer (&s_wsec));
 }
 
 static gboolean
@@ -3160,7 +3159,7 @@ eap_tls_reader (const char *eap_method,
 	/* FIXME: writer does not actually write IEEE_8021X_CLIENT_CERT_PASSWORD and other
 	 * certificate related passwords. It should, because otherwise persisting such profiles
 	 * to ifcfg looses information. As this currently only matters for PKCS11 URIs, it seems
-	 * a seldomly used feature so that it is not fixed yet. */
+	 * a seldom used feature so that it is not fixed yet. */
 	_secret_set_from_ifcfg (s_8021x,
 	                        ifcfg,
 	                        keys_ifcfg,
@@ -3535,7 +3534,7 @@ fill_8021x (shvarFile *ifcfg,
 				goto next;
 
 			/* Some EAP methods don't provide keying material, thus they
-			 * cannot be used with WiFi unless they are an inner method
+			 * cannot be used with Wi-Fi unless they are an inner method
 			 * used with TTLS or PEAP or whatever.
 			 */
 			if (wifi && eap->wifi_phase2_only) {
@@ -3611,7 +3610,7 @@ make_wpa_setting (shvarFile *ifcfg,
 	gs_unref_object NMSettingWirelessSecurity *wsec = NULL;
 	gs_free char *value = NULL;
 	const char *v;
-	gboolean wpa_psk = FALSE, wpa_eap = FALSE, ieee8021x = FALSE;
+	gboolean wpa_psk = FALSE, wpa_sae = FALSE, wpa_eap = FALSE, ieee8021x = FALSE;
 	int i_val;
 	GError *local = NULL;
 
@@ -3619,9 +3618,10 @@ make_wpa_setting (shvarFile *ifcfg,
 
 	v = svGetValueStr (ifcfg, "KEY_MGMT", &value);
 	wpa_psk = nm_streq0 (v, "WPA-PSK");
+	wpa_sae = nm_streq0 (v, "SAE");
 	wpa_eap = nm_streq0 (v, "WPA-EAP");
 	ieee8021x = nm_streq0 (v, "IEEE8021X");
-	if (!wpa_psk && !wpa_eap && !ieee8021x)
+	if (!wpa_psk && !wpa_sae && !wpa_eap && !ieee8021x)
 		return NULL; /* Not WPA or Dynamic WEP */
 
 	/* WPS */
@@ -3635,7 +3635,7 @@ make_wpa_setting (shvarFile *ifcfg,
 	              NULL);
 
 	/* Pairwise and Group ciphers (only relevant for WPA/RSN) */
-	if (wpa_psk || wpa_eap) {
+	if (wpa_psk || wpa_sae || wpa_eap) {
 		fill_wpa_ciphers (ifcfg, wsec, FALSE, adhoc);
 		fill_wpa_ciphers (ifcfg, wsec, TRUE, adhoc);
 	}
@@ -3658,7 +3658,7 @@ make_wpa_setting (shvarFile *ifcfg,
 			nm_setting_wireless_security_add_proto (wsec, "rsn");
 	}
 
-	if (wpa_psk) {
+	if (wpa_psk || wpa_sae) {
 		NMSettingSecretFlags psk_flags;
 
 		psk_flags = _secret_read_ifcfg_flags (ifcfg, "WPA_PSK_FLAGS");
@@ -3679,8 +3679,12 @@ make_wpa_setting (shvarFile *ifcfg,
 
 		if (adhoc)
 			g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-none", NULL);
-		else
+		else if (wpa_psk)
 			g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", NULL);
+		else if (wpa_sae)
+			g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL);
+		else
+			g_assert_not_reached ();
 	} else if (wpa_eap || ieee8021x) {
 		/* Adhoc mode is mutually exclusive with any 802.1x-based authentication */
 		if (adhoc) {
@@ -3945,9 +3949,8 @@ make_wireless_setting (shvarFile *ifcfg,
 
 	value = svGetValueStr_cp (ifcfg, "CHANNEL");
 	if (value) {
-		errno = 0;
 		chan = _nm_utils_ascii_str_to_int64 (value, 10, 1, 196, 0);
-		if (errno || (chan == 0)) {
+		if (chan == 0) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Invalid wireless channel '%s'", value);
 			g_free (value);
@@ -4991,7 +4994,7 @@ handle_bridge_option (NMSetting *setting,
 			} else {
 				v = _nm_utils_ascii_str_to_int64 (value, 10, 0, 1, -1);
 				if (v == -1) {
-					error_message = g_strerror (errno);
+					error_message = nm_strerror_native (errno);
 					goto warn;
 				}
 			}
@@ -5003,7 +5006,7 @@ handle_bridge_option (NMSetting *setting,
 		case G_TYPE_UINT:
 			v = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT, -1);
 			if (v == -1) {
-				error_message = g_strerror (errno);
+				error_message = nm_strerror_native (errno);
 				goto warn;
 			}
 			if (!nm_g_object_set_property_uint (G_OBJECT (setting), m[i].property_name, v, NULL)) {
@@ -5228,16 +5231,14 @@ is_vlan_device (const char *name, shvarFile *parsed)
 static gboolean
 is_wifi_device (const char *name, shvarFile *parsed)
 {
-	int ifindex;
+	const NMPlatformLink *pllink;
 
 	g_return_val_if_fail (name != NULL, FALSE);
 	g_return_val_if_fail (parsed != NULL, FALSE);
 
-	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, name);
-	if (ifindex == 0)
-		return FALSE;
-
-	return nm_platform_link_get_type (NM_PLATFORM_GET, ifindex) == NM_LINK_TYPE_WIFI;
+	pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name);
+	return    pllink
+	       && pllink->type == NM_LINK_TYPE_WIFI;
 }
 
 static void
@@ -5383,7 +5384,7 @@ make_vlan_setting (shvarFile *ifcfg,
 	parse_prio_map_list (s_vlan, ifcfg, "VLAN_INGRESS_PRIORITY_MAP", NM_VLAN_INGRESS_MAP);
 	parse_prio_map_list (s_vlan, ifcfg, "VLAN_EGRESS_PRIORITY_MAP", NM_VLAN_EGRESS_MAP);
 
-	return g_steal_pointer (&s_vlan);
+	return NM_SETTING (g_steal_pointer (&s_vlan));
 }
 
 static NMConnection *
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 49096d26..22c9061b 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -23,7 +23,6 @@
 #include "nms-ifcfg-rh-utils.h"
 
 #include <stdlib.h>
-#include <string.h>
 
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index f5be7520..ee7fd161 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -22,11 +22,9 @@
 
 #include "nms-ifcfg-rh-writer.h"
 
-#include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
-#include <errno.h>
 #include <stdlib.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -603,6 +601,10 @@ write_wireless_security_setting (NMConnection *connection,
 		svSetValueStr (ifcfg, "KEY_MGMT", "WPA-PSK");
 		wpa = TRUE;
 		*no_8021x = TRUE;
+	} else if (!strcmp (key_mgmt, "sae")) {
+		svSetValueStr (ifcfg, "KEY_MGMT", "SAE");
+		wpa = TRUE;
+		*no_8021x = TRUE;
 	} else if (!strcmp (key_mgmt, "ieee8021x")) {
 		svSetValueStr (ifcfg, "KEY_MGMT", "IEEE8021X");
 		dynamic_wep = TRUE;
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index fe82fbdd..f3d58e26 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -27,11 +27,9 @@
 
 #include "shvar.h"
 
-#include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -215,9 +213,9 @@ _escape_ansic (const char *source)
 
 /*****************************************************************************/
 
-#define _char_req_escape(ch)        NM_IN_SET (ch,      '\"', '\\',       '$', '`')
-#define _char_req_escape_old(ch)    NM_IN_SET (ch,      '\"', '\\', '\'', '$', '`', '~')
-#define _char_req_quotes(ch)        NM_IN_SET (ch, ' ',             '\'',           '~', '\t', '|', '&', ';', '(', ')', '<', '>')
+#define _char_req_escape(ch)        NM_IN_SET (ch,      '"', '\\',       '$', '`')
+#define _char_req_escape_old(ch)    NM_IN_SET (ch,      '"', '\\', '\'', '$', '`', '~')
+#define _char_req_quotes(ch)        NM_IN_SET (ch, ' ',            '\'',           '~', '\t', '|', '&', ';', '(', ')', '<', '>')
 
 const char *
 svEscape (const char *s, char **to_free)
@@ -330,7 +328,7 @@ _gstr_init (GString **str, const char *value, gsize i)
 		 * Unescaping usually does not extend the length of a string,
 		 * so we might be tempted to allocate a fixed buffer of length
 		 * (strlen(value)+CONST).
-		 * However, due to $'\Ux' escapes, the maxium length is some
+		 * However, due to $'\Ux' escapes, the maximum length is some
 		 * (FACTOR*strlen(value) + CONST), which is non trivial to get
 		 * right in all cases. Also, we would have to provision for the
 		 * very unlikely extreme case.
@@ -453,7 +451,7 @@ svUnescape (const char *value, char **to_free)
 					if (NM_IN_SET (value[i], '$', '`', '"', '\\')) {
 						/* Drop the backslash. */
 					} else if (NM_IN_SET (value[i], '\'', '~')) {
-						/* '\'' and '~' in double qoutes are not handled special by shell.
+						/* '\'' and '~' in double quotes are not handled special by shell.
 						 * However, old versions of svEscape() would wrongly use double-quoting
 						 * with backslash escaping for these characters (expecting svUnescape()
 						 * to remove the backslash).
@@ -649,7 +647,7 @@ void
 _nmtst_svFileSetName (shvarFile *s, const char *fileName)
 {
 	/* changing the file name is not supported for regular
-	 * operation. Only allowed to use in tests, othewise,
+	 * operation. Only allowed to use in tests, otherwise,
 	 * the filename is immutable. */
 	g_free (s->fileName);
 	s->fileName = g_strdup (fileName);
@@ -815,7 +813,7 @@ svOpenFileInternal (const char *name, gboolean create, GError **error)
 
 		g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv),
 		             "Could not read file '%s': %s",
-		             name, strerror (errsv));
+		             name, nm_strerror_native (errsv));
 		return NULL;
 	}
 
@@ -1317,34 +1315,32 @@ svWriteFile (shvarFile *s, int mode, GError **error)
 	FILE *f;
 	int tmpfd;
 	CList *current;
+	int errsv;
 
 	if (s->modified) {
 		if (s->fd == -1)
 			s->fd = open (s->fileName, O_WRONLY | O_CREAT | O_CLOEXEC, mode);
 		if (s->fd == -1) {
-			int errsv = errno;
-
+			errsv = errno;
 			g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv),
 			             "Could not open file '%s' for writing: %s",
-			             s->fileName, strerror (errsv));
+			             s->fileName, nm_strerror_native (errsv));
 			return FALSE;
 		}
 		if (ftruncate (s->fd, 0) < 0) {
-			int errsv = errno;
-
+			errsv = errno;
 			g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv),
 			             "Could not overwrite file '%s': %s",
-			             s->fileName, strerror (errsv));
+			             s->fileName, nm_strerror_native (errsv));
 			return FALSE;
 		}
 
 		tmpfd = fcntl (s->fd, F_DUPFD_CLOEXEC, 0);
 		if (tmpfd == -1) {
-			int errsv = errno;
-
+			errsv = errno;
 			g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv),
 			             "Internal error writing file '%s': %s",
-			             s->fileName, strerror (errsv));
+			             s->fileName, nm_strerror_native (errsv));
 			return FALSE;
 		}
 		f = fdopen (tmpfd, "w");
diff --git a/src/settings/plugins/ifcfg-rh/tests/meson.build b/src/settings/plugins/ifcfg-rh/tests/meson.build
index 29bc9699..f65494bb 100644
--- a/src/settings/plugins/ifcfg-rh/tests/meson.build
+++ b/src/settings/plugins/ifcfg-rh/tests/meson.build
@@ -6,12 +6,12 @@ exe = executable(
   test_unit,
   test_unit + '.c',
   dependencies: test_nm_dep,
-  link_with: libnms_ifcfg_rh_core
+  link_with: libnms_ifcfg_rh_core,
 )
 
 test(
   'ifcfg-rh/' + test_unit,
   test_script,
   timeout: 90,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
 )
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected
index a95a58db..ddbd986f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected
@@ -13,6 +13,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi Band A"
+NAME="Test Write Wi-Fi Band A"
 UUID=${UUID}
 ONBOOT=yes
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 cf325f35..495a24d3 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
@@ -12,6 +12,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi Hidden"
+NAME="Test Write Wi-Fi Hidden"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected
index f3704f10..aec6918a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected
@@ -13,6 +13,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi MAC always"
+NAME="Test Write Wi-Fi MAC always"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected
index 005c6179..9d47163f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected
@@ -12,6 +12,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi MAC default"
+NAME="Test Write Wi-Fi MAC default"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected
index dff17ef2..43c07ddb 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected
@@ -13,6 +13,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi MAC missing"
+NAME="Test Write Wi-Fi MAC missing"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected
index 94274cf9..21f2e2de 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected
@@ -13,6 +13,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi MAC never"
+NAME="Test Write Wi-Fi MAC never"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae
new file mode 100644
index 00000000..68afbe97
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae
@@ -0,0 +1,5 @@
+TYPE=Wireless
+DEVICE=wlan1
+ESSID=blahblah
+MODE=Managed
+KEY_MGMT=SAE
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
index a7156e33..2c1b7fb4 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
@@ -6,7 +6,7 @@
 # expected.
 #
 # Also note that setting NAME will replace the last
-# occurance, and delete all previous once.
+# occurrence, and delete all previous once.
 
 #L1
 NAME=l2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
index 674df840..cf3f45bd 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
@@ -6,7 +6,7 @@
 # expected.
 #
 # Also note that setting NAME will replace the last
-# occurance, and delete all previous once.
+# occurrence, and delete all previous once.
 
 #L1
 
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae b/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae
new file mode 100644
index 00000000..5a9569ed
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae
@@ -0,0 +1 @@
+WPA_PSK="The king is dead."
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default b/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default
index 9a292679..4347405e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default
@@ -1,4 +1,4 @@
 GATEWAYDEV=eth0
-# when devices in IPV6_DEFAULTDEV and IPV6_DEFAULTGW don't match the one in IPV6_DEFAULTGW is prefered
+# when devices in IPV6_DEFAULTDEV and IPV6_DEFAULTGW don't match the one in IPV6_DEFAULTGW is preferred
 IPV6_DEFAULTDEV=eth4
 IPV6_DEFAULTGW=2001::1234%eth0
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 472bb8a6..b352fbfc 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <linux/pkt_sched.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
@@ -123,7 +122,7 @@ _assert_reread_same_FIXME (NMConnection *connection, NMConnection *reread)
 
 /* dummy path for an "expected" file, meaning: don't check for expected
  * written ifcfg file. */
-static const char const NO_EXPECTED[1];
+static const char NO_EXPECTED[1];
 
 static void
 _assert_expected_content (NMConnection *connection, const char *filename, const char *expected)
@@ -2374,7 +2373,7 @@ test_read_wifi_open (void)
 	g_assert_cmpstr (nm_setting_wireless_get_mode (s_wireless), ==, "infrastructure");
 	g_assert_cmpint (nm_setting_wireless_get_channel (s_wireless), ==, 1);
 
-	/* ===== WiFi SECURITY SETTING ===== */
+	/* ===== Wi-Fi SECURITY SETTING ===== */
 	s_wsec = nm_connection_get_setting_wireless_security (connection);
 	g_assert (s_wsec == NULL);
 
@@ -2967,6 +2966,45 @@ test_read_wifi_wpa_psk (void)
 }
 
 static void
+test_read_wifi_sae (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWireless *s_wireless;
+	NMSettingWirelessSecurity *s_wsec;
+	GBytes *ssid;
+	const char *expected_ssid = "blahblah";
+
+	connection = _connection_from_file (TEST_IFCFG_DIR"/ifcfg-test-wifi-sae",
+	                                    NULL, TYPE_WIRELESS, NULL);
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (s_con);
+	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "System blahblah (test-wifi-sae)");
+
+	g_assert_cmpint (nm_setting_connection_get_timestamp (s_con), ==, 0);
+	g_assert (nm_setting_connection_get_autoconnect (s_con));
+
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	g_assert (s_wireless);
+
+	g_assert_cmpint (nm_setting_wireless_get_mtu (s_wireless), ==, 0);
+
+	ssid = nm_setting_wireless_get_ssid (s_wireless);
+	g_assert (ssid);
+	g_assert_cmpmem (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid), expected_ssid, strlen (expected_ssid));
+
+	g_assert (!nm_setting_wireless_get_bssid (s_wireless));
+	g_assert_cmpstr (nm_setting_wireless_get_mode (s_wireless), ==, "infrastructure");
+
+	s_wsec = nm_connection_get_setting_wireless_security (connection);
+	g_assert (s_wsec);
+	g_assert_cmpstr (nm_setting_wireless_security_get_key_mgmt (s_wsec), ==, "sae");
+	g_assert_cmpstr (nm_setting_wireless_security_get_psk (s_wsec), ==, "The king is dead.");
+	g_assert (!nm_setting_wireless_security_get_auth_alg (s_wsec));
+}
+
+static void
 test_read_wifi_wpa_psk_2 (void)
 {
 	NMConnection *connection;
@@ -3275,7 +3313,7 @@ test_read_wifi_dynamic_wep_leap (void)
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wifi);
 
-	/* ===== WiFi SECURITY SETTING ===== */
+	/* ===== Wi-Fi SECURITY SETTING ===== */
 	s_wsec = nm_connection_get_setting_wireless_security (connection);
 	g_assert (s_wsec);
 
@@ -3501,7 +3539,7 @@ test_write_wifi_hidden (void)
 	nm_connection_add_setting (connection, NM_SETTING (s_con));
 
 	g_object_set (s_con,
-	              NM_SETTING_CONNECTION_ID, "Test Write WiFi Hidden",
+	              NM_SETTING_CONNECTION_ID, "Test Write Wi-Fi Hidden",
 	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
 	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
 	              NULL);
@@ -3584,7 +3622,7 @@ test_write_wifi_mac_random (gconstpointer user_data)
 	s_con = (NMSettingConnection *) nm_setting_connection_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_con));
 
-	val = g_strdup_printf ("Test Write WiFi MAC %s", name);
+	val = g_strdup_printf ("Test Write Wi-Fi MAC %s", name);
 	g_object_set (s_con,
 	              NM_SETTING_CONNECTION_ID, val,
 	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
@@ -3803,7 +3841,7 @@ test_write_wifi_band_a (void)
 	nm_connection_add_setting (connection, NM_SETTING (s_con));
 
 	g_object_set (s_con,
-	              NM_SETTING_CONNECTION_ID, "Test Write WiFi Band A",
+	              NM_SETTING_CONNECTION_ID, "Test Write Wi-Fi Band A",
 	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
 	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
 	              NULL);
@@ -7402,8 +7440,6 @@ test_write_mobile_broadband (gconstpointer data)
 		/* GSM setting */
 		s_gsm = (NMSettingGsm *) nm_setting_gsm_new ();
 		nm_connection_add_setting (connection, NM_SETTING (s_gsm));
-
-		g_object_set (s_gsm, NM_SETTING_GSM_NUMBER, "*99#", NULL);
 	} else {
 		/* CDMA setting */
 		s_cdma = (NMSettingCdma *) nm_setting_cdma_new ();
@@ -9983,10 +10019,14 @@ NMTST_DEFINE ();
 
 int main (int argc, char **argv)
 {
+	int errsv;
+
 	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
 
-	if (g_mkdir_with_parents (TEST_SCRATCH_DIR_TMP, 0755) != 0)
-		g_error ("failure to create test directory \"%s\": %s", TEST_SCRATCH_DIR_TMP, g_strerror (errno));
+	if (g_mkdir_with_parents (TEST_SCRATCH_DIR_TMP, 0755) != 0) {
+		errsv = errno;
+		g_error ("failure to create test directory \"%s\": %s", TEST_SCRATCH_DIR_TMP, nm_strerror_native (errsv));
+	}
 
 	g_test_add_func (TPATH "svUnescape", test_svUnescape);
 
@@ -10083,6 +10123,7 @@ int main (int argc, char **argv)
 	g_test_add_func (TPATH "wifi/read/wpa-psk/unquoted2", test_read_wifi_wpa_psk_unquoted2);
 	g_test_add_func (TPATH "wifi/read/wpa-psk/adhoc", test_read_wifi_wpa_psk_adhoc);
 	g_test_add_func (TPATH "wifi/read/wpa-psk/hex", test_read_wifi_wpa_psk_hex);
+	g_test_add_func (TPATH "wifi/read/sae", test_read_wifi_sae);
 	g_test_add_func (TPATH "wifi/read/dynamic-wep/leap", test_read_wifi_dynamic_wep_leap);
 	g_test_add_func (TPATH "wifi/read/wpa/eap/tls", test_read_wifi_wpa_eap_tls);
 	g_test_add_func (TPATH "wifi/read/wpa/eap/ttls/tls", test_read_wifi_wpa_eap_ttls_tls);
diff --git a/src/settings/plugins/ifupdown/meson.build b/src/settings/plugins/ifupdown/meson.build
index 826c7458..42edd438 100644
--- a/src/settings/plugins/ifupdown/meson.build
+++ b/src/settings/plugins/ifupdown/meson.build
@@ -1,11 +1,11 @@
 sources = files(
   'nms-ifupdown-interface-parser.c',
-  'nms-ifupdown-parser.c'
+  'nms-ifupdown-parser.c',
 )
 
 deps = [
   libudev_dep,
-  nm_dep
+  nm_dep,
 ]
 
 libnms_ifupdown_core = static_library(
@@ -16,7 +16,7 @@ libnms_ifupdown_core = static_library(
 
 sources = files(
   'nms-ifupdown-connection.c',
-  'nms-ifupdown-plugin.c'
+  'nms-ifupdown-plugin.c',
 )
 
 libnm_settings_plugin_ifupdown = shared_module(
@@ -27,7 +27,7 @@ libnm_settings_plugin_ifupdown = shared_module(
   link_args: ldflags_linker_script_settings,
   link_depends: linker_script_settings,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_settings_plugin_ifupdown
@@ -37,7 +37,7 @@ core_plugins += libnm_settings_plugin_ifupdown
 run_target(
   'check-local-symbols-settings-ifupdown',
   command: [check_so_symbols, libnm_settings_plugin_ifupdown.full_path()],
-  depends: libnm_settings_plugin_ifupdown
+  depends: libnm_settings_plugin_ifupdown,
 )
 
 check-local-symbols-settings-ifupdown: src/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-connection.c b/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
index 1b817044..d06078a9 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
@@ -24,7 +24,6 @@
 
 #include "nms-ifupdown-connection.h"
 
-#include <string.h>
 #include <glib/gstdio.h>
 
 #include "nm-dbus-interface.h"
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
index 73ecc2f9..6587fc84 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
@@ -26,7 +26,6 @@
 
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 #include <wordexp.h>
 #include <libgen.h>
 
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
index 369fa70d..fd5561ae 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
@@ -25,10 +25,8 @@
 
 #include "nms-ifupdown-parser.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <ctype.h>
 
 #include "nm-core-internal.h"
@@ -63,7 +61,7 @@ _ifupdownplugin_guess_connection_type (if_block *block)
 {
 	const char *ret_type = NULL;
 
-	if(nm_streq0 (ifparser_getkey (block, "inet"), "ppp"))
+	if (nm_streq0 (ifparser_getkey (block, "inet"), "ppp"))
 		ret_type = NM_SETTING_PPP_SETTING_NAME;
 	else {
 		if_data *ifb;
@@ -75,7 +73,7 @@ _ifupdownplugin_guess_connection_type (if_block *block)
 				break;
 			}
 		}
-		if(!ret_type)
+		if (!ret_type)
 			ret_type = NM_SETTING_WIRED_SETTING_NAME;
 	}
 
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index f0e64d1d..99a59477 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -26,7 +26,6 @@
 
 #include "nms-ifupdown-plugin.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <gmodule.h>
 
diff --git a/src/settings/plugins/ifupdown/tests/meson.build b/src/settings/plugins/ifupdown/tests/meson.build
index 5a2383d9..9b844c75 100644
--- a/src/settings/plugins/ifupdown/tests/meson.build
+++ b/src/settings/plugins/ifupdown/tests/meson.build
@@ -4,11 +4,11 @@ exe = executable(
   test_unit,
   test_unit + '.c',
   dependencies: test_nm_dep,
-  link_with: libnms_ifupdown_core
+  link_with: libnms_ifupdown_core,
 )
 
 test(
   'ifupdown/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  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 82ee1c4a..674cb19c 100644
--- a/src/settings/plugins/ifupdown/tests/test-ifupdown.c
+++ b/src/settings/plugins/ifupdown/tests/test-ifupdown.c
@@ -20,8 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "nm-core-internal.h"
 
 #include "settings/plugins/ifupdown/nms-ifupdown-interface-parser.h"
diff --git a/src/settings/plugins/keyfile/nms-keyfile-connection.c b/src/settings/plugins/keyfile/nms-keyfile-connection.c
index 7511f206..3b362978 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-connection.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-connection.c
@@ -23,7 +23,6 @@
 
 #include "nms-keyfile-connection.h"
 
-#include <string.h>
 #include <glib/gstdio.h>
 
 #include "nm-dbus-interface.h"
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index 346b78c0..c13cc1ff 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -26,8 +26,6 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <sys/types.h>
-#include <string.h>
-
 #include <glib/gstdio.h>
 
 #include "nm-connection.h"
@@ -36,6 +34,7 @@
 #include "nm-utils.h"
 #include "nm-config.h"
 #include "nm-core-internal.h"
+#include "nm-keyfile-internal.h"
 
 #include "settings/nm-settings-plugin.h"
 
@@ -171,7 +170,6 @@ update_connection (NMSKeyfilePlugin *self,
 	NMSKeyfileConnection *connection_by_uuid;
 	GError *local = NULL;
 	const char *uuid;
-	int dir_len;
 
 	g_return_val_if_fail (!source || NM_IS_CONNECTION (source), NULL);
 	g_return_val_if_fail (full_path || source, NULL);
@@ -179,17 +177,8 @@ update_connection (NMSKeyfilePlugin *self,
 	if (full_path)
 		_LOGD ("loading from file \"%s\"...", full_path);
 
-	if (g_str_has_prefix (full_path, nms_keyfile_utils_get_path ())) {
-		dir_len = strlen (nms_keyfile_utils_get_path ());
-	} else if (g_str_has_prefix (full_path, NM_CONFIG_KEYFILE_PATH_IN_MEMORY)) {
-		dir_len = NM_STRLEN (NM_CONFIG_KEYFILE_PATH_IN_MEMORY);
-	} else {
-		/* Just make sure the file name is not going go pass the following check. */
-		dir_len = strlen (full_path);
-	}
-
-	if (   full_path[dir_len] != '/'
-	    || strchr (full_path + dir_len + 1, '/') != NULL) {
+	if (   !nm_utils_file_is_in_path (full_path, nms_keyfile_utils_get_path ())
+	    && !nm_utils_file_is_in_path (full_path, NM_KEYFILE_PATH_NAME_RUN)) {
 		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		                     "File not in recognized system-connections directory");
 		return FALSE;
@@ -322,7 +311,7 @@ dir_changed (GFileMonitor *monitor,
 	gboolean exists;
 
 	full_path = g_file_get_path (file);
-	if (nms_keyfile_utils_should_ignore_file (full_path, FALSE)) {
+	if (nm_keyfile_utils_ignore_filename (full_path, FALSE)) {
 		g_free (full_path);
 		return;
 	}
@@ -444,7 +433,7 @@ _read_dir (GPtrArray *filenames,
 	}
 
 	while ((item = g_dir_read_name (dir))) {
-		if (nms_keyfile_utils_should_ignore_file (item, require_extension))
+		if (nm_keyfile_utils_ignore_filename (item, require_extension))
 			continue;
 		g_ptr_array_add (filenames, g_build_filename (path, item, NULL));
 	}
@@ -467,7 +456,7 @@ read_connections (NMSettingsPlugin *config)
 
 	filenames = g_ptr_array_new_with_free_func (g_free);
 
-	_read_dir (filenames, NM_CONFIG_KEYFILE_PATH_IN_MEMORY, TRUE);
+	_read_dir (filenames, NM_KEYFILE_PATH_NAME_RUN, TRUE);
 	_read_dir (filenames, nms_keyfile_utils_get_path (), FALSE);
 
 	alive_connections = g_hash_table_new (nm_direct_hash, NULL);
@@ -476,7 +465,7 @@ read_connections (NMSettingsPlugin *config)
 	 * iterating over the files.
 	 *
 	 * To have sensible, reproducible behavior, sort the paths by last modification
-	 * time prefering older files.
+	 * time preferring older files.
 	 */
 	paths = _paths_from_connections (priv->connections);
 	g_ptr_array_sort_with_data (filenames, (GCompareDataFunc) _sort_paths, paths);
@@ -523,35 +512,6 @@ get_connections (NMSettingsPlugin *config)
 }
 
 static gboolean
-_file_is_in_path (const char *abs_filename,
-                  const char *abs_path)
-{
-	gsize l;
-
-	/* FIXME: ensure that both paths are at least normalized (coalescing ".",
-	 * duplicate '/', and trailing '/'). */
-
-	nm_assert (abs_filename && abs_filename[0] == '/');
-	nm_assert (abs_path && abs_path[0] == '/');
-
-	l = strlen (abs_path);
-	if (strncmp (abs_filename, abs_path, l) != 0)
-		return FALSE;
-
-	abs_filename += l;
-	while (abs_filename[0] == '/')
-		abs_filename++;
-
-	if (!abs_filename[0])
-		return FALSE;
-
-	if (strchr (abs_filename, '/'))
-		return FALSE;
-
-	return TRUE;
-}
-
-static gboolean
 load_connection (NMSettingsPlugin *config,
                  const char *filename)
 {
@@ -559,25 +519,14 @@ load_connection (NMSettingsPlugin *config,
 	NMSKeyfileConnection *connection;
 	gboolean require_extension;
 
-	/* the test whether to require a file extension tries to figure out whether
-	 * the provided filename is inside /etc or /run.
-	 *
-	 * However, on Posix a filename just resolves to an Inode, and there can
-	 * be any kind of paths that point to the same Inode. It's not generally possible
-	 * to check for that (unless, we would stat all files in the target directory
-	 * and see whether their inode matches).
-	 *
-	 * So, when loading the file do something simpler: require that the path
-	 * starts with the well-known prefix. This rejects symlinks or hard links
-	 * which would actually also point to the same file. */
-	if (_file_is_in_path (filename, nms_keyfile_utils_get_path ()))
+	if (nm_utils_file_is_in_path (filename, nms_keyfile_utils_get_path ()))
 		require_extension = FALSE;
-	else if (_file_is_in_path (filename, NM_CONFIG_KEYFILE_PATH_IN_MEMORY))
+	else if (nm_utils_file_is_in_path (filename, NM_KEYFILE_PATH_NAME_RUN))
 		require_extension = TRUE;
 	else
 		return FALSE;
 
-	if (nms_keyfile_utils_should_ignore_file (filename, require_extension))
+	if (nm_keyfile_utils_ignore_filename (filename, require_extension))
 		return FALSE;
 
 	connection = update_connection (self, NULL, filename, find_by_path (self, filename), TRUE, NULL, NULL);
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/settings/plugins/keyfile/nms-keyfile-reader.c
index 580a857a..5778f13c 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -23,7 +23,6 @@
 #include "nms-keyfile-reader.h"
 
 #include <sys/stat.h>
-#include <string.h>
 
 #include "nm-keyfile-internal.h"
 
@@ -142,11 +141,11 @@ nms_keyfile_reader_from_keyfile (GKeyFile *key_file,
 	if (!connection)
 		return NULL;
 
-	if (g_str_has_suffix (filename, NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION)) {
+	if (g_str_has_suffix (filename, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) {
 		gsize l = strlen (filename);
 
-		if (l > NM_STRLEN (NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION))
-			filename_id = g_strndup (filename, l - NM_STRLEN (NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION));
+		if (l > NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION))
+			filename_id = g_strndup (filename, l - NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION));
 	}
 
 	nm_keyfile_read_ensure_id (connection, filename_id ?: filename);
@@ -172,7 +171,8 @@ nms_keyfile_reader_from_file (const char *full_filename,
 	nm_assert (full_filename && full_filename[0] == '/');
 	nm_assert (!profile_dir || profile_dir[0] == '/');
 
-	if (!nms_keyfile_utils_check_file_permissions (full_filename,
+	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_KEYFILE,
+	                                               full_filename,
 	                                               NULL,
 	                                               error))
 		return NULL;
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.c b/src/settings/plugins/keyfile/nms-keyfile-utils.c
index c3bfcdee..3c4b0288 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -23,111 +23,223 @@
 #include "nms-keyfile-utils.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/stat.h>
 
+#include "nm-keyfile-internal.h"
+#include "nm-utils.h"
 #include "nm-setting-wired.h"
 #include "nm-setting-wireless.h"
 #include "nm-setting-wireless-security.h"
 #include "nm-config.h"
 
-#define NM_CONFIG_KEYFILE_PATH_DEFAULT NMCONFDIR "/system-connections"
-
 /*****************************************************************************/
 
-static const char temp_letters[] =
-"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
-
-/*
- * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()).
- */
-static gboolean
-check_mkstemp_suffix (const char *path)
+char *
+nms_keyfile_loaded_uuid_filename (const char *dirname,
+                                  const char *uuid,
+                                  gboolean temporary)
 {
-	const char *ptr;
-
-	g_return_val_if_fail (path != NULL, FALSE);
+	char filename[250];
+
+	nm_assert (dirname && dirname[0] == '/');
+	nm_assert (uuid && nm_utils_is_uuid (uuid) && !strchr (uuid, '/'));
+
+	if (g_snprintf (filename,
+	                sizeof (filename),
+	                "%s%s%s%s",
+	                NM_KEYFILE_PATH_PREFIX_NMLOADED,
+	                uuid,
+	                NM_KEYFILE_PATH_SUFFIX_NMCONNECTION,
+	                temporary ? "~" : "") >= sizeof (filename)) {
+		/* valid uuids are limited in length. The buffer should always be large
+		 * enough. */
+		nm_assert_not_reached ();
+		return NULL;
+	}
 
-	/* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */
-	ptr = strrchr (path, '.');
-	if (ptr && (strspn (ptr + 1, temp_letters) == 6) && (! ptr[7]))
-		return TRUE;
-	return FALSE;
+	return g_build_filename (dirname, filename, NULL);
 }
 
-static gboolean
-check_prefix_dot (const char *base)
+gboolean
+nms_keyfile_loaded_uuid_read (const char *dirname,
+                              const char *filename,
+                              char **out_full_filename,
+                              char **out_uuid,
+                              char **out_loaded_path)
 {
-	nm_assert (base && base[0]);
+	const char *uuid;
+	const char *tmp;
+	gsize len;
+	gs_free char *full_filename = NULL;
+	gs_free char *ln = NULL;
+
+	nm_assert (dirname && dirname[0] == '/');
+	nm_assert (filename && filename[0] && !strchr (filename, '/'));
+
+	if (filename[0] != '.') {
+		/* the hidden-uuid filename must start with '.'. That is,
+		 * so that it does not conflict with regular keyfiles according
+		 * to nm_keyfile_utils_ignore_filename(). */
+		return FALSE;
+	}
+
+	len = strlen (filename);
+	if (   len <= NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)
+	    || memcmp (filename, NM_KEYFILE_PATH_PREFIX_NMLOADED, NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)) != 0) {
+		/* the filename does not have the right prefix. */
+		return FALSE;
+	}
+
+	tmp = &filename[NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)];
+	len -= NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED);
+
+	if (   len <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)
+	    || memcmp (&tmp[len - NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)],
+	               NM_KEYFILE_PATH_SUFFIX_NMCONNECTION,
+	               NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) != 0) {
+		/* the file does not have the right suffix. */
+		return FALSE;
+	}
+	len -= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
+
+	if (!NM_IN_SET (len, 36, 40)) {
+		/* the remaining part of the filename has not the right length to
+		 * contain a UUID (according to nm_utils_is_uuid()). */
+		return FALSE;
+	}
+
+	uuid = nm_strndup_a (100, tmp, len, NULL);
+	if (!nm_utils_is_uuid (uuid))
+		return FALSE;
 
-	return base[0] == '.';
+	full_filename = g_build_filename (dirname, filename, NULL);
+
+	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_NMLOADED,
+	                                               full_filename,
+	                                               NULL,
+	                                               NULL))
+		return FALSE;
+
+	ln = nm_utils_read_link_absolute (full_filename, NULL);
+	if (!ln)
+		return FALSE;
+
+	NM_SET_OUT (out_uuid, g_strdup (uuid));
+	NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
+	NM_SET_OUT (out_loaded_path, g_steal_pointer (&ln));
+	return TRUE;
 }
 
-static gboolean
-check_suffix (const char *base, const char *tag)
+gboolean
+nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
+                                        char **out_dirname,
+                                        char **out_filename,
+                                        char **out_uuid,
+                                        char **out_loaded_path)
 {
-	int len, tag_len;
+	gs_free char *dirname = NULL;
+	gs_free char *filename = NULL;
 
-	g_return_val_if_fail (base != NULL, TRUE);
-	g_return_val_if_fail (tag != NULL, TRUE);
+	nm_assert (full_filename && full_filename[0] == '/');
 
-	len = strlen (base);
-	tag_len = strlen (tag);
-	if ((len > tag_len) && !g_ascii_strcasecmp (base + len - tag_len, tag))
-		return TRUE;
-	return FALSE;
-}
+	filename = g_path_get_basename (full_filename);
+	dirname = g_path_get_dirname (full_filename);
 
-#define SWP_TAG ".swp"
-#define SWPX_TAG ".swpx"
-#define PEM_TAG ".pem"
-#define DER_TAG ".der"
+	if (!nms_keyfile_loaded_uuid_read (dirname,
+	                                   filename,
+	                                   NULL,
+	                                   out_uuid,
+	                                   out_loaded_path))
+		return FALSE;
+
+	NM_SET_OUT (out_dirname, g_steal_pointer (&dirname));
+	NM_SET_OUT (out_filename, g_steal_pointer (&filename));
+	return TRUE;
+}
 
 gboolean
-nms_keyfile_utils_should_ignore_file (const char *filename, gboolean require_extension)
+nms_keyfile_loaded_uuid_write (const char *dirname,
+                               const char *uuid,
+                               const char *loaded_path,
+                               gboolean allow_relative,
+                               char **out_full_filename)
 {
-	gs_free char *base = NULL;
-
-	g_return_val_if_fail (filename != NULL, TRUE);
-
-	base = g_path_get_basename (filename);
-	g_return_val_if_fail (base != NULL, TRUE);
-
-	/* Ignore hidden and backup files */
-	/* should_ignore_file() must mirror escape_filename() */
-	if (check_prefix_dot (base) || check_suffix (base, "~"))
-		return TRUE;
-	/* Ignore temporary files */
-	if (check_mkstemp_suffix (base))
-		return TRUE;
-	/* Ignore 802.1x certificates and keys */
-	if (check_suffix (base, PEM_TAG) || check_suffix (base, DER_TAG))
-		return TRUE;
-
-	if (require_extension) {
-		gsize l = strlen (base);
-
-		if (   l <= NM_STRLEN (NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION)
-		    || !g_str_has_suffix (base, NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION))
-			return TRUE;
+	gs_free char *full_filename_tmp = NULL;
+	gs_free char *full_filename = NULL;
+
+	nm_assert (dirname && dirname[0] == '/');
+	nm_assert (uuid && nm_utils_is_uuid (uuid) && !strchr (uuid, '/'));
+	nm_assert (!loaded_path || loaded_path[0] == '/');
+
+	full_filename_tmp = nms_keyfile_loaded_uuid_filename (dirname, uuid, TRUE);
+
+	nm_assert (g_str_has_suffix (full_filename_tmp, "~"));
+	nm_assert (nm_utils_file_is_in_path (full_filename_tmp, dirname));
+
+	(void) unlink (full_filename_tmp);
+
+	if (!loaded_path) {
+		gboolean success = TRUE;
+
+		full_filename_tmp[strlen (full_filename_tmp) - 1] = '\0';
+		if (unlink (full_filename_tmp) != 0)
+			success = NM_IN_SET (errno, ENOENT);
+		NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp));
+		return success;
+	}
+
+	if (allow_relative) {
+		const char *f;
+
+		f = nm_utils_file_is_in_path (loaded_path, dirname);
+		if (f) {
+			/* @loaded_path points to a file directly in @dirname.
+			 * Don't use absolute paths. */
+			loaded_path = f;
+		}
+	}
+
+	if (symlink (loaded_path, full_filename_tmp) != 0) {
+		full_filename_tmp[strlen (full_filename_tmp) - 1] = '\0';
+		NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp));
+		return FALSE;
 	}
 
-	return FALSE;
+	full_filename = g_strdup (full_filename_tmp);
+	full_filename[strlen (full_filename) - 1] = '\0';
+	if (rename (full_filename_tmp, full_filename) != 0) {
+		(void) unlink (full_filename_tmp);
+		NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
+		return FALSE;
+	}
+
+	NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
+	return TRUE;
 }
 
 /*****************************************************************************/
 
 gboolean
-nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
+nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype,
+                                               const struct stat *st,
                                                GError **error)
 {
 	g_return_val_if_fail (st, FALSE);
 
-	if (!S_ISREG (st->st_mode)) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "file is not a regular file");
-		return FALSE;
-	}
+	if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) {
+		if (!S_ISREG (st->st_mode)) {
+			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			                     "file is not a regular file");
+			return FALSE;
+		}
+	} else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) {
+		if (!S_ISLNK (st->st_mode)) {
+			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			                     "file is not a slink");
+			return FALSE;
+		}
+	} else
+		g_return_val_if_reached (FALSE);
 
 	if (!NM_FLAGS_HAS (nm_utils_get_testing (), NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK)) {
 		if (st->st_uid != 0) {
@@ -137,7 +249,8 @@ nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
 			return FALSE;
 		}
 
-		if (st->st_mode & 0077) {
+		if (   filetype == NMS_KEYFILE_FILETYPE_KEYFILE
+		    && (st->st_mode & 0077)) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "File permissions (%03o) are insecure",
 			             st->st_mode);
@@ -149,7 +262,8 @@ nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
 }
 
 gboolean
-nms_keyfile_utils_check_file_permissions (const char *filename,
+nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype,
+                                          const char *filename,
                                           struct stat *out_st,
                                           GError **error)
 {
@@ -158,14 +272,24 @@ nms_keyfile_utils_check_file_permissions (const char *filename,
 
 	g_return_val_if_fail (filename && filename[0] == '/', FALSE);
 
-	if (stat (filename, &st) != 0) {
-		errsv = errno;
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "cannot access file: %s", g_strerror (errsv));
-		return FALSE;
-	}
+	if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) {
+		if (stat (filename, &st) != 0) {
+			errsv = errno;
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			             "cannot access file: %s", nm_strerror_native (errsv));
+			return FALSE;
+		}
+	} else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) {
+		if (lstat (filename, &st) != 0) {
+			errsv = errno;
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			             "cannot access file: %s", nm_strerror_native (errsv));
+			return FALSE;
+		}
+	} else
+		g_return_val_if_reached (FALSE);
 
-	if (!nms_keyfile_utils_check_file_permissions_stat (&st, error))
+	if (!nms_keyfile_utils_check_file_permissions_stat (filetype, &st, error))
 		return FALSE;
 
 	NM_SET_OUT (out_st, st);
@@ -174,50 +298,6 @@ nms_keyfile_utils_check_file_permissions (const char *filename,
 
 /*****************************************************************************/
 
-char *
-nms_keyfile_utils_escape_filename (const char *filename,
-                                   gboolean with_extension)
-{
-	GString *str;
-	const char *f = filename;
-	/* keyfile used to escape with '*', do not change that behavior.
-	 *
-	 * But for newly added escapings, use '_' instead.
-	 * Also, @with_extension is new-style. */
-	const char ESCAPE_CHAR = with_extension ? '_' : '*';
-	const char ESCAPE_CHAR2 = '_';
-
-	g_return_val_if_fail (filename && filename[0], NULL);
-
-	str = g_string_sized_new (60);
-
-	/* Convert '/' to ESCAPE_CHAR */
-	for (f = filename; f[0]; f++) {
-		if (f[0] == '/')
-			g_string_append_c (str, ESCAPE_CHAR);
-		else
-			g_string_append_c (str, f[0]);
-	}
-
-	/* escape_filename() must avoid anything that should_ignore_file() would reject.
-	 * We can escape here more aggressivly then what we would read back. */
-	if (check_prefix_dot (str->str))
-		str->str[0] = ESCAPE_CHAR2;
-	if (check_suffix (str->str, "~"))
-		str->str[str->len - 1] = ESCAPE_CHAR2;
-	if (   check_mkstemp_suffix (str->str)
-	    || check_suffix (str->str, PEM_TAG)
-	    || check_suffix (str->str, DER_TAG))
-		g_string_append_c (str, ESCAPE_CHAR2);
-
-	if (with_extension)
-		g_string_append (str, NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION);
-
-	return g_string_free (str, FALSE);;
-}
-
-/*****************************************************************************/
-
 const char *
 nms_keyfile_utils_get_path (void)
 {
@@ -229,7 +309,7 @@ nms_keyfile_utils_get_path (void)
 		                                 NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
 		                                 NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
 		if (!path)
-			path = g_strdup (""NM_CONFIG_KEYFILE_PATH_DEFAULT"");
+			path = g_strdup (""NM_KEYFILE_PATH_NAME_ETC_DEFAULT"");
 	}
 	return path;
 }
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.h b/src/settings/plugins/keyfile/nms-keyfile-utils.h
index 297dd4ea..bc601dad 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.h
@@ -23,27 +23,52 @@
 
 #include "NetworkManagerUtils.h"
 
-#define NM_CONFIG_KEYFILE_PATH_IN_MEMORY NMRUNDIR "/system-connections"
-
-#define NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION      ".nmconnection"
-
 #define NMS_KEYFILE_CONNECTION_LOG_PATH(path)  ((path) ?: "in-memory")
 #define NMS_KEYFILE_CONNECTION_LOG_FMT         "%s (%s,\"%s\")"
 #define NMS_KEYFILE_CONNECTION_LOG_ARG(con)    NMS_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con))
 #define NMS_KEYFILE_CONNECTION_LOG_FMTD        "%s (%s,\"%s\",%p)"
 #define NMS_KEYFILE_CONNECTION_LOG_ARGD(con)   NMS_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con)), (con)
 
-gboolean nms_keyfile_utils_should_ignore_file (const char *filename, gboolean require_extension);
-
-char *nms_keyfile_utils_escape_filename (const char *filename, gboolean with_extension);
+typedef enum {
+	NMS_KEYFILE_FILETYPE_KEYFILE,
+	NMS_KEYFILE_FILETYPE_NMLOADED,
+} NMSKeyfileFiletype;
 
 const char *nms_keyfile_utils_get_path (void);
 
+/*****************************************************************************/
+
+char *nms_keyfile_loaded_uuid_filename (const char *dirname,
+                                        const char *uuid,
+                                        gboolean temporary);
+
+gboolean nms_keyfile_loaded_uuid_read (const char *dirname,
+                                       const char *filename,
+                                       char **out_full_filename,
+                                       char **out_uuid,
+                                       char **out_loaded_path);
+
+gboolean nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
+                                                 char **out_dirname,
+                                                 char **out_filename,
+                                                 char **out_uuid,
+                                                 char **out_loaded_path);
+
+gboolean nms_keyfile_loaded_uuid_write (const char *dirname,
+                                        const char *uuid,
+                                        const char *loaded_path,
+                                        gboolean allow_relative,
+                                        char **out_full_filename);
+
+/*****************************************************************************/
+
 struct stat;
-gboolean nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
+gboolean nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype,
+                                                        const struct stat *st,
                                                         GError **error);
 
-gboolean nms_keyfile_utils_check_file_permissions (const char *filename,
+gboolean nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype,
+                                                   const char *filename,
                                                    struct stat *out_st,
                                                    GError **error);
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index df26ea60..8c75d8c7 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -26,8 +26,6 @@
 #include <stdlib.h>
 #include <sys/stat.h>
 #include <unistd.h>
-#include <errno.h>
-#include <string.h>
 
 #include "nm-keyfile-internal.h"
 
@@ -177,27 +175,30 @@ _internal_write_connection (NMConnection *connection,
                             uid_t owner_uid,
                             pid_t owner_grp,
                             const char *existing_path,
+                            gboolean existing_path_read_only,
                             gboolean force_rename,
                             char **out_path,
                             NMConnection **out_reread,
                             gboolean *out_reread_same,
                             GError **error)
 {
-	gs_unref_keyfile GKeyFile *key_file = NULL;
-	gs_free char *data = NULL;
-	gsize len;
+	gs_unref_keyfile GKeyFile *kf_file = NULL;
+	gs_free char *kf_content_buf = NULL;
+	gsize kf_content_len;
 	gs_free char *path = NULL;
 	const char *id;
 	WriteInfo info = { 0 };
 	GError *local_err = NULL;
 	int errsv;
-	gboolean rename = force_rename;
+	gboolean rename;
 
 	g_return_val_if_fail (!out_path || !*out_path, FALSE);
 	g_return_val_if_fail (keyfile_dir && keyfile_dir[0] == '/', FALSE);
 
-	if (existing_path && !g_str_has_prefix (existing_path, keyfile_dir))
-		rename = TRUE;
+	rename =    force_rename
+	         || existing_path_read_only
+	         || (   existing_path
+	             && !nm_utils_file_is_in_path (existing_path, keyfile_dir));
 
 	switch (_nm_connection_verify (connection, error)) {
 	case NM_SETTING_VERIFY_NORMALIZABLE:
@@ -214,11 +215,11 @@ _internal_write_connection (NMConnection *connection,
 
 	info.keyfile_dir = keyfile_dir;
 
-	key_file = nm_keyfile_write (connection, _handler_write, &info, error);
-	if (!key_file)
+	kf_file = nm_keyfile_write (connection, _handler_write, &info, error);
+	if (!kf_file)
 		return FALSE;
-	data = g_key_file_to_data (key_file, &len, error);
-	if (!data)
+	kf_content_buf = g_key_file_to_data (kf_file, &kf_content_len, error);
+	if (!kf_content_buf)
 		return FALSE;
 
 	if (!g_file_test (keyfile_dir, G_FILE_TEST_IS_DIR))
@@ -227,13 +228,14 @@ _internal_write_connection (NMConnection *connection,
 	/* If we have existing file path, use it. Else generate one from
 	 * connection's ID.
 	 */
-	if (existing_path != NULL && !rename) {
+	if (   existing_path
+	    && !rename)
 		path = g_strdup (existing_path);
-	} else {
-		char *filename_escaped = nms_keyfile_utils_escape_filename (id, with_extension);
+	else {
+		gs_free char *filename_escaped = NULL;
 
+		filename_escaped = nm_keyfile_utils_create_filename (id, with_extension);
 		path = g_build_filename (keyfile_dir, filename_escaped, NULL);
-		g_free (filename_escaped);
 	}
 
 	/* If a file with this path already exists (but isn't the existing path
@@ -243,32 +245,34 @@ _internal_write_connection (NMConnection *connection,
 	 * there's a race here, but there's not a lot we can do about it, and
 	 * we shouldn't get more than one connection with the same UUID either.
 	 */
-	if (g_strcmp0 (path, existing_path) != 0 && g_file_test (path, G_FILE_TEST_EXISTS)) {
+	if (   !nm_streq0 (path, existing_path)
+	    && g_file_test (path, G_FILE_TEST_EXISTS)) {
 		guint i;
 		gboolean name_found = FALSE;
 
 		/* A keyfile with this connection's ID already exists. Pick another name. */
 		for (i = 0; i < 100; i++) {
-			char *filename, *filename_escaped;
+			gs_free char *filename_escaped = NULL;
+			gs_free char *filename = NULL;
 
 			if (i == 0)
 				filename = g_strdup_printf ("%s-%s", id, nm_connection_get_uuid (connection));
 			else
 				filename = g_strdup_printf ("%s-%s-%u", id, nm_connection_get_uuid (connection), i);
 
-			filename_escaped = nms_keyfile_utils_escape_filename (filename, with_extension);
+			filename_escaped = nm_keyfile_utils_create_filename (filename, with_extension);
 
 			g_free (path);
 			path = g_strdup_printf ("%s/%s", keyfile_dir, filename_escaped);
-			g_free (filename);
-			g_free (filename_escaped);
-			if (g_strcmp0 (path, existing_path) == 0 || !g_file_test (path, G_FILE_TEST_EXISTS)) {
+
+			if (   nm_streq0 (path, existing_path)
+			    || !g_file_test (path, G_FILE_TEST_EXISTS)) {
 				name_found = TRUE;
 				break;
 			}
 		}
 		if (!name_found) {
-			if (existing_path == NULL) {
+			if (existing_path_read_only || !existing_path) {
 				/* this really should not happen, we tried hard to find an unused name... bail out. */
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 				                    "could not find suitable keyfile file name (%s already used)", path);
@@ -281,13 +285,7 @@ _internal_write_connection (NMConnection *connection,
 		}
 	}
 
-	/* In case of updating the connection and changing the file path,
-	 * we need to remove the old one, not to end up with two connections.
-	 */
-	if (existing_path != NULL && strcmp (path, existing_path) != 0)
-		unlink (existing_path);
-
-	nm_utils_file_set_contents (path, data, len, 0600, &local_err);
+	nm_utils_file_set_contents (path, kf_content_buf, kf_content_len, 0600, &local_err);
 	if (local_err) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "error writing to file '%s': %s",
@@ -300,17 +298,24 @@ _internal_write_connection (NMConnection *connection,
 		errsv = errno;
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "error chowning '%s': %s (%d)",
-		             path, g_strerror (errsv), errsv);
+		             path, nm_strerror_native (errsv), errsv);
 		unlink (path);
 		return FALSE;
 	}
 
-	if (out_reread || out_reread_same)
-	{
+	/* In case of updating the connection and changing the file path,
+	 * we need to remove the old one, not to end up with two connections.
+	 */
+	if (   existing_path
+	    && !existing_path_read_only
+	    && !nm_streq (path, existing_path))
+		unlink (existing_path);
+
+	if (out_reread || out_reread_same) {
 		gs_unref_object NMConnection *reread = NULL;
 		gboolean reread_same = FALSE;
 
-		reread = nms_keyfile_reader_from_keyfile (key_file, path, NULL, profile_dir, FALSE, NULL);
+		reread = nms_keyfile_reader_from_keyfile (kf_file, path, NULL, profile_dir, FALSE, NULL);
 
 		nm_assert (NM_IS_CONNECTION (reread));
 
@@ -356,7 +361,7 @@ nms_keyfile_writer_connection (NMConnection *connection,
 	if (save_to_disk)
 		keyfile_dir = nms_keyfile_utils_get_path ();
 	else
-		keyfile_dir = NM_CONFIG_KEYFILE_PATH_IN_MEMORY;
+		keyfile_dir = NM_KEYFILE_PATH_NAME_RUN;
 
 	return _internal_write_connection (connection,
 	                                   keyfile_dir,
@@ -365,6 +370,7 @@ nms_keyfile_writer_connection (NMConnection *connection,
 	                                   0,
 	                                   0,
 	                                   existing_path,
+	                                   FALSE,
 	                                   force_rename,
 	                                   out_path,
 	                                   out_reread,
@@ -390,6 +396,7 @@ nms_keyfile_writer_test_connection (NMConnection *connection,
 	                                   owner_grp,
 	                                   NULL,
 	                                   FALSE,
+	                                   FALSE,
 	                                   out_path,
 	                                   out_reread,
 	                                   out_reread_same,
diff --git a/src/settings/plugins/keyfile/tests/meson.build b/src/settings/plugins/keyfile/tests/meson.build
index 8b94b256..752b6d7b 100644
--- a/src/settings/plugins/keyfile/tests/meson.build
+++ b/src/settings/plugins/keyfile/tests/meson.build
@@ -11,5 +11,6 @@ exe = executable(
 test(
   'keyfile/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile.c b/src/settings/plugins/keyfile/tests/test-keyfile.c
index 4a0e01b3..baecac13 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -1177,7 +1176,6 @@ test_write_bt_dun_connection (void)
 	              NM_SETTING_GSM_APN, "internet2.voicestream.com",
 	              NM_SETTING_GSM_USERNAME, "george.clinton",
 	              NM_SETTING_GSM_PASSWORD, "parliament",
-	              NM_SETTING_GSM_NUMBER,  "*99#",
 	              NULL);
 
 	write_test_connection_and_reread (connection, TRUE);
@@ -1259,7 +1257,6 @@ test_write_gsm_connection (void)
 	              NM_SETTING_GSM_APN, "internet2.voicestream.com",
 	              NM_SETTING_GSM_USERNAME, "george.clinton.again",
 	              NM_SETTING_GSM_PASSWORD, "parliament2",
-	              NM_SETTING_GSM_NUMBER,  "*99#",
 	              NM_SETTING_GSM_PIN, "123456",
 	              NM_SETTING_GSM_NETWORK_ID, "254098",
 	              NM_SETTING_GSM_HOME_ONLY, TRUE,
@@ -2073,7 +2070,7 @@ test_write_new_wireless_group_names (void)
 	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
 	              NULL);
 
-	/* WiFi setting */
+	/* Wi-Fi setting */
 	s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_wifi));
 
@@ -2084,7 +2081,7 @@ test_write_new_wireless_group_names (void)
 	              NULL);
 	g_bytes_unref (ssid);
 
-	/* WiFi security setting */
+	/* Wi-Fi security setting */
 	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
 	g_object_set (s_wsec,
@@ -2333,7 +2330,6 @@ test_write_flags_property (void)
 	s_gsm = nm_setting_gsm_new ();
 	nm_connection_add_setting (connection, s_gsm);
 	g_object_set (s_gsm,
-	              NM_SETTING_GSM_NUMBER, "#99*",
 	              NM_SETTING_GSM_APN, "myapn",
 	              NM_SETTING_GSM_USERNAME, "adfasdfasdf",
 	              NM_SETTING_GSM_PASSWORD_FLAGS, NM_SETTING_SECRET_FLAG_NOT_SAVED | NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
@@ -2466,18 +2462,18 @@ _escape_filename (gboolean with_extension, const char *filename, gboolean would_
 
 	g_assert (filename && filename[0]);
 
-	if (!!would_be_ignored != !!nms_keyfile_utils_should_ignore_file (filename, with_extension)) {
+	if (!!would_be_ignored != !!nm_keyfile_utils_ignore_filename (filename, with_extension)) {
 		if (would_be_ignored)
 			g_error ("We expect filename \"%s\" to be ignored, but it isn't", filename);
 		else
 			g_error ("We expect filename \"%s\" not to be ignored, but it is", filename);
 	}
 
-	esc = nms_keyfile_utils_escape_filename (filename, with_extension);
+	esc = nm_keyfile_utils_create_filename (filename, with_extension);
 	g_assert (esc && esc[0]);
 	g_assert (!strchr (esc, '/'));
 
-	if (nms_keyfile_utils_should_ignore_file (esc, with_extension))
+	if (nm_keyfile_utils_ignore_filename (esc, with_extension))
 		g_error ("Escaping filename \"%s\" yielded \"%s\", but this is ignored", filename, esc);
 }
 
@@ -2503,21 +2499,131 @@ test_nm_keyfile_plugin_utils_escape_filename (void)
 	_escape_filename (FALSE, ".#emacs-locking", TRUE);
 	_escape_filename (FALSE, "file-with-tilde~", TRUE);
 	_escape_filename (FALSE, ".file-with-dot", TRUE);
+	_escape_filename (FALSE, "/some/path/with/trailing/slash/", TRUE);
+	_escape_filename (FALSE, "/some/path/without/trailing/slash", FALSE);
 
 	_escape_filename (TRUE, "lala", TRUE);
 }
 
 /*****************************************************************************/
 
+static void
+_assert_keyfile_loaded_uuid (const char *dirname,
+                             const char *uuid,
+                             const char *loaded_path,
+                             gboolean allow_relative,
+                             const char *exp_full_filename,
+                             const char *exp_uuid,
+                             const char *exp_symlink_target,
+                             const char *exp_loaded_path)
+{
+	gs_free char *full_filename = NULL;
+	gs_free char *symlink_target = NULL;
+	gs_free char *uuid2 = NULL;
+	gs_free char *loaded_path2 = NULL;
+	gs_free char *dirname3 = NULL;
+	gs_free char *filename3 = NULL;
+	gs_free char *uuid3 = NULL;
+	gs_free char *loaded_path3 = NULL;
+	gboolean success;
+	gs_free char *filename = NULL;
+
+	g_assert (dirname && dirname[0] == '/');
+	g_assert (exp_full_filename && exp_full_filename[0]);
+	g_assert (!exp_loaded_path || exp_loaded_path[0] == '/');
+
+	filename = g_path_get_basename (exp_full_filename);
+
+	full_filename = nms_keyfile_loaded_uuid_filename (dirname, uuid, FALSE);
+	g_assert_cmpstr (full_filename, ==, full_filename);
+	nm_clear_g_free (&full_filename);
+
+
+	g_assert (nms_keyfile_loaded_uuid_write (dirname, uuid, loaded_path, allow_relative, &full_filename));
+	g_assert_cmpstr (full_filename, ==, exp_full_filename);
+	nm_clear_g_free (&full_filename);
+
+	if (exp_symlink_target)
+		g_assert (g_file_test (exp_full_filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_SYMLINK));
+	else
+		g_assert (!g_file_test (exp_full_filename, G_FILE_TEST_EXISTS));
+	symlink_target = g_file_read_link (exp_full_filename, NULL);
+	g_assert_cmpstr (symlink_target, ==, exp_symlink_target);
+
+
+	success = nms_keyfile_loaded_uuid_read (dirname, filename, &full_filename, &uuid2, &loaded_path2);
+	g_assert_cmpint (!!exp_uuid, ==, success);
+	if (success)
+		g_assert_cmpstr (full_filename, ==, exp_full_filename);
+	else
+		g_assert_cmpstr (full_filename, ==, NULL);
+	nm_clear_g_free (&full_filename);
+	g_assert_cmpstr (uuid2, ==, exp_uuid);
+	g_assert_cmpstr (loaded_path2, ==, exp_loaded_path);
+
+
+	success = nms_keyfile_loaded_uuid_read_from_file (exp_full_filename, &dirname3, &filename3, &uuid3, &loaded_path3);
+	g_assert_cmpint (!!exp_uuid, ==, success);
+	if (success) {
+		g_assert_cmpstr (dirname3, ==, dirname);
+		g_assert_cmpstr (filename3, ==, filename);
+	} else {
+		g_assert_cmpstr (dirname3, ==, NULL);
+		g_assert_cmpstr (filename3, ==, NULL);
+	}
+	g_assert_cmpstr (uuid3, ==, exp_uuid);
+	g_assert_cmpstr (loaded_path3, ==, exp_loaded_path);
+}
+
+static void
+test_loaded_uuid (void)
+{
+	const char *uuid = "3c03fd17-ddc3-4100-a954-88b6fafff959";
+	gs_free char *filename = g_strdup_printf ("%s%s%s",
+	                                          NM_KEYFILE_PATH_PREFIX_NMLOADED,
+	                                          uuid,
+	                                          NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
+	gs_free char *full_filename = g_strdup_printf ("%s/%s",
+	                                               TEST_SCRATCH_DIR,
+	                                               filename);
+	const char *loaded_path0 = NM_KEYFILE_PATH_NMLOADED_NULL;
+	const char *loaded_path1 = "/some/where/but/not/scratch/dir";
+	const char *filename2 = "foo1";
+	gs_free char *loaded_path2 = g_strdup_printf ("%s/%s",
+	                                              TEST_SCRATCH_DIR,
+	                                              filename2);
+
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, NULL,         FALSE, full_filename, NULL, NULL,         NULL);
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, NULL,         TRUE,  full_filename, NULL, NULL,         NULL);
+
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path0, FALSE, full_filename, uuid, loaded_path0, loaded_path0);
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path0, TRUE,  full_filename, uuid, loaded_path0, loaded_path0);
+
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path1, FALSE, full_filename, uuid, loaded_path1, loaded_path1);
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path1, TRUE,  full_filename, uuid, loaded_path1, loaded_path1);
+
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path2, FALSE, full_filename, uuid, loaded_path2, loaded_path2);
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path2, TRUE,  full_filename, uuid, filename2,    loaded_path2);
+
+	(void) unlink (full_filename);
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int main (int argc, char **argv)
 {
+	int errsv;
+
 	_nm_utils_set_testing (NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK);
+
 	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
 
-	if (g_mkdir_with_parents (TEST_SCRATCH_DIR, 0755) != 0)
-		g_error ("failure to create test directory \"%s\": %s", TEST_SCRATCH_DIR, g_strerror (errno));
+	if (g_mkdir_with_parents (TEST_SCRATCH_DIR, 0755) != 0) {
+		errsv = errno;
+		g_error ("failure to create test directory \"%s\": %s", TEST_SCRATCH_DIR, nm_strerror_native (errsv));
+	}
 
 	/* The tests */
 	g_test_add_func ("/keyfile/test_read_valid_wired_connection", test_read_valid_wired_connection);
@@ -2591,6 +2697,7 @@ int main (int argc, char **argv)
 
 	g_test_add_func ("/keyfile/test_nm_keyfile_plugin_utils_escape_filename", test_nm_keyfile_plugin_utils_escape_filename);
 
+	g_test_add_func ("/keyfile/test_loaded_uuid", test_loaded_uuid);
+
 	return g_test_run ();
 }
-
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index cfb33008..7708224b 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -23,9 +23,10 @@
 
 #include "nm-supplicant-config.h"
 
-#include <string.h>
 #include <stdlib.h>
 
+#include "nm-core-internal.h"
+
 #include "nm-supplicant-settings-verify.h"
 #include "nm-setting.h"
 #include "nm-auth-subject.h"
@@ -371,7 +372,6 @@ nm_supplicant_config_add_setting_macsec (NMSupplicantConfig * self,
                                          NMSettingMacsec * setting,
                                          GError **error)
 {
-	gs_unref_bytes GBytes *bytes = NULL;
 	const char *value;
 	char buf[32];
 	int port;
@@ -395,43 +395,50 @@ nm_supplicant_config_add_setting_macsec (NMSupplicantConfig * self,
 	}
 
 	if (nm_setting_macsec_get_mode (setting) == NM_SETTING_MACSEC_MODE_PSK) {
+		guint8 buffer_cak[NM_SETTING_MACSEC_MKA_CAK_LENGTH/2];
+		guint8 buffer_ckn[NM_SETTING_MACSEC_MKA_CKN_LENGTH/2];
+
 		if (!nm_supplicant_config_add_option (self, "key_mgmt", "NONE", -1, NULL, error))
 			return FALSE;
 
-		/* CAK */
 		value = nm_setting_macsec_get_mka_cak (setting);
-		if (!value) {
+		if (   !value
+		    || !nm_utils_hexstr2bin_buf (value,
+		                                 FALSE,
+		                                 FALSE,
+		                                 NULL,
+		                                 buffer_cak)) {
 			g_set_error_literal (error,
 			                     NM_SUPPLICANT_ERROR,
 			                     NM_SUPPLICANT_ERROR_CONFIG,
-			                     "missing MKA CAK");
+			                     value ? "invalid MKA CAK" : "missing MKA CAK");
 			return FALSE;
 		}
-
-		bytes = nm_utils_hexstr2bin (value);
 		if (!nm_supplicant_config_add_option (self,
 		                                      "mka_cak",
-		                                      g_bytes_get_data (bytes, NULL),
-		                                      g_bytes_get_size (bytes),
+		                                      (char *) buffer_cak,
+		                                      sizeof (buffer_cak),
 		                                      "<hidden>",
 		                                      error))
 			return FALSE;
 
-		/* CKN */
 		value = nm_setting_macsec_get_mka_ckn (setting);
-		if (!value) {
+		if (   !value
+		    || !nm_utils_hexstr2bin_buf (value,
+		                                 FALSE,
+		                                 FALSE,
+		                                 NULL,
+		                                 buffer_ckn)) {
 			g_set_error_literal (error,
 			                     NM_SUPPLICANT_ERROR,
 			                     NM_SUPPLICANT_ERROR_CONFIG,
-			                     "missing MKA CKN");
+			                     value ? "invalid MKA CKN" : "missing MKA CKN");
 			return FALSE;
 		}
-
-		bytes = nm_utils_hexstr2bin (value);
 		if (!nm_supplicant_config_add_option (self,
 		                                      "mka_ckn",
-		                                      g_bytes_get_data (bytes, NULL),
-		                                      g_bytes_get_size (bytes),
+		                                      (char *) buffer_ckn,
+		                                      sizeof (buffer_ckn),
 		                                      NULL,
 		                                      error))
 			return FALSE;
@@ -563,7 +570,7 @@ nm_supplicant_config_add_bgscan (NMSupplicantConfig *self,
 	                  NM_SETTING_WIRELESS_MODE_ADHOC))
 		return TRUE;
 
-	/* Don't scan when the connection is locked to a specifc AP, since
+	/* Don't scan when the connection is locked to a specific AP, since
 	 * intra-ESS roaming (which requires periodic scanning) isn't being
 	 * used due to the specific AP lock. (bgo #513820)
 	 */
@@ -695,10 +702,16 @@ add_wep_key (NMSupplicantConfig *self,
 	if (   (wep_type == NM_WEP_KEY_TYPE_UNKNOWN)
 	    || (wep_type == NM_WEP_KEY_TYPE_KEY)) {
 		if ((key_len == 10) || (key_len == 26)) {
-			gs_unref_bytes GBytes *bytes = NULL;
-
-			bytes = nm_utils_hexstr2bin (key);
-			if (!bytes) {
+			guint8 buffer[26/2];
+
+			if (!nm_utils_hexstr2bin_full (key,
+			                               FALSE,
+			                               FALSE,
+			                               NULL,
+			                               key_len / 2,
+			                               buffer,
+			                               sizeof (buffer),
+			                               NULL)) {
 				g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 				             "cannot add wep-key %s to suplicant config because key is not hex",
 				             name);
@@ -706,8 +719,8 @@ add_wep_key (NMSupplicantConfig *self,
 			}
 			if (!nm_supplicant_config_add_option (self,
 			                                      name,
-			                                      g_bytes_get_data (bytes, NULL),
-			                                      g_bytes_get_size (bytes),
+			                                      (char *) buffer,
+			                                      key_len / 2,
 			                                      "<hidden>",
 			                                      error))
 				return FALSE;
@@ -793,32 +806,41 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	if (psk) {
 		size_t psk_len = strlen (psk);
 
-		if (psk_len == 64) {
-			gs_unref_bytes GBytes *bytes = NULL;
+
+		if (psk_len >= 8 && psk_len <= 63) {
+			/* Use TYPE_STRING here so that it gets pushed to the
+			 * supplicant as a string, and therefore gets quoted,
+			 * and therefore the supplicant will interpret it as a
+			 * passphrase and not a hex key.
+			 */
+			if (!nm_supplicant_config_add_option_with_type (self, "psk", psk, -1, TYPE_STRING, "<hidden>", error))
+				return FALSE;
+		} else if (nm_streq (key_mgmt, "sae")) {
+			/* If the SAE password doesn't comply with WPA-PSK limitation,
+			 * we need to call it "sae_password" instead of "psk".
+			 */
+			if (!nm_supplicant_config_add_option_with_type (self, "sae_password", psk, -1, TYPE_STRING, "<hidden>", error))
+				return FALSE;
+		} else if (psk_len == 64) {
+			guint8 buffer[32];
 
 			/* Hex PSK */
-			bytes = nm_utils_hexstr2bin (psk);
-			if (!bytes) {
+			if (!nm_utils_hexstr2bin_buf (psk,
+			                              FALSE,
+			                              FALSE,
+			                              NULL,
+			                              buffer)) {
 				g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 				             "Cannot add psk to supplicant config due to invalid hex");
 				return FALSE;
 			}
-
 			if (!nm_supplicant_config_add_option (self,
 			                                      "psk",
-			                                      g_bytes_get_data (bytes, NULL),
-			                                      g_bytes_get_size (bytes),
+			                                      (char *) buffer,
+			                                      sizeof (buffer),
 			                                      "<hidden>",
 			                                      error))
 				return FALSE;
-		} else if (psk_len >= 8 && psk_len <= 63) {
-			/* Use TYPE_STRING here so that it gets pushed to the
-			 * supplicant as a string, and therefore gets quoted,
-			 * and therefore the supplicant will interpret it as a
-			 * passphrase and not a hex key.
-			 */
-			if (!nm_supplicant_config_add_option_with_type (self, "psk", psk, -1, TYPE_STRING, "<hidden>", error))
-				return FALSE;
 		} else {
 			g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 			             "Cannot add psk to supplicant config due to invalid PSK length %u (not between 8 and 63 characters)",
@@ -845,7 +867,8 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	/* Only WPA-specific things when using WPA */
 	if (   !strcmp (key_mgmt, "wpa-none")
 	    || !strcmp (key_mgmt, "wpa-psk")
-	    || !strcmp (key_mgmt, "wpa-eap")) {
+	    || !strcmp (key_mgmt, "wpa-eap")
+	    || !strcmp (key_mgmt, "sae")) {
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, proto, protos, "proto", ' ', TRUE, NULL, error))
 			return FALSE;
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, pairwise, pairwise, "pairwise", ' ', TRUE, NULL, error))
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 0af9ebdb..f46689eb 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -22,21 +22,24 @@
 #include "nm-default.h"
 
 #include "nm-supplicant-interface.h"
+#include "nm-supplicant-manager.h"
 
 #include <stdio.h>
-#include <string.h>
 
 #include "NetworkManagerUtils.h"
 #include "nm-supplicant-config.h"
 #include "nm-core-internal.h"
 #include "nm-dbus-compat.h"
 
-#define WPAS_DBUS_IFACE_INTERFACE       WPAS_DBUS_INTERFACE ".Interface"
-#define WPAS_DBUS_IFACE_INTERFACE_WPS   WPAS_DBUS_INTERFACE ".Interface.WPS"
-#define WPAS_DBUS_IFACE_BSS             WPAS_DBUS_INTERFACE ".BSS"
-#define WPAS_DBUS_IFACE_NETWORK         WPAS_DBUS_INTERFACE ".Network"
-#define WPAS_ERROR_INVALID_IFACE        WPAS_DBUS_INTERFACE ".InvalidInterface"
-#define WPAS_ERROR_EXISTS_ERROR         WPAS_DBUS_INTERFACE ".InterfaceExists"
+#define WPAS_DBUS_IFACE_INTERFACE             WPAS_DBUS_INTERFACE ".Interface"
+#define WPAS_DBUS_IFACE_INTERFACE_WPS         WPAS_DBUS_INTERFACE ".Interface.WPS"
+#define WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE  WPAS_DBUS_INTERFACE ".Interface.P2PDevice"
+#define WPAS_DBUS_IFACE_BSS                   WPAS_DBUS_INTERFACE ".BSS"
+#define WPAS_DBUS_IFACE_PEER                  WPAS_DBUS_INTERFACE ".Peer"
+#define WPAS_DBUS_IFACE_GROUP                 WPAS_DBUS_INTERFACE ".Group"
+#define WPAS_DBUS_IFACE_NETWORK               WPAS_DBUS_INTERFACE ".Network"
+#define WPAS_ERROR_INVALID_IFACE              WPAS_DBUS_INTERFACE ".InvalidInterface"
+#define WPAS_ERROR_EXISTS_ERROR               WPAS_DBUS_INTERFACE ".InterfaceExists"
 
 /*****************************************************************************/
 
@@ -45,6 +48,11 @@ typedef struct {
 	gulong change_id;
 } BssData;
 
+typedef struct {
+	GDBusProxy *proxy;
+	gulong change_id;
+} PeerData;
+
 struct _AddNetworkData;
 
 typedef struct {
@@ -74,26 +82,38 @@ typedef struct _AddNetworkData {
 } AddNetworkData;
 
 enum {
-	STATE,               /* change in the interface's state */
-	REMOVED,             /* interface was removed by the supplicant */
-	BSS_UPDATED,         /* a new BSS appeared or an existing had properties changed */
-	BSS_REMOVED,         /* supplicant removed BSS from its scan list */
-	SCAN_DONE,           /* wifi scan is complete */
-	CREDENTIALS_REQUEST, /* 802.1x identity or password requested */
-	WPS_CREDENTIALS,     /* WPS credentials received */
+	STATE,                   /* change in the interface's state */
+	REMOVED,                 /* interface was removed by the supplicant */
+	BSS_UPDATED,             /* a new BSS appeared or an existing had properties changed */
+	BSS_REMOVED,             /* supplicant removed BSS from its scan list */
+	PEER_UPDATED,            /* a new Peer appeared or an existing had properties changed */
+	PEER_REMOVED,            /* supplicant removed Peer from its scan list */
+	SCAN_DONE,               /* wifi scan is complete */
+	CREDENTIALS_REQUEST,     /* 802.1x identity or password requested */
+	WPS_CREDENTIALS,         /* WPS credentials received */
+	GROUP_STARTED,           /* a new Group (interface) was created */
+	GROUP_FINISHED,          /* a Group (interface) has been finished */
+	GROUP_FORMATION_FAILURE, /* P2P Group formation failed */
 	LAST_SIGNAL
 };
 static guint signals[LAST_SIGNAL] = { 0 };
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
 	PROP_IFACE,
+	PROP_OBJECT_PATH,
+	PROP_P2P_GROUP_JOINED,
+	PROP_P2P_GROUP_PATH,
+	PROP_P2P_GROUP_OWNER,
 	PROP_SCANNING,
 	PROP_CURRENT_BSS,
 	PROP_DRIVER,
+	PROP_P2P_AVAILABLE,
 	PROP_FAST_SUPPORT,
 	PROP_AP_SUPPORT,
 	PROP_PMF_SUPPORT,
 	PROP_FILS_SUPPORT,
+	PROP_P2P_SUPPORT,
+	PROP_WFD_SUPPORT,
 );
 
 typedef struct {
@@ -104,6 +124,8 @@ typedef struct {
 	NMSupplicantFeature ap_support;   /* Lightweight AP mode support */
 	NMSupplicantFeature pmf_support;
 	NMSupplicantFeature fils_support;
+	NMSupplicantFeature p2p_support;
+	NMSupplicantFeature wfd_support;
 	guint32        max_scan_ssids;
 	guint32        ready_count;
 
@@ -120,6 +142,14 @@ typedef struct {
 	GCancellable * init_cancellable;
 	GDBusProxy *   iface_proxy;
 	GCancellable * other_cancellable;
+	GDBusProxy *   p2p_proxy;
+	GDBusProxy *   group_proxy;
+
+	gboolean       p2p_proxy_acquired;
+	gboolean       group_proxy_acquired;
+	gboolean       p2p_capable;
+
+	gboolean       p2p_group_owner;
 
 	WpsData *wps_data;
 
@@ -129,6 +159,8 @@ typedef struct {
 	GHashTable *   bss_proxies;
 	char *         current_bss;
 
+	GHashTable *   peer_proxies;
+
 	gint64         last_scan; /* timestamp as returned by nm_utils_get_monotonic_timestamp_ms() */
 
 } NMSupplicantInterfacePrivate;
@@ -311,6 +343,122 @@ bss_add_new (NMSupplicantInterface *self, const char *object_path)
 	                             self);
 }
 
+static void
+peer_data_destroy (gpointer user_data)
+{
+	PeerData *peer_data = user_data;
+
+	nm_clear_g_signal_handler (peer_data->proxy, &peer_data->change_id);
+	g_object_unref (peer_data->proxy);
+	g_slice_free (PeerData, peer_data);
+}
+
+static void
+peer_proxy_properties_changed_cb (GDBusProxy *proxy,
+                                  GVariant *changed_properties,
+                                  char **invalidated_properties,
+                                  gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+
+	g_signal_emit (self, signals[PEER_UPDATED], 0,
+	               g_dbus_proxy_get_object_path (proxy),
+	               changed_properties);
+}
+
+static GVariant *
+peer_proxy_get_properties (NMSupplicantInterface *self, GDBusProxy *proxy)
+{
+	gs_strfreev char **properties = NULL;
+	GVariantBuilder builder;
+	char **iter;
+
+	iter = properties = g_dbus_proxy_get_cached_property_names (proxy);
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+	if (iter) {
+		while (*iter) {
+			GVariant *copy = g_dbus_proxy_get_cached_property (proxy, *iter);
+
+			g_variant_builder_add (&builder, "{sv}", *iter++, copy);
+			g_variant_unref (copy);
+		}
+	}
+	return g_variant_builder_end (&builder);
+}
+
+static void
+peer_proxy_acquired_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_free_error GError *error = NULL;
+	GVariant *props = NULL;
+	const char *object_path;
+	PeerData *peer_data;
+	gboolean success;
+
+	success = g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error);
+	if (   !success
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (!success) {
+		_LOGD ("failed to acquire Peer proxy: (%s)", error->message);
+		g_hash_table_remove (priv->peer_proxies,
+		                     g_dbus_proxy_get_object_path (proxy));
+		return;
+	}
+
+	object_path = g_dbus_proxy_get_object_path (proxy);
+	peer_data = g_hash_table_lookup (priv->peer_proxies, object_path);
+	if (!peer_data)
+		return;
+
+	peer_data->change_id = g_signal_connect (proxy, "g-properties-changed", G_CALLBACK (peer_proxy_properties_changed_cb), self);
+
+	props = peer_proxy_get_properties (self, proxy);
+
+	g_signal_emit (self, signals[PEER_UPDATED], 0,
+	               g_dbus_proxy_get_object_path (proxy),
+	               g_variant_ref_sink (props));
+	g_variant_unref (props);
+}
+
+static void
+peer_add_new (NMSupplicantInterface *self, const char *object_path)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	GDBusProxy *peer_proxy;
+	PeerData *peer_data;
+
+	g_return_if_fail (object_path != NULL);
+
+	if (g_hash_table_lookup (priv->peer_proxies, object_path))
+		return;
+
+	peer_proxy = g_object_new (G_TYPE_DBUS_PROXY,
+	                           "g-bus-type", G_BUS_TYPE_SYSTEM,
+	                           "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+	                           "g-name", WPAS_DBUS_SERVICE,
+	                           "g-object-path", object_path,
+	                           "g-interface-name", WPAS_DBUS_IFACE_PEER,
+	                           NULL);
+	peer_data = g_slice_new0 (PeerData);
+	peer_data->proxy = peer_proxy;
+	g_hash_table_insert (priv->peer_proxies,
+	                     (char *) g_dbus_proxy_get_object_path (peer_proxy),
+	                     peer_data);
+	g_async_initable_init_async (G_ASYNC_INITABLE (peer_proxy),
+	                             G_PRIORITY_DEFAULT,
+	                             priv->other_cancellable,
+	                             (GAsyncReadyCallback) peer_proxy_acquired_cb,
+	                             self);
+}
+
 /*****************************************************************************/
 
 static void
@@ -450,12 +598,24 @@ static void
 parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	gboolean have_active = FALSE, have_ssid = FALSE;
+	gboolean have_active = FALSE, have_p2p = FALSE, have_ssid = FALSE;
 	gint32 max_scan_ssids = -1;
 	const char **array;
 
 	g_return_if_fail (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT));
 
+	if (   g_variant_lookup (capabilities, "Modes", "^a&s", &array)
+	    && array) {
+		if (g_strv_contains (array, "p2p"))
+			have_p2p = TRUE;
+		g_free (array);
+	}
+
+	if (priv->p2p_capable != have_p2p) {
+		priv->p2p_capable = have_p2p;
+		_notify (self, PROP_P2P_AVAILABLE);
+	}
+
 	if (   g_variant_lookup (capabilities, "Scan", "^a&s", &array)
 	    && array) {
 		if (g_strv_contains (array, "active"))
@@ -568,13 +728,35 @@ iface_set_pmf_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
 
-	/* This can fail if the supplicant doesn't support PMF */
 	if (error)
-		_LOGD ("failed to set Pmf=1: %s", error->message);
+		_LOGW ("failed to set Pmf=1: %s", error->message);
 
 	iface_check_ready (self);
 }
 
+gboolean
+nm_supplicant_interface_get_p2p_group_joined (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->group_proxy_acquired;
+}
+
+const char*
+nm_supplicant_interface_get_p2p_group_path (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (priv->group_proxy_acquired)
+		return g_dbus_proxy_get_object_path (priv->group_proxy);
+	else
+		return NULL;
+}
+
+gboolean
+nm_supplicant_interface_get_p2p_group_owner (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_group_owner;
+}
+
 NMSupplicantFeature
 nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self)
 {
@@ -593,6 +775,18 @@ nm_supplicant_interface_get_fils_support (NMSupplicantInterface *self)
 	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->fils_support;
 }
 
+NMSupplicantFeature
+nm_supplicant_interface_get_p2p_support (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_support;
+}
+
+NMSupplicantFeature
+nm_supplicant_interface_get_wfd_support (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->wfd_support;
+}
+
 void
 nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                         NMSupplicantFeature ap_support)
@@ -633,6 +827,24 @@ nm_supplicant_interface_set_fils_support (NMSupplicantInterface *self,
 	priv->fils_support = fils_support;
 }
 
+void
+nm_supplicant_interface_set_p2p_support (NMSupplicantInterface *self,
+                                         NMSupplicantFeature p2p_support)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->p2p_support = p2p_support;
+}
+
+void
+nm_supplicant_interface_set_wfd_support (NMSupplicantInterface *self,
+                                         NMSupplicantFeature wfd_support)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->wfd_support = wfd_support;
+}
+
 /*****************************************************************************/
 
 static void
@@ -1120,10 +1332,260 @@ props_changed_cb (GDBusProxy *proxy,
 			_LOGW ("connection disconnected (reason %d)", priv->disconnect_reason);
 	}
 
+	/* We may not have priv->dev set yet if this interface was created from a
+	 * known wpa_supplicant interface without knowing the device name.
+	 */
+	if (priv->dev == NULL && g_variant_lookup (changed_properties, "Ifname", "&s", &s)) {
+		priv->dev = g_strdup (s);
+		_notify (self, PROP_IFACE);
+	}
+
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+group_props_changed_cb (GDBusProxy *proxy,
+                        GVariant *changed_properties,
+                        char **invalidated_properties,
+                        gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	char *s;
+
+	g_object_freeze_notify (G_OBJECT (self));
+
+#if 0
+	v = g_variant_lookup_value (properties, "BSSID", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		if (   len == ETH_ALEN
+		    && memcmp (bytes, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
+		    && memcmp (bytes, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0)
+			nm_wifi_p2p_group_set_bssid_bin (group, bytes);
+		g_variant_unref (v);
+	}
+
+	v = g_variant_lookup_value (properties, "SSID", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		len = MIN (32, len);
+
+		/* Stupid ieee80211 layer uses <hidden> */
+		if (   bytes && len
+		    && !(((len == 8) || (len == 9)) && !memcmp (bytes, "<hidden>", 8))
+		    && !nm_utils_is_empty_ssid (bytes, len))
+			nm_wifi_p2p_group_set_ssid (group, bytes, len);
+
+		g_variant_unref (v);
+	}
+#endif
+
+	if (g_variant_lookup (changed_properties, "Role", "s", &s)) {
+		priv->p2p_group_owner = g_strcmp0 (s, "GO") == 0;
+		_notify (self, PROP_P2P_GROUP_OWNER);
+		g_free (s);
+	}
+
+	/* NOTE: We do not seem to get any property change notifications for the Members
+	 *       property. However, we can keep track of these indirectly either by querying
+	 *       the groups that each peer is in or listening to the Join/Disconnect
+	 *       notifications.
+	 */
+
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+group_proxy_acquired_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_free_error GError *error = NULL;
+	gboolean success;
+
+	success = g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error);
+	if (   !success
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (!success) {
+		_LOGD ("failed to acquire Group proxy: (%s)", error->message);
+		g_clear_object (&priv->group_proxy);
+		return;
+	}
+
+	priv->group_proxy_acquired = TRUE;
+	_notify (self, PROP_P2P_GROUP_JOINED);
+	_notify (self, PROP_P2P_GROUP_PATH);
+
+	iface_check_ready (self);
+}
+
+static void
+p2p_props_changed_cb (GDBusProxy *proxy,
+                      GVariant *changed_properties,
+                      GStrv invalidated_properties,
+                      gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	const char **array, **iter;
+	const char *path = NULL;
+
+	g_object_freeze_notify (G_OBJECT (self));
+
+	if (g_variant_lookup (changed_properties, "Peers", "^a&o", &array)) {
+		iter = array;
+		while (*iter)
+			peer_add_new (self, *iter++);
+		g_free (array);
+	}
+
+	if (g_variant_lookup (changed_properties, "Group", "&o", &path)) {
+		if (priv->group_proxy && g_strcmp0 (path, g_dbus_proxy_get_object_path (priv->group_proxy)) == 0) {
+			/* We already have the proxy, nothing to do. */
+		} else if (path && g_strcmp0 (path, "/") != 0) {
+			if (priv->group_proxy != NULL) {
+				_LOGW ("P2P: Unexpected update of the group object path");
+				priv->group_proxy_acquired = FALSE;
+				_notify (self, PROP_P2P_GROUP_JOINED);
+				_notify (self, PROP_P2P_GROUP_PATH);
+				g_clear_object (&priv->group_proxy);
+			}
+
+			/* Delay ready state if we have not reached it yet. */
+			if (priv->ready_count)
+				priv->ready_count++;
+
+			priv->group_proxy = g_object_new (G_TYPE_DBUS_PROXY,
+			                                  "g-bus-type", G_BUS_TYPE_SYSTEM,
+			                                  "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+			                                  "g-name", WPAS_DBUS_SERVICE,
+			                                  "g-object-path", path,
+			                                  "g-interface-name", WPAS_DBUS_IFACE_GROUP,
+			                                  NULL);
+			g_signal_connect (priv->group_proxy, "g-properties-changed", G_CALLBACK (group_props_changed_cb), self);
+			g_async_initable_init_async (G_ASYNC_INITABLE (priv->group_proxy),
+			                             G_PRIORITY_DEFAULT,
+			                             priv->other_cancellable,
+			                             (GAsyncReadyCallback) group_proxy_acquired_cb,
+			                             self);
+		} else {
+			priv->group_proxy_acquired = FALSE;
+			_notify (self, PROP_P2P_GROUP_JOINED);
+			_notify (self, PROP_P2P_GROUP_PATH);
+			g_clear_object (&priv->group_proxy);
+		}
+	}
+
 	g_object_thaw_notify (G_OBJECT (self));
 }
 
 static void
+p2p_device_found (GDBusProxy *proxy,
+                  const char *path,
+                  gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+
+	peer_add_new (self, path);
+}
+
+static void
+p2p_device_lost (GDBusProxy *proxy,
+                 const char *path,
+                 gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	PeerData *peer_data;
+
+	peer_data = g_hash_table_lookup (priv->peer_proxies, path);
+	if (!peer_data)
+		return;
+	g_hash_table_steal (priv->peer_proxies, path);
+	g_signal_emit (self, signals[PEER_REMOVED], 0, path);
+	peer_data_destroy (peer_data);
+}
+
+static void
+p2p_group_started (GDBusProxy *proxy,
+                   GVariant *params,
+                   gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantInterface *iface = NULL;
+	char *group_path = NULL;
+	char *iface_path = NULL;
+
+	/* There is one more parameter: the role, but we don't really care about that here. */
+	if (!g_variant_lookup (params, "group_object", "&o", &group_path)) {
+		_LOGW ("P2P: GroupStarted signal is missing the \"group_object\" parameter");
+		return;
+	}
+
+	if (!g_variant_lookup (params, "interface_object", "&o", &iface_path)) {
+		_LOGW ("P2P: GroupStarted signal is missing the \"interface\" parameter");
+		return;
+	}
+
+	if (g_strcmp0 (iface_path, priv->object_path) == 0) {
+		_LOGW ("P2P: GroupStarted on existing interface");
+		iface = g_object_ref (self);
+	} else {
+		iface = nm_supplicant_manager_create_interface_from_path (nm_supplicant_manager_get (),
+		                                                          iface_path);
+		if (iface == NULL) {
+			_LOGW ("P2P: Group interface already exists in GroupStarted handler, aborting further processing.");
+			return;
+		}
+	}
+
+	/* Signal existence of the (new) interface. */
+	g_signal_emit (self, signals[GROUP_STARTED], 0, iface);
+	g_object_unref (iface);
+}
+
+static void
+p2p_group_formation_failure (GDBusProxy *proxy,
+                             const char *group,
+                             gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+
+	g_signal_emit (self, signals[GROUP_FORMATION_FAILURE], 0, group);
+}
+
+static void
+p2p_group_finished (GDBusProxy *proxy,
+                    GVariant *params,
+                    gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	const char *iface_path = NULL;
+	/* TODO: Group finished is called on the management interface!
+	 *       This means the signal consumer will currently need to assume which
+	 *       interface is finishing or it needs to match the object paths.
+	 */
+
+	if (!g_variant_lookup (params, "interface_object", "&o", &iface_path)) {
+		_LOGW ("P2P: GroupFinished signal is missing the \"interface\" parameter");
+		return;
+	}
+
+	_LOGD ("P2P: GroupFinished signal on interface %s for interface %s", priv->object_path, iface_path);
+
+	/* Signal group finish interface (on management interface). */
+	g_signal_emit (self, signals[GROUP_FINISHED], 0, iface_path);
+}
+
+static void
 on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
@@ -1175,22 +1637,9 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	                   NULL,
 	                   NULL);
 
-	/* Initialize global PMF setting to 'optional' */
-	priv->ready_count = 1;
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   DBUS_INTERFACE_PROPERTIES ".Set",
-	                   g_variant_new ("(ssv)",
-	                                  WPAS_DBUS_IFACE_INTERFACE,
-	                                  "Pmf",
-	                                  g_variant_new_string ("1")),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->init_cancellable,
-	                   (GAsyncReadyCallback) iface_set_pmf_cb,
-	                   self);
-
-	/* Check whether NetworkReply and AP mode are supported */
-	priv->ready_count++;
+	/* Check whether NetworkReply and AP mode are supported.
+	 * ready_count was initialized to 1 in interface_add_done().
+	 */
 	g_dbus_proxy_call (priv->iface_proxy,
 	                   "NetworkReply",
 	                   g_variant_new ("(oss)",
@@ -1203,6 +1652,22 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	                   (GAsyncReadyCallback) iface_check_netreply_cb,
 	                   self);
 
+	if (priv->pmf_support == NM_SUPPLICANT_FEATURE_YES) {
+		/* Initialize global PMF setting to 'optional' */
+		priv->ready_count++;
+		g_dbus_proxy_call (priv->iface_proxy,
+		                   DBUS_INTERFACE_PROPERTIES ".Set",
+		                   g_variant_new ("(ssv)",
+		                                  WPAS_DBUS_IFACE_INTERFACE,
+		                                  "Pmf",
+		                                  g_variant_new_string ("1")),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->init_cancellable,
+		                   (GAsyncReadyCallback) iface_set_pmf_cb,
+		                   self);
+	}
+
 	if (priv->ap_support == NM_SUPPLICANT_FEATURE_UNKNOWN) {
 		/* If the global supplicant capabilities property is not present, we can
 		 * fall back to checking whether the ProbeRequest method is supported.  If
@@ -1222,13 +1687,63 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 }
 
 static void
+on_p2p_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_free_error GError *error = NULL;
+
+	if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			self = NM_SUPPLICANT_INTERFACE (user_data);
+			priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+			_LOGW ("failed to acquire wpa_supplicant p2p proxy: (%s)", error->message);
+
+			g_clear_object (&priv->p2p_proxy);
+
+			iface_check_ready (self);
+		}
+		return;
+	}
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	_nm_dbus_signal_connect (priv->p2p_proxy, "DeviceFound", G_VARIANT_TYPE ("(o)"),
+	                         G_CALLBACK (p2p_device_found), self);
+	_nm_dbus_signal_connect (priv->p2p_proxy, "DeviceLost", G_VARIANT_TYPE ("(o)"),
+	                         G_CALLBACK (p2p_device_lost), self);
+	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupStarted", G_VARIANT_TYPE ("(a{sv})"),
+	                         G_CALLBACK (p2p_group_started), self);
+	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupFormationFailure", G_VARIANT_TYPE ("(s)"),
+	                         G_CALLBACK (p2p_group_formation_failure), self);
+	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupFinished", G_VARIANT_TYPE ("(a{sv})"),
+	                         G_CALLBACK (p2p_group_finished), self);
+	/* TODO:
+	 *  * WpsFailed
+	 *  * FindStopped
+	 *  * GONegotationFailure
+	 *  * InvitationReceived
+	 */
+
+	priv->p2p_proxy_acquired = TRUE;
+	_notify (self, PROP_P2P_AVAILABLE);
+
+	iface_check_ready (self);
+}
+
+static void
 interface_add_done (NMSupplicantInterface *self, const char *path)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
 	_LOGD ("interface added to supplicant");
 
+	/* Iface ready check happens in iface_check_netreply_cb */
+	priv->ready_count = 1;
+
 	priv->object_path = g_strdup (path);
+	_notify (self, PROP_OBJECT_PATH);
 	priv->iface_proxy = g_object_new (G_TYPE_DBUS_PROXY,
 	                                  "g-bus-type", G_BUS_TYPE_SYSTEM,
 	                                  "g-flags", G_DBUS_PROXY_FLAGS_NONE,
@@ -1242,6 +1757,23 @@ interface_add_done (NMSupplicantInterface *self, const char *path)
 	                             priv->init_cancellable,
 	                             (GAsyncReadyCallback) on_iface_proxy_acquired,
 	                             self);
+
+	if (priv->p2p_support == NM_SUPPLICANT_FEATURE_YES) {
+		priv->ready_count++;
+		priv->p2p_proxy = g_object_new (G_TYPE_DBUS_PROXY,
+		                                "g-bus-type", G_BUS_TYPE_SYSTEM,
+		                                "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+		                                "g-name", WPAS_DBUS_SERVICE,
+		                                "g-object-path", priv->object_path,
+		                                "g-interface-name", WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+		                                NULL);
+		g_signal_connect (priv->p2p_proxy, "g-properties-changed", G_CALLBACK (p2p_props_changed_cb), self);
+		g_async_initable_init_async (G_ASYNC_INITABLE (priv->p2p_proxy),
+		                             G_PRIORITY_DEFAULT,
+		                             priv->init_cancellable,
+		                             (GAsyncReadyCallback) on_p2p_proxy_acquired,
+		                             self);
+	}
 }
 
 static void
@@ -1323,6 +1855,39 @@ interface_add_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 	}
 }
 
+static void
+interface_removed_cb (GDBusProxy *proxy,
+                      const char *path,
+                      gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (g_strcmp0 (priv->object_path, path) != 0)
+		return;
+
+	_LOGD ("Received interface removed signal");
+
+	/* The interface may lose its last reference during signal handling otherwise. */
+	g_object_ref (self);
+
+	/* Invalidate the object path to prevent the manager from trying to remove
+	 * a non-existing interface. */
+	g_clear_pointer (&priv->object_path, g_free);
+	_notify (self, PROP_OBJECT_PATH);
+
+	/* No need to clean up everything now, that will happen at dispose time. */
+
+	/* Interface is down and has been removed. */
+	set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+	g_signal_emit (self, signals[REMOVED], 0);
+
+	g_object_unref (self);
+}
+
 #if HAVE_WEXT
 #define DEFAULT_WIFI_DRIVER "nl80211,wext"
 #else
@@ -1337,7 +1902,6 @@ on_wpas_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_d
 	gs_free_error GError *error = NULL;
 	GDBusProxy *wpas_proxy;
 	GVariantBuilder props;
-	const char *driver_name = NULL;
 
 	wpas_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
 	if (!wpas_proxy) {
@@ -1354,41 +1918,53 @@ on_wpas_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_d
 
 	priv->wpas_proxy = wpas_proxy;
 
+	/* Watch for interface removal. */
+	_nm_dbus_signal_connect (priv->wpas_proxy, "InterfaceRemoved", G_VARIANT_TYPE ("(o)"),
+	                         G_CALLBACK (interface_removed_cb), self);
+
 	/* Try to add the interface to the supplicant.  If the supplicant isn't
 	 * running, this will start it via D-Bus activation and return the response
 	 * when the supplicant has started.
 	 */
 
-	switch (priv->driver) {
-	case NM_SUPPLICANT_DRIVER_WIRELESS:
-		driver_name = DEFAULT_WIFI_DRIVER;
-		break;
-	case NM_SUPPLICANT_DRIVER_WIRED:
-		driver_name = "wired";
-		break;
-	case NM_SUPPLICANT_DRIVER_MACSEC:
-		driver_name = "macsec_linux";
-		break;
-	}
+	if (priv->dev != NULL) {
+		const char *driver_name = NULL;
+
+		switch (priv->driver) {
+		case NM_SUPPLICANT_DRIVER_WIRELESS:
+			driver_name = DEFAULT_WIFI_DRIVER;
+			break;
+		case NM_SUPPLICANT_DRIVER_WIRED:
+			driver_name = "wired";
+			break;
+		case NM_SUPPLICANT_DRIVER_MACSEC:
+			driver_name = "macsec_linux";
+			break;
+		}
 
-	g_return_if_fail (driver_name);
+		g_return_if_fail (driver_name);
 
-	g_variant_builder_init (&props, G_VARIANT_TYPE_VARDICT);
-	g_variant_builder_add (&props, "{sv}",
-	                       "Driver",
-	                       g_variant_new_string (driver_name));
-	g_variant_builder_add (&props, "{sv}",
-	                       "Ifname",
-	                       g_variant_new_string (priv->dev));
+		g_variant_builder_init (&props, G_VARIANT_TYPE_VARDICT);
+		g_variant_builder_add (&props, "{sv}",
+		                       "Driver",
+		                       g_variant_new_string (driver_name));
+		g_variant_builder_add (&props, "{sv}",
+		                       "Ifname",
+		                       g_variant_new_string (priv->dev));
 
-	g_dbus_proxy_call (priv->wpas_proxy,
-	                   "CreateInterface",
-	                   g_variant_new ("(a{sv})", &props),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->init_cancellable,
-	                   (GAsyncReadyCallback) interface_add_cb,
-	                   self);
+		g_dbus_proxy_call (priv->wpas_proxy,
+		                   "CreateInterface",
+		                   g_variant_new ("(a{sv})", &props),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->init_cancellable,
+		                   (GAsyncReadyCallback) interface_add_cb,
+		                   self);
+	} else if (priv->object_path) {
+		interface_add_done (self, priv->object_path);
+	} else {
+		g_assert_not_reached ();
+	}
 }
 
 static void
@@ -1408,8 +1984,7 @@ interface_add (NMSupplicantInterface *self)
 	priv->init_cancellable = g_cancellable_new ();
 
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
-	                            G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
 	                          NULL,
 	                          WPAS_DBUS_SERVICE,
 	                          WPAS_DBUS_PATH,
@@ -1897,6 +2472,141 @@ nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self)
 
 /*****************************************************************************/
 
+void
+nm_supplicant_interface_p2p_start_find (NMSupplicantInterface *self,
+                                        guint timeout)
+{
+	NMSupplicantInterfacePrivate *priv;
+	GVariantBuilder builder;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+	g_return_if_fail (timeout > 0 && timeout <= 600);
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add (&builder, "{sv}", "Timeout", g_variant_new_int32 (timeout));
+
+	g_dbus_proxy_call (priv->p2p_proxy,
+	                   "Find",
+	                   g_variant_new ("(a{sv})", &builder),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->other_cancellable,
+	                   (GAsyncReadyCallback) log_result_cb,
+	                   self);
+}
+
+void
+nm_supplicant_interface_p2p_stop_find (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	g_dbus_proxy_call (priv->p2p_proxy,
+	                   "StopFind",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->other_cancellable,
+	                   (GAsyncReadyCallback) scan_request_cb,
+	                   self);
+}
+
+/*****************************************************************************/
+
+void
+nm_supplicant_interface_p2p_connect (NMSupplicantInterface * self,
+                                     const char * peer,
+                                     const char * wps_method,
+                                     const char * wps_pin)
+{
+	NMSupplicantInterfacePrivate *priv;
+	GVariantBuilder builder;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	/* Don't do anything if there is no connection to the supplicant yet. */
+	if (!priv->p2p_proxy || !priv->object_path)
+		return;
+
+	/* Connect parameters */
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+
+	g_variant_builder_add (&builder, "{sv}", "wps_method", g_variant_new_string (wps_method));
+
+	if (wps_pin)
+		g_variant_builder_add (&builder, "{sv}", "pin", g_variant_new_string (wps_pin));
+
+	g_variant_builder_add (&builder, "{sv}", "peer", g_variant_new_object_path (peer));
+
+	g_variant_builder_add (&builder, "{sv}", "join", g_variant_new_boolean (FALSE));
+	g_variant_builder_add (&builder, "{sv}", "persistent", g_variant_new_boolean (FALSE));
+	g_variant_builder_add (&builder, "{sv}", "go_intent", g_variant_new_int32 (7));
+
+	g_dbus_proxy_call (priv->p2p_proxy,
+	                   "Connect",
+	                   g_variant_new ("(a{sv})", &builder),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->other_cancellable,
+	                   (GAsyncReadyCallback) log_result_cb,
+	                   "p2p connect");
+}
+
+void
+nm_supplicant_interface_p2p_cancel_connect (NMSupplicantInterface * self)
+{
+	NMSupplicantInterfacePrivate *priv;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	/* Don't do anything if there is no connection to the supplicant yet. */
+	if (!priv->p2p_proxy || !priv->object_path)
+		return;
+
+	g_dbus_proxy_call (priv->p2p_proxy,
+	                   "Cancel",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->other_cancellable,
+	                   (GAsyncReadyCallback) log_result_cb,
+	                   "cancel p2p connect");
+}
+
+void
+nm_supplicant_interface_p2p_disconnect (NMSupplicantInterface * self)
+{
+	NMSupplicantInterfacePrivate *priv;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	/* Don't do anything if there is no connection to the supplicant. */
+	if (!priv->p2p_proxy || !priv->object_path)
+		return;
+
+	g_dbus_proxy_call (priv->p2p_proxy,
+	                   "Disconnect",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->other_cancellable,
+	                   (GAsyncReadyCallback) log_result_cb,
+	                   "p2p disconnect");
+}
+
+/*****************************************************************************/
+
 static void
 get_property (GObject *object,
               guint prop_id,
@@ -1912,6 +2622,18 @@ get_property (GObject *object,
 	case PROP_CURRENT_BSS:
 		g_value_set_string (value, priv->current_bss);
 		break;
+	case PROP_P2P_GROUP_JOINED:
+		g_value_set_boolean (value, priv->p2p_capable && priv->group_proxy_acquired);
+		break;
+	case PROP_P2P_GROUP_PATH:
+		g_value_set_string (value, nm_supplicant_interface_get_p2p_group_path (NM_SUPPLICANT_INTERFACE (object)));
+		break;
+	case PROP_P2P_GROUP_OWNER:
+		g_value_set_boolean (value, priv->p2p_group_owner);
+		break;
+	case PROP_P2P_AVAILABLE:
+		g_value_set_boolean (value, priv->p2p_capable && priv->p2p_proxy_acquired);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1930,7 +2652,10 @@ set_property (GObject *object,
 	case PROP_IFACE:
 		/* construct-only */
 		priv->dev = g_value_dup_string (value);
-		g_return_if_fail (priv->dev);
+		break;
+	case PROP_OBJECT_PATH:
+		/* construct-only */
+		priv->object_path = g_value_dup_string (value);
 		break;
 	case PROP_DRIVER:
 		/* construct-only */
@@ -1952,6 +2677,14 @@ set_property (GObject *object,
 		/* construct-only */
 		priv->fils_support = g_value_get_int (value);
 		break;
+	case PROP_P2P_SUPPORT:
+		/* construct-only */
+		priv->p2p_support = g_value_get_int (value);
+		break;
+	case PROP_WFD_SUPPORT:
+		/* construct-only */
+		priv->wfd_support = g_value_get_int (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1965,25 +2698,34 @@ nm_supplicant_interface_init (NMSupplicantInterface * self)
 
 	priv->state = NM_SUPPLICANT_INTERFACE_STATE_INIT;
 	priv->bss_proxies = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, bss_data_destroy);
+	priv->peer_proxies = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, peer_data_destroy);
 }
 
 NMSupplicantInterface *
 nm_supplicant_interface_new (const char *ifname,
+                             const char *object_path,
                              NMSupplicantDriver driver,
                              NMSupplicantFeature fast_support,
                              NMSupplicantFeature ap_support,
                              NMSupplicantFeature pmf_support,
-                             NMSupplicantFeature fils_support)
+                             NMSupplicantFeature fils_support,
+                             NMSupplicantFeature p2p_support,
+                             NMSupplicantFeature wfd_support)
 {
-	g_return_val_if_fail (ifname != NULL, NULL);
+	/* One of ifname or path need to be set */
+	g_return_val_if_fail (ifname != NULL || object_path != NULL, NULL);
+	g_return_val_if_fail (ifname == NULL || object_path == NULL, NULL);
 
 	return g_object_new (NM_TYPE_SUPPLICANT_INTERFACE,
 	                     NM_SUPPLICANT_INTERFACE_IFACE, ifname,
+	                     NM_SUPPLICANT_INTERFACE_OBJECT_PATH, object_path,
 	                     NM_SUPPLICANT_INTERFACE_DRIVER, (guint) driver,
 	                     NM_SUPPLICANT_INTERFACE_FAST_SUPPORT, (int) fast_support,
 	                     NM_SUPPLICANT_INTERFACE_AP_SUPPORT, (int) ap_support,
 	                     NM_SUPPLICANT_INTERFACE_PMF_SUPPORT, (int) pmf_support,
 	                     NM_SUPPLICANT_INTERFACE_FILS_SUPPORT, (int) fils_support,
+	                     NM_SUPPLICANT_INTERFACE_P2P_SUPPORT, (int) p2p_support,
+	                     NM_SUPPLICANT_INTERFACE_WFD_SUPPORT, (int) wfd_support,
 	                     NULL);
 }
 
@@ -2013,12 +2755,21 @@ dispose (GObject *object)
 	if (priv->iface_proxy)
 		g_signal_handlers_disconnect_by_data (priv->iface_proxy, object);
 	g_clear_object (&priv->iface_proxy);
+	if (priv->p2p_proxy)
+		g_signal_handlers_disconnect_by_data (priv->p2p_proxy, object);
+	g_clear_object (&priv->p2p_proxy);
+	if (priv->group_proxy)
+		g_signal_handlers_disconnect_by_data (priv->group_proxy, object);
+	g_clear_object (&priv->group_proxy);
 
 	nm_clear_g_cancellable (&priv->init_cancellable);
 	nm_clear_g_cancellable (&priv->other_cancellable);
 
+	if (priv->wpas_proxy)
+		g_signal_handlers_disconnect_by_data (priv->wpas_proxy, object);
 	g_clear_object (&priv->wpas_proxy);
 	g_clear_pointer (&priv->bss_proxies, g_hash_table_destroy);
+	g_clear_pointer (&priv->peer_proxies, g_hash_table_destroy);
 
 	g_clear_pointer (&priv->net_path, g_free);
 	g_clear_pointer (&priv->dev, g_free);
@@ -2053,12 +2804,38 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                         G_PARAM_WRITABLE |
 	                         G_PARAM_CONSTRUCT_ONLY |
 	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_OBJECT_PATH] =
+	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_OBJECT_PATH, "", "",
+	                         NULL,
+	                         G_PARAM_WRITABLE |
+	                         G_PARAM_CONSTRUCT_ONLY |
+	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_P2P_GROUP_JOINED] =
+	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED, "", "",
+	                          FALSE,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_P2P_GROUP_PATH] =
+	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_P2P_GROUP_PATH, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_P2P_GROUP_OWNER] =
+	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_P2P_GROUP_OWNER, "", "",
+	                          FALSE,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_DRIVER] =
 	    g_param_spec_uint (NM_SUPPLICANT_INTERFACE_DRIVER, "", "",
 	                       0, G_MAXUINT, NM_SUPPLICANT_DRIVER_WIRELESS,
 	                       G_PARAM_WRITABLE |
 	                       G_PARAM_CONSTRUCT_ONLY |
 	                       G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_P2P_AVAILABLE] =
+	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, "", "",
+	                          FALSE,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_FAST_SUPPORT] =
 	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_FAST_SUPPORT, "", "",
 	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
@@ -2091,6 +2868,22 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                      G_PARAM_WRITABLE |
 	                      G_PARAM_CONSTRUCT_ONLY |
 	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_P2P_SUPPORT] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_P2P_SUPPORT, "", "",
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      NM_SUPPLICANT_FEATURE_YES,
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_WFD_SUPPORT] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_WFD_SUPPORT, "", "",
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      NM_SUPPLICANT_FEATURE_YES,
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
@@ -2126,6 +2919,22 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_STRING);
 
+	signals[PEER_UPDATED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_PEER_UPDATED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);
+
+	signals[PEER_REMOVED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_PEER_REMOVED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, G_TYPE_STRING);
+
 	signals[SCAN_DONE] =
 	    g_signal_new (NM_SUPPLICANT_INTERFACE_SCAN_DONE,
 	                  G_OBJECT_CLASS_TYPE (object_class),
@@ -2149,4 +2958,28 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                  0,
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_VARIANT);
+
+	signals[GROUP_STARTED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_GROUP_STARTED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, NM_TYPE_SUPPLICANT_INTERFACE);
+
+	signals[GROUP_FINISHED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_GROUP_FINISHED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	signals[GROUP_FORMATION_FAILURE] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_GROUP_FORMATION_FAILURE,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, G_TYPE_VARIANT);
 }
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h
index 0365fdcd..0aa7732a 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/supplicant/nm-supplicant-interface.h
@@ -55,33 +55,48 @@ typedef enum {
 
 /* Properties */
 #define NM_SUPPLICANT_INTERFACE_IFACE            "iface"
+#define NM_SUPPLICANT_INTERFACE_OBJECT_PATH      "object-path"
 #define NM_SUPPLICANT_INTERFACE_SCANNING         "scanning"
 #define NM_SUPPLICANT_INTERFACE_CURRENT_BSS      "current-bss"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED "p2p-group-joined"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_PATH   "p2p-group-path"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_OWNER  "p2p-group-owner"
 #define NM_SUPPLICANT_INTERFACE_DRIVER           "driver"
+#define NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE    "p2p-available"
 #define NM_SUPPLICANT_INTERFACE_FAST_SUPPORT     "fast-support"
 #define NM_SUPPLICANT_INTERFACE_AP_SUPPORT       "ap-support"
 #define NM_SUPPLICANT_INTERFACE_PMF_SUPPORT      "pmf-support"
 #define NM_SUPPLICANT_INTERFACE_FILS_SUPPORT     "fils-support"
+#define NM_SUPPLICANT_INTERFACE_P2P_SUPPORT      "p2p-support"
+#define NM_SUPPLICANT_INTERFACE_WFD_SUPPORT      "wfd-support"
 
 /* Signals */
 #define NM_SUPPLICANT_INTERFACE_STATE            "state"
 #define NM_SUPPLICANT_INTERFACE_REMOVED          "removed"
 #define NM_SUPPLICANT_INTERFACE_BSS_UPDATED      "bss-updated"
 #define NM_SUPPLICANT_INTERFACE_BSS_REMOVED      "bss-removed"
+#define NM_SUPPLICANT_INTERFACE_PEER_UPDATED     "peer-updated"
+#define NM_SUPPLICANT_INTERFACE_PEER_REMOVED     "peer-removed"
 #define NM_SUPPLICANT_INTERFACE_SCAN_DONE        "scan-done"
 #define NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST "credentials-request"
 #define NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS  "wps-credentials"
+#define NM_SUPPLICANT_INTERFACE_GROUP_STARTED           "group-started"
+#define NM_SUPPLICANT_INTERFACE_GROUP_FINISHED          "group-finished"
+#define NM_SUPPLICANT_INTERFACE_GROUP_FORMATION_FAILURE "group-formation-failure"
 
 typedef struct _NMSupplicantInterfaceClass NMSupplicantInterfaceClass;
 
 GType nm_supplicant_interface_get_type (void);
 
 NMSupplicantInterface * nm_supplicant_interface_new (const char *ifname,
+                                                     const char *object_path,
                                                      NMSupplicantDriver driver,
                                                      NMSupplicantFeature fast_support,
                                                      NMSupplicantFeature ap_support,
                                                      NMSupplicantFeature pmf_support,
-                                                     NMSupplicantFeature fils_support);
+                                                     NMSupplicantFeature fils_support,
+                                                     NMSupplicantFeature p2p_support,
+                                                     NMSupplicantFeature wfd_support);
 
 void nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
                                                        gboolean available);
@@ -120,14 +135,33 @@ guint nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self);
 
 gboolean nm_supplicant_interface_get_has_credentials_request (NMSupplicantInterface *self);
 
+gboolean nm_supplicant_interface_get_p2p_group_joined (NMSupplicantInterface *self);
+
+const char* nm_supplicant_interface_get_p2p_group_path (NMSupplicantInterface *self);
+
+gboolean nm_supplicant_interface_get_p2p_group_owner (NMSupplicantInterface *self);
+
 gboolean nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self,
                                                     const char *field,
                                                     const char *value,
                                                     GError **error);
 
+void nm_supplicant_interface_p2p_start_find (NMSupplicantInterface *self,
+                                             guint timeout);
+void nm_supplicant_interface_p2p_stop_find (NMSupplicantInterface *self);
+
+void nm_supplicant_interface_p2p_connect (NMSupplicantInterface * self,
+                                          const char * peer,
+                                          const char * wps_method,
+                                          const char * wps_pin);
+void nm_supplicant_interface_p2p_cancel_connect (NMSupplicantInterface * self);
+void nm_supplicant_interface_p2p_disconnect (NMSupplicantInterface * self);
+
 NMSupplicantFeature nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self);
 NMSupplicantFeature nm_supplicant_interface_get_pmf_support (NMSupplicantInterface *self);
 NMSupplicantFeature nm_supplicant_interface_get_fils_support (NMSupplicantInterface *self);
+NMSupplicantFeature nm_supplicant_interface_get_p2p_support (NMSupplicantInterface *self);
+NMSupplicantFeature nm_supplicant_interface_get_wfd_support (NMSupplicantInterface *self);
 
 void nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                              NMSupplicantFeature apmode);
@@ -141,6 +175,12 @@ void nm_supplicant_interface_set_pmf_support (NMSupplicantInterface *self,
 void nm_supplicant_interface_set_fils_support (NMSupplicantInterface *self,
                                                NMSupplicantFeature fils_support);
 
+void nm_supplicant_interface_set_p2p_support (NMSupplicantInterface *self,
+                                              NMSupplicantFeature p2p_support);
+
+void nm_supplicant_interface_set_wfd_support (NMSupplicantInterface *self,
+                                              NMSupplicantFeature wfd_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 5ab96f88..104aeee7 100644
--- a/src/supplicant/nm-supplicant-manager.c
+++ b/src/supplicant/nm-supplicant-manager.c
@@ -23,8 +23,6 @@
 
 #include "nm-supplicant-manager.h"
 
-#include <string.h>
-
 #include "nm-supplicant-interface.h"
 #include "nm-supplicant-types.h"
 #include "nm-core-internal.h"
@@ -41,6 +39,8 @@ typedef struct {
 	NMSupplicantFeature ap_support;
 	NMSupplicantFeature pmf_support;
 	NMSupplicantFeature fils_support;
+	NMSupplicantFeature p2p_support;
+	NMSupplicantFeature wfd_support;
 	guint             die_count_reset_id;
 	guint             die_count;
 } NMSupplicantManagerPrivate;
@@ -69,7 +69,7 @@ NM_CACHED_QUARK_FCN ("nm-supplicant-error-quark", nm_supplicant_error_quark)
 
 /*****************************************************************************/
 
-static inline gboolean
+static gboolean
 die_count_exceeded (guint32 count)
 {
 	return count > 2;
@@ -123,6 +123,72 @@ _sup_iface_last_ref (gpointer data,
 	g_object_remove_toggle_ref ((GObject *) sup_iface, _sup_iface_last_ref, self);
 }
 
+static void
+on_supplicant_wfd_ies_set (GObject *source_object,
+                           GAsyncResult *res,
+                           gpointer user_data)
+{
+	gs_unref_variant GVariant *result = NULL;
+	gs_free_error GError *error = NULL;
+
+	result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
+
+	if (!result)
+		_LOGW ("failed to set WFD IEs on wpa_supplicant: %s", error->message);
+}
+
+/**
+ * nm_supplicant_manager_set_wfd_ies:
+ * @self: the #NMSupplicantManager
+ * @wfd_ies: a #GBytes with the WFD IEs or %NULL
+ *
+ * This function sets the global WFD IEs on wpa_supplicant. Note that
+ * it would make more sense if this was per-device, but wpa_supplicant
+ * simply does not work that way.
+ * */
+void
+nm_supplicant_manager_set_wfd_ies (NMSupplicantManager *self,
+                                   GBytes *wfd_ies)
+{
+	NMSupplicantManagerPrivate *priv;
+	GVariantBuilder params;
+	GVariant *val;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (self));
+
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	_LOGD ("setting WFD IEs for P2P operation");
+
+	if (wfd_ies)
+		val = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                 g_bytes_get_data (wfd_ies, NULL),
+		                                 g_bytes_get_size (wfd_ies),
+		                                 sizeof (guint8));
+	else
+		val = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                 NULL, 0, sizeof (guint8));
+
+	g_variant_builder_init (&params, G_VARIANT_TYPE ("(ssv)"));
+
+	g_variant_builder_add (&params, "s", g_dbus_proxy_get_interface_name (priv->proxy));
+	g_variant_builder_add (&params, "s", "WFDIEs");
+	g_variant_builder_add_value (&params, g_variant_new_variant (val));
+
+	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
+	                        g_dbus_proxy_get_name (priv->proxy),
+	                        g_dbus_proxy_get_object_path (priv->proxy),
+	                        "org.freedesktop.DBus.Properties",
+	                        "Set",
+	                        g_variant_builder_end (&params),
+	                        G_VARIANT_TYPE_UNIT,
+	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                        1000,
+	                        NULL,
+	                        on_supplicant_wfd_ies_set,
+	                        NULL);
+}
+
 /**
  * nm_supplicant_manager_create_interface:
  * @self: the #NMSupplicantManager
@@ -159,11 +225,71 @@ nm_supplicant_manager_create_interface (NMSupplicantManager *self,
 	}
 
 	iface = nm_supplicant_interface_new (ifname,
+	                                     NULL,
 	                                     driver,
 	                                     priv->fast_support,
 	                                     priv->ap_support,
 	                                     priv->pmf_support,
-	                                     priv->fils_support);
+	                                     priv->fils_support,
+	                                     priv->p2p_support,
+	                                     priv->wfd_support);
+
+	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
+	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
+
+	/* If we're making the supplicant take a time out for a bit, don't
+	 * let the supplicant interface start immediately, just let it hang
+	 * around in INIT state until we're ready to talk to the supplicant
+	 * again.
+	 */
+	if (is_available (self))
+		nm_supplicant_interface_set_supplicant_available (iface, TRUE);
+
+	return iface;
+}
+
+/**
+ * nm_supplicant_manager_create_interface_from_path:
+ * @self: the #NMSupplicantManager
+ * @object_path: the DBus object path for which to obtain the supplicant interface
+ *
+ * Note: the manager owns a reference to the instance and the only way to
+ *   get the manager to release it, is by dropping all other references
+ *   to the supplicant-interface (or destroying the manager).
+ *
+ * Returns: (transfer full): returns a #NMSupplicantInterface or %NULL.
+ *   Must be unrefed at the end.
+ * */
+NMSupplicantInterface *
+nm_supplicant_manager_create_interface_from_path (NMSupplicantManager *self,
+                                                  const char *object_path)
+{
+	NMSupplicantManagerPrivate *priv;
+	NMSupplicantInterface *iface;
+	GSList *ifaces;
+
+	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL);
+	g_return_val_if_fail (object_path != NULL, NULL);
+
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	_LOGD ("creating new supplicant interface for dbus path %s", object_path);
+
+	/* assert against not requesting duplicate interfaces. */
+	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
+		if (g_strcmp0 (nm_supplicant_interface_get_object_path (ifaces->data), object_path) == 0)
+			g_return_val_if_reached (NULL);
+	}
+
+	iface = nm_supplicant_interface_new (NULL,
+	                                     object_path,
+	                                     NM_SUPPLICANT_DRIVER_WIRELESS,
+	                                     priv->fast_support,
+	                                     priv->ap_support,
+	                                     priv->pmf_support,
+	                                     priv->fils_support,
+	                                     priv->p2p_support,
+	                                     priv->wfd_support);
 
 	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
 	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
@@ -199,6 +325,8 @@ update_capabilities (NMSupplicantManager *self)
 	priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 	priv->pmf_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 	priv->fils_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+	/* P2P support is newer than the capabilities property */
+	priv->p2p_support = NM_SUPPLICANT_FEATURE_NO;
 
 	value = g_dbus_proxy_get_cached_property (priv->proxy, "Capabilities");
 	if (value) {
@@ -207,6 +335,7 @@ update_capabilities (NMSupplicantManager *self)
 			priv->ap_support = NM_SUPPLICANT_FEATURE_NO;
 			priv->pmf_support = NM_SUPPLICANT_FEATURE_NO;
 			priv->fils_support = NM_SUPPLICANT_FEATURE_NO;
+			priv->p2p_support = NM_SUPPLICANT_FEATURE_NO;
 			if (array) {
 				if (g_strv_contains (array, "ap"))
 					priv->ap_support = NM_SUPPLICANT_FEATURE_YES;
@@ -214,17 +343,20 @@ update_capabilities (NMSupplicantManager *self)
 					priv->pmf_support = NM_SUPPLICANT_FEATURE_YES;
 				if (g_strv_contains (array, "fils"))
 					priv->fils_support = NM_SUPPLICANT_FEATURE_YES;
+				if (g_strv_contains (array, "p2p"))
+					priv->p2p_support = NM_SUPPLICANT_FEATURE_YES;
 				g_free (array);
 			}
 		}
 		g_variant_unref (value);
 	}
 
-	/* Tell all interfaces about results of the AP/PMF/FILS check */
+	/* Tell all interfaces about results of the AP/PMF/FILS/P2P check */
 	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
 		nm_supplicant_interface_set_ap_support (ifaces->data, priv->ap_support);
 		nm_supplicant_interface_set_pmf_support (ifaces->data, priv->pmf_support);
 		nm_supplicant_interface_set_fils_support (ifaces->data, priv->fils_support);
+		nm_supplicant_interface_set_p2p_support (ifaces->data, priv->p2p_support);
 	}
 
 	_LOGD ("AP mode is %ssupported",
@@ -236,6 +368,9 @@ update_capabilities (NMSupplicantManager *self)
 	_LOGD ("FILS is %ssupported",
 	       (priv->fils_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
 	           (priv->fils_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+	_LOGD ("P2P is %ssupported",
+	       (priv->p2p_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
+	           (priv->p2p_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
 
 	/* EAP-FAST */
 	priv->fast_support = NM_SUPPLICANT_FEATURE_NO;
@@ -264,6 +399,20 @@ update_capabilities (NMSupplicantManager *self)
 	_LOGD ("EAP-FAST is %ssupported",
 	       (priv->fast_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
 	           (priv->fast_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+
+	priv->wfd_support = NM_SUPPLICANT_FEATURE_NO;
+	value = g_dbus_proxy_get_cached_property (priv->proxy, "WFDIEs");
+	if (value) {
+		priv->wfd_support = NM_SUPPLICANT_FEATURE_YES;
+		g_variant_unref (value);
+	}
+
+	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next)
+		nm_supplicant_interface_set_wfd_support (ifaces->data, priv->fast_support);
+
+	_LOGD ("WFD is %ssupported",
+	       (priv->wfd_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
+	           (priv->wfd_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
 }
 
 static void
diff --git a/src/supplicant/nm-supplicant-manager.h b/src/supplicant/nm-supplicant-manager.h
index 8928cf20..058745fb 100644
--- a/src/supplicant/nm-supplicant-manager.h
+++ b/src/supplicant/nm-supplicant-manager.h
@@ -38,8 +38,13 @@ GType nm_supplicant_manager_get_type (void);
 
 NMSupplicantManager *nm_supplicant_manager_get (void);
 
+void nm_supplicant_manager_set_wfd_ies (NMSupplicantManager *self,
+                                        GBytes *wfd_ies);
+
 NMSupplicantInterface *nm_supplicant_manager_create_interface (NMSupplicantManager *mgr,
                                                                const char *ifname,
                                                                NMSupplicantDriver driver);
+NMSupplicantInterface *nm_supplicant_manager_create_interface_from_path (NMSupplicantManager *self,
+                                                                         const char *object_path);
 
 #endif /* __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ */
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
index 1e25675d..f10bbb04 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -24,8 +24,6 @@
 
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
-#include <errno.h>
 
 struct Opt {
 	const char *     key;
@@ -72,7 +70,7 @@ 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",
+                                    "IEEE8021X", "WPA-NONE", "SAE",
                                     "NONE", NULL };
 const char * auth_alg_allowed[] = { "OPEN", "SHARED", "LEAP", NULL };
 const char * eap_allowed[] =      { "LEAP", "MD5", "TLS", "PEAP", "TTLS", "SIM",
@@ -159,23 +157,13 @@ validate_type_int (const struct Opt * opt,
                    const char * value,
                    const guint32 len)
 {
-	long int intval;
+	gint64 v;
 
 	g_return_val_if_fail (opt != NULL, FALSE);
 	g_return_val_if_fail (value != NULL, FALSE);
 
-	errno = 0;
-	intval = strtol (value, NULL, 10);
-	if (errno != 0)
-		return FALSE;
-
-	/* strtol returns a long, but we are dealing with ints */
-	if (intval > INT_MAX || intval < INT_MIN)
-		return FALSE;
-	if (intval > opt->int_high || intval < opt->int_low)
-		return FALSE;
-
-	return TRUE;
+	v = _nm_utils_ascii_str_to_int64 (value, 10, opt->int_low, opt->int_high, G_MININT64);
+	return v != G_MININT64 || errno == 0;
 }
 
 static gboolean
diff --git a/src/supplicant/tests/meson.build b/src/supplicant/tests/meson.build
index 5e4cbdbe..fbccb313 100644
--- a/src/supplicant/tests/meson.build
+++ b/src/supplicant/tests/meson.build
@@ -9,5 +9,6 @@ exe = executable(
 test(
   'supplicant/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index d7ec1fe2..2c7a71a3 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -389,6 +388,76 @@ test_wifi_wpa_psk (const char *detail,
 }
 
 static void
+test_wifi_sae_psk (const char *psk)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_variant GVariant *config_dict = NULL;
+	NMSettingWirelessSecurity *s_wsec;
+	gboolean success;
+	GError *error = NULL;
+	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	gs_unref_bytes GBytes *ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
+	const char *bssid_str = "11:22:33:44:55:66";
+	int short_psk = strlen (psk) < 8;
+
+	connection = new_basic_connection ("Test Wifi SAE", ssid, bssid_str);
+
+	/* Wifi Security setting */
+	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
+	g_object_set (s_wsec,
+	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae",
+	              NM_SETTING_WIRELESS_SECURITY_PSK, psk,
+	              NULL);
+	nm_setting_wireless_security_add_proto (s_wsec, "rsn");
+	nm_setting_wireless_security_add_pairwise (s_wsec, "tkip");
+	nm_setting_wireless_security_add_pairwise (s_wsec, "ccmp");
+	nm_setting_wireless_security_add_group (s_wsec, "tkip");
+	nm_setting_wireless_security_add_group (s_wsec, "ccmp");
+
+	success = nm_connection_verify (connection, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	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 'SAE'");
+	if (short_psk)
+		NMTST_EXPECT_NM_INFO ("Config: added 'sae_password' value *");
+	else
+		NMTST_EXPECT_NM_INFO ("Config: added 'psk' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'proto' value 'RSN'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '0'");
+	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
+
+	g_test_assert_expected_messages ();
+	g_assert (config_dict);
+
+	validate_opt ("wifi-sae", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-sae", config_dict, "ssid", TYPE_BYTES, ssid);
+	validate_opt ("wifi-sae", config_dict, "bssid", TYPE_KEYWORD, bssid_str);
+	validate_opt ("wifi-sae", config_dict, "key_mgmt", TYPE_KEYWORD, "SAE");
+	validate_opt ("wifi-sae", config_dict, "proto", TYPE_KEYWORD, "RSN");
+	validate_opt ("wifi-sae", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-sae", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
+	if (short_psk)
+		validate_opt ("wifi-sae", config_dict, "sae_password", TYPE_KEYWORD, psk);
+	else
+		validate_opt ("wifi-sae", config_dict, "psk", TYPE_KEYWORD, psk);
+}
+
+static void
+test_wifi_sae (void)
+{
+	test_wifi_sae_psk ("Moo");
+	test_wifi_sae_psk ("Hello World!");
+}
+
+static void
 test_wifi_wpa_psk_types (void)
 {
 	const char *key1 = "d4721e911461d3cdef9793858e977fcda091779243abb7316c2f11605a160893";
@@ -580,6 +649,7 @@ int main (int argc, char **argv)
 	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);
+	g_test_add_func ("/supplicant-config/wifi-sae", test_wifi_sae);
 
 	return g_test_run ();
 }
diff --git a/src/systemd/meson.build b/src/systemd/meson.build
index 1bf1ea41..9dea4fb5 100644
--- a/src/systemd/meson.build
+++ b/src/systemd/meson.build
@@ -1,73 +1,46 @@
-sources = files(
-  'sd-adapt/nm-sd-adapt.c',
-  'src/basic/alloc-util.c',
-  'src/basic/escape.c',
-  'src/basic/env-util.c',
-  'src/basic/ether-addr-util.c',
-  'src/basic/extract-word.c',
-  'src/basic/fd-util.c',
-  '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/stat-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',
-  'nm-sd-utils.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'
+libnm_systemd_core = static_library(
+  'nm-systemd-core',
+  sources: files(
+    'sd-adapt-core/nm-sd-adapt-core.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/event-util.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',
+    'nm-sd-utils-core.c',
+  ),
+  include_directories: [
+    src_inc,
+    include_directories(
+      'sd-adapt-core',
+      'src/libsystemd-network',
+      'src/libsystemd/sd-event',
+      'src/shared',
+      'src/systemd',
+    )
+  ],
+  dependencies: [
+    nm_core_dep,
+  ],
+  c_args: [
+    '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD',
+  ],
 )
diff --git a/src/systemd/nm-sd-utils.c b/src/systemd/nm-sd-utils-core.c
index c6c4c123..42560789 100644
--- a/src/systemd/nm-sd-utils.c
+++ b/src/systemd/nm-sd-utils-core.c
@@ -18,37 +18,16 @@
 
 #include "nm-default.h"
 
-#include "nm-sd-utils.h"
+#include "nm-sd-utils-core.h"
 
 #include "nm-core-internal.h"
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
-#include "path-util.h"
 #include "sd-id128.h"
 
 /*****************************************************************************/
 
-gboolean
-nm_sd_utils_path_equal (const char *a, const char *b)
-{
-	return path_equal (a, b);
-}
-
-char *
-nm_sd_utils_path_simplify (char *path, gboolean kill_dots)
-{
-	return path_simplify (path, kill_dots);
-}
-
-const char *
-nm_sd_utils_path_startswith (const char *path, const char *prefix)
-{
-	return path_startswith (path, prefix);
-}
-
-/*****************************************************************************/
-
 NMUuid *
 nm_sd_utils_id128_get_machine (NMUuid *out_uuid)
 {
diff --git a/src/systemd/nm-sd-utils.h b/src/systemd/nm-sd-utils-core.h
index 0af514eb..a7b092b3 100644
--- a/src/systemd/nm-sd-utils.h
+++ b/src/systemd/nm-sd-utils-core.h
@@ -16,16 +16,8 @@
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#ifndef __NM_SD_UTILS_H__
-#define __NM_SD_UTILS_H__
-
-/*****************************************************************************/
-
-gboolean nm_sd_utils_path_equal (const char *a, const char *b);
-
-char *nm_sd_utils_path_simplify (char *path, gboolean kill_dots);
-
-const char *nm_sd_utils_path_startswith (const char *path, const char *prefix);
+#ifndef __NM_SD_UTILS_CORE_H__
+#define __NM_SD_UTILS_CORE_H__
 
 /*****************************************************************************/
 
@@ -35,4 +27,4 @@ struct _NMUuid *nm_sd_utils_id128_get_machine (struct _NMUuid *out_uuid);
 
 /*****************************************************************************/
 
-#endif /* __NM_SD_UTILS_H__ */
+#endif /* __NM_SD_UTILS_CORE_H__ */
diff --git a/src/systemd/nm-sd.c b/src/systemd/nm-sd.c
index cbef91d1..4009c617 100644
--- a/src/systemd/nm-sd.c
+++ b/src/systemd/nm-sd.c
@@ -133,9 +133,13 @@ nm_sd_event_attach_default (void)
 
 /*****************************************************************************/
 
+const bool mempool_use_allowed = true;
+
+/*****************************************************************************/
+
 /* ensure that defines in nm-sd.h correspond to the internal defines. */
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 #include "dhcp-lease-internal.h"
 
 /*****************************************************************************/
diff --git a/src/systemd/sd-adapt/condition.h b/src/systemd/sd-adapt-core/condition.h
index d3a6812a..d3a6812a 100644
--- a/src/systemd/sd-adapt/condition.h
+++ b/src/systemd/sd-adapt-core/condition.h
diff --git a/src/systemd/sd-adapt/conf-parser.h b/src/systemd/sd-adapt-core/conf-parser.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt/conf-parser.h
+++ b/src/systemd/sd-adapt-core/conf-parser.h
diff --git a/src/systemd/sd-adapt/architecture.h b/src/systemd/sd-adapt-core/device-util.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt/architecture.h
+++ b/src/systemd/sd-adapt-core/device-util.h
diff --git a/src/systemd/sd-adapt/khash.h b/src/systemd/sd-adapt-core/khash.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt/khash.h
+++ b/src/systemd/sd-adapt-core/khash.h
diff --git a/src/systemd/sd-adapt/nm-sd-adapt.c b/src/systemd/sd-adapt-core/nm-sd-adapt-core.c
index 4e308276..d7ed687e 100644
--- a/src/systemd/sd-adapt/nm-sd-adapt.c
+++ b/src/systemd/sd-adapt-core/nm-sd-adapt-core.c
@@ -18,7 +18,7 @@
 
 #include "nm-default.h"
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #include "fd-util.h"
 
diff --git a/src/systemd/sd-adapt-core/nm-sd-adapt-core.h b/src/systemd/sd-adapt-core/nm-sd-adapt-core.h
new file mode 100644
index 00000000..8c07c53a
--- /dev/null
+++ b/src/systemd/sd-adapt-core/nm-sd-adapt-core.h
@@ -0,0 +1,101 @@
+/* This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2014 - 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_SD_ADAPT_CORE_H__
+#define __NM_SD_ADAPT_CORE_H__
+
+#include "nm-default.h"
+
+#include <stdbool.h>
+#include <sys/resource.h>
+#include <time.h>
+
+#include "systemd/sd-adapt-shared/nm-sd-adapt-shared.h"
+
+#ifndef HAVE_SYS_AUXV_H
+#define HAVE_SYS_AUXV_H 0
+#endif
+
+/*****************************************************************************
+ * The remainder of the header is only enabled when building the systemd code
+ * itself.
+ *****************************************************************************/
+
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD
+
+#include <netinet/in.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <elf.h>
+#ifdef HAVE_SYS_AUXV_H
+#include <sys/auxv.h>
+#endif
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <sys/ioctl.h>
+
+#include <net/if_arp.h>
+
+/* Missing in Linux 3.2.0, in Ubuntu 12.04 */
+#ifndef BPF_XOR
+#define BPF_XOR 0xa0
+#endif
+
+#ifndef ETHERTYPE_LLDP
+#define ETHERTYPE_LLDP 0x88cc
+#endif
+
+#ifndef HAVE_SECURE_GETENV
+#  ifdef HAVE___SECURE_GETENV
+#    define secure_getenv __secure_getenv
+#  else
+#    error neither secure_getenv nor __secure_getenv is available
+#  endif
+#endif
+
+/*****************************************************************************/
+
+static inline int
+sd_notify (int unset_environment, const char *state)
+{
+	return 0;
+}
+
+/* Can't include both net/if.h and linux/if.h; so have to define this here */
+#ifndef IF_NAMESIZE
+#define IF_NAMESIZE 16
+#endif
+
+#ifndef IFNAMSIZ
+#define IFNAMSIZ IF_NAMESIZE
+#endif
+
+#ifndef MAX_HANDLE_SZ
+#define MAX_HANDLE_SZ 128
+#endif
+
+#include "sd-id128.h"
+#include "sparse-endian.h"
+#include "async.h"
+#include "util.h"
+
+#endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */
+
+#endif /* __NM_SD_ADAPT_CORE_H__ */
+
diff --git a/src/systemd/sd-adapt/sd-daemon.h b/src/systemd/sd-adapt-core/sd-daemon.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt/sd-daemon.h
+++ b/src/systemd/sd-adapt-core/sd-daemon.h
diff --git a/src/systemd/sd-adapt/sd-device.h b/src/systemd/sd-adapt-core/sd-device.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt/sd-device.h
+++ b/src/systemd/sd-adapt-core/sd-device.h
diff --git a/src/systemd/sd-adapt/stat-util.h b/src/systemd/sd-adapt-core/stat-util.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt/stat-util.h
+++ b/src/systemd/sd-adapt-core/stat-util.h
diff --git a/src/systemd/sd-adapt/btrfs-util.h b/src/systemd/sd-adapt/btrfs-util.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/btrfs-util.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/build.h b/src/systemd/sd-adapt/build.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/build.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/cgroup-util.h b/src/systemd/sd-adapt/cgroup-util.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/cgroup-util.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/copy.h b/src/systemd/sd-adapt/copy.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/copy.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/def.h b/src/systemd/sd-adapt/def.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/def.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/device-nodes.h b/src/systemd/sd-adapt/device-nodes.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/device-nodes.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/dirent-util.h b/src/systemd/sd-adapt/dirent-util.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/dirent-util.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/errno-list.h b/src/systemd/sd-adapt/errno-list.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/errno-list.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/format-util.h b/src/systemd/sd-adapt/format-util.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/format-util.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/glob-util.h b/src/systemd/sd-adapt/glob-util.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/glob-util.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/gunicode.h b/src/systemd/sd-adapt/gunicode.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/gunicode.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/ioprio.h b/src/systemd/sd-adapt/ioprio.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/ioprio.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/locale-util.h b/src/systemd/sd-adapt/locale-util.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/locale-util.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/memfd-util.h b/src/systemd/sd-adapt/memfd-util.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/memfd-util.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/missing.h b/src/systemd/sd-adapt/missing.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/missing.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/mkdir.h b/src/systemd/sd-adapt/mkdir.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/mkdir.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/nm-sd-adapt.h b/src/systemd/sd-adapt/nm-sd-adapt.h
deleted file mode 100644
index e163eeda..00000000
--- a/src/systemd/sd-adapt/nm-sd-adapt.h
+++ /dev/null
@@ -1,191 +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) 2014 - 2015 Red Hat, Inc.
- */
-
-#ifndef NM_SD_ADAPT_H
-#define NM_SD_ADAPT_H
-
-#include "nm-default.h"
-
-#include <stdbool.h>
-#include <syslog.h>
-#include <sys/resource.h>
-#include <time.h>
-
-#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
-#define HAVE_EXPLICIT_BZERO 0
-#endif
-
-#define ENABLE_DEBUG_HASHMAP 0
-
-#ifndef HAVE_SYS_AUXV_H
-#define HAVE_SYS_AUXV_H 0
-#endif
-
-/*****************************************************************************/
-
-static inline NMLogLevel
-_slog_level_to_nm (int slevel)
-{
-    switch (LOG_PRI (slevel)) {
-    case LOG_DEBUG:   return LOGL_DEBUG;
-	case LOG_WARNING: return LOGL_WARN;
-	case LOG_CRIT:
-	case LOG_ERR:     return LOGL_ERR;
-	case LOG_INFO:
-	case LOG_NOTICE:
-	default:          return LOGL_INFO;
-	}
-}
-
-static inline int
-_nm_log_get_max_level_realm (void)
-{
-	/* inline function, to avoid coverity warning about constant expression. */
-	return LOG_DEBUG;
-}
-#define log_get_max_level_realm(realm) _nm_log_get_max_level_realm ()
-
-#define log_internal_realm(level, error, file, line, func, format, ...) \
-({ \
-	const int _nm_e = (error); \
-	const NMLogLevel _nm_l = _slog_level_to_nm ((level)); \
-	\
-	if (nm_logging_enabled (_nm_l, LOGD_SYSTEMD)) { \
-		const char *_nm_location = strrchr ((""file), '/'); \
-		\
-		_nm_log_impl (_nm_location ? _nm_location + 1 : (""file), (line), (func), _nm_l, LOGD_DHCP, _nm_e, NULL, NULL, ("%s"format), "libsystemd: ", ## __VA_ARGS__); \
-	} \
-	(_nm_e > 0 ? -_nm_e : _nm_e); \
-})
-
-#define log_assert_failed(text, file, line, func) \
-G_STMT_START { \
-	log_internal (LOG_CRIT, 0, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting.", text, file, line, func); \
-	g_assert_not_reached (); \
-} G_STMT_END
-
-#define log_assert_failed_unreachable(text, file, line, func) \
-G_STMT_START { \
-	log_internal (LOG_CRIT, 0, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting.", text, file, line, func); \
-	g_assert_not_reached (); \
-} G_STMT_END
-
-#define log_assert_failed_return(text, file, line, func) \
-({ \
-	log_internal (LOG_DEBUG, 0, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Ignoring.", text, file, line, func); \
-	g_return_if_fail_warning (G_LOG_DOMAIN, G_STRFUNC, text); \
-	(void) 0; \
-})
-
-/*****************************************************************************
- * The remainder of the header is only enabled when building the systemd code
- * itself.
- *****************************************************************************/
-
-#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD
-
-#include <netinet/in.h>
-#include <string.h>
-#include <stdio.h>
-#include <errno.h>
-#include <elf.h>
-#ifdef HAVE_SYS_AUXV_H
-#include <sys/auxv.h>
-#endif
-#include <unistd.h>
-#include <sys/syscall.h>
-#include <sys/ioctl.h>
-
-#include <net/if_arp.h>
-
-/* Missing in Linux 3.2.0, in Ubuntu 12.04 */
-#ifndef BPF_XOR
-#define BPF_XOR 0xa0
-#endif
-
-#ifndef ETHERTYPE_LLDP
-#define ETHERTYPE_LLDP 0x88cc
-#endif
-
-#ifndef HAVE_SECURE_GETENV
-#  ifdef HAVE___SECURE_GETENV
-#    define secure_getenv __secure_getenv
-#  else
-#    error neither secure_getenv nor __secure_getenv is available
-#  endif
-#endif
-
-#define VALGRIND 0
-
-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 */
-typedef guint16 char16_t;
-typedef guint32 char32_t;
-
-/*****************************************************************************/
-
-static inline int
-sd_notify (int unset_environment, const char *state)
-{
-	return 0;
-}
-
-/* Can't include both net/if.h and linux/if.h; so have to define this here */
-#ifndef IF_NAMESIZE
-#define IF_NAMESIZE 16
-#endif
-
-#ifndef IFNAMSIZ
-#define IFNAMSIZ IF_NAMESIZE
-#endif
-
-#ifndef MAX_HANDLE_SZ
-#define MAX_HANDLE_SZ 128
-#endif
-
-#include "sd-id128.h"
-#include "sparse-endian.h"
-#include "async.h"
-#include "util.h"
-
-static inline pid_t gettid(void) {
-        return (pid_t) syscall(SYS_gettid);
-}
-
-#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
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/procfs-util.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/raw-clone.h b/src/systemd/sd-adapt/raw-clone.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/raw-clone.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/terminal-util.h b/src/systemd/sd-adapt/terminal-util.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/terminal-util.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/unaligned.h b/src/systemd/sd-adapt/unaligned.h
deleted file mode 100644
index 17dc0444..00000000
--- a/src/systemd/sd-adapt/unaligned.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-#include "nm-utils/unaligned.h"
diff --git a/src/systemd/sd-adapt/user-util.h b/src/systemd/sd-adapt/user-util.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/user-util.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/virt.h b/src/systemd/sd-adapt/virt.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/virt.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/src/basic/alloc-util.c b/src/systemd/src/basic/alloc-util.c
deleted file mode 100644
index ef405098..00000000
--- a/src/systemd/src/basic/alloc-util.c
+++ /dev/null
@@ -1,83 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <stdint.h>
-#include <string.h>
-
-#include "alloc-util.h"
-#include "macro.h"
-#include "util.h"
-
-void* memdup(const void *p, size_t l) {
-        void *ret;
-
-        assert(l == 0 || p);
-
-        ret = malloc(l);
-        if (!ret)
-                return NULL;
-
-        memcpy(ret, p, l);
-        return ret;
-}
-
-void* memdup_suffix0(const void *p, size_t l) {
-        void *ret;
-
-        assert(l == 0 || p);
-
-        /* The same as memdup() but place a safety NUL byte after the allocated memory */
-
-        ret = malloc(l + 1);
-        if (!ret)
-                return NULL;
-
-        *((uint8_t*) mempcpy(ret, p, l)) = 0;
-        return ret;
-}
-
-void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) {
-        size_t a, newalloc;
-        void *q;
-
-        assert(p);
-        assert(allocated);
-
-        if (*allocated >= need)
-                return *p;
-
-        newalloc = MAX(need * 2, 64u / size);
-        a = newalloc * size;
-
-        /* check for overflows */
-        if (a < size * need)
-                return NULL;
-
-        q = realloc(*p, a);
-        if (!q)
-                return NULL;
-
-        *p = q;
-        *allocated = newalloc;
-        return q;
-}
-
-void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size) {
-        size_t prev;
-        uint8_t *q;
-
-        assert(p);
-        assert(allocated);
-
-        prev = *allocated;
-
-        q = greedy_realloc(p, allocated, need, size);
-        if (!q)
-                return NULL;
-
-        if (*allocated > prev)
-                memzero(q + prev * size, (*allocated - prev) * size);
-
-        return q;
-}
diff --git a/src/systemd/src/basic/alloc-util.h b/src/systemd/src/basic/alloc-util.h
deleted file mode 100644
index ebe42889..00000000
--- a/src/systemd/src/basic/alloc-util.h
+++ /dev/null
@@ -1,130 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <alloca.h>
-#include <stddef.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "macro.h"
-
-#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n)))
-
-#define new0(t, n) ((t*) calloc((n), sizeof(t)))
-
-#define newa(t, n)                                              \
-        ({                                                      \
-                assert(!size_multiply_overflow(sizeof(t), n));  \
-                (t*) alloca(sizeof(t)*(n));                     \
-        })
-
-#define newa0(t, n)                                             \
-        ({                                                      \
-                assert(!size_multiply_overflow(sizeof(t), n));  \
-                (t*) alloca0(sizeof(t)*(n));                    \
-        })
-
-#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n)))
-
-#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, sizeof(t), (n)))
-
-#define malloc0(n) (calloc(1, (n)))
-
-static inline void *mfree(void *memory) {
-        free(memory);
-        return NULL;
-}
-
-#define free_and_replace(a, b)                  \
-        ({                                      \
-                free(a);                        \
-                (a) = (b);                      \
-                (b) = NULL;                     \
-                0;                              \
-        })
-
-void* memdup(const void *p, size_t l) _alloc_(2);
-void* memdup_suffix0(const void *p, size_t l) _alloc_(2);
-
-static inline void freep(void *p) {
-        free(*(void**) p);
-}
-
-#define _cleanup_free_ _cleanup_(freep)
-
-static inline bool size_multiply_overflow(size_t size, size_t need) {
-        return _unlikely_(need != 0 && size > (SIZE_MAX / need));
-}
-
-_malloc_  _alloc_(1, 2) static inline void *malloc_multiply(size_t size, size_t need) {
-        if (size_multiply_overflow(size, need))
-                return NULL;
-
-        return malloc(size * 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))
-                return NULL;
-
-        return memdup(p, size * need);
-}
-
-_alloc_(2, 3) static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) {
-        if (size_multiply_overflow(size, need))
-                return NULL;
-
-        return memdup_suffix0(p, size * need);
-}
-
-void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size);
-void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size);
-
-#define GREEDY_REALLOC(array, allocated, need)                          \
-        greedy_realloc((void**) &(array), &(allocated), (need), sizeof((array)[0]))
-
-#define GREEDY_REALLOC0(array, allocated, need)                         \
-        greedy_realloc0((void**) &(array), &(allocated), (need), sizeof((array)[0]))
-
-#define alloca0(n)                                      \
-        ({                                              \
-                char *_new_;                            \
-                size_t _len_ = n;                       \
-                _new_ = alloca(_len_);                  \
-                (void *) memset(_new_, 0, _len_);       \
-        })
-
-/* It's not clear what alignment glibc/gcc alloca() guarantee, hence provide a guaranteed safe version */
-#define alloca_align(size, align)                                       \
-        ({                                                              \
-                void *_ptr_;                                            \
-                size_t _mask_ = (align) - 1;                            \
-                _ptr_ = alloca((size) + _mask_);                        \
-                (void*)(((uintptr_t)_ptr_ + _mask_) & ~_mask_);         \
-        })
-
-#define alloca0_align(size, align)                                      \
-        ({                                                              \
-                void *_new_;                                            \
-                size_t _size_ = (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
deleted file mode 100644
index 31606131..00000000
--- a/src/systemd/src/basic/async.h
+++ /dev/null
@@ -1,7 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-int asynchronous_job(void* (*func)(void *p), void *arg);
-
-int asynchronous_sync(pid_t *ret_pid);
-int asynchronous_close(int fd);
diff --git a/src/systemd/src/basic/env-util.c b/src/systemd/src/basic/env-util.c
deleted file mode 100644
index 52d12a8a..00000000
--- a/src/systemd/src/basic/env-util.c
+++ /dev/null
@@ -1,791 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <limits.h>
-#include <stdarg.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "alloc-util.h"
-#include "env-util.h"
-#include "escape.h"
-#include "extract-word.h"
-#include "macro.h"
-#include "parse-util.h"
-#include "string-util.h"
-#include "strv.h"
-#include "utf8.h"
-
-#if 0 /* NM_IGNORED */
-#define VALID_CHARS_ENV_NAME                    \
-        DIGITS LETTERS                          \
-        "_"
-
-#ifndef ARG_MAX
-#define ARG_MAX ((size_t) sysconf(_SC_ARG_MAX))
-#endif
-
-static bool env_name_is_valid_n(const char *e, size_t n) {
-        const char *p;
-
-        if (!e)
-                return false;
-
-        if (n <= 0)
-                return false;
-
-        if (e[0] >= '0' && e[0] <= '9')
-                return false;
-
-        /* POSIX says the overall size of the environment block cannot
-         * be > ARG_MAX, an individual assignment hence cannot be
-         * either. Discounting the equal sign and trailing NUL this
-         * hence leaves ARG_MAX-2 as longest possible variable
-         * name. */
-        if (n > ARG_MAX - 2)
-                return false;
-
-        for (p = e; p < e + n; p++)
-                if (!strchr(VALID_CHARS_ENV_NAME, *p))
-                        return false;
-
-        return true;
-}
-
-bool env_name_is_valid(const char *e) {
-        if (!e)
-                return false;
-
-        return env_name_is_valid_n(e, strlen(e));
-}
-
-bool env_value_is_valid(const char *e) {
-        if (!e)
-                return false;
-
-        if (!utf8_is_valid(e))
-                return false;
-
-        /* bash allows tabs and newlines in environment variables, and so
-         * should we */
-        if (string_has_cc(e, "\t\n"))
-                return false;
-
-        /* POSIX says the overall size of the environment block cannot
-         * be > ARG_MAX, an individual assignment hence cannot be
-         * either. Discounting the shortest possible variable name of
-         * length 1, the equal sign and trailing NUL this hence leaves
-         * ARG_MAX-3 as longest possible variable value. */
-        if (strlen(e) > ARG_MAX - 3)
-                return false;
-
-        return true;
-}
-
-bool env_assignment_is_valid(const char *e) {
-        const char *eq;
-
-        eq = strchr(e, '=');
-        if (!eq)
-                return false;
-
-        if (!env_name_is_valid_n(e, eq - e))
-                return false;
-
-        if (!env_value_is_valid(eq + 1))
-                return false;
-
-        /* POSIX says the overall size of the environment block cannot
-         * be > ARG_MAX, hence the individual variable assignments
-         * cannot be either, but let's leave room for one trailing NUL
-         * byte. */
-        if (strlen(e) > ARG_MAX - 1)
-                return false;
-
-        return true;
-}
-
-bool strv_env_is_valid(char **e) {
-        char **p, **q;
-
-        STRV_FOREACH(p, e) {
-                size_t k;
-
-                if (!env_assignment_is_valid(*p))
-                        return false;
-
-                /* Check if there are duplicate assginments */
-                k = strcspn(*p, "=");
-                STRV_FOREACH(q, p + 1)
-                        if (strneq(*p, *q, k) && (*q)[k] == '=')
-                                return false;
-        }
-
-        return true;
-}
-
-bool strv_env_name_is_valid(char **l) {
-        char **p, **q;
-
-        STRV_FOREACH(p, l) {
-                if (!env_name_is_valid(*p))
-                        return false;
-
-                STRV_FOREACH(q, p + 1)
-                        if (streq(*p, *q))
-                                return false;
-        }
-
-        return true;
-}
-
-bool strv_env_name_or_assignment_is_valid(char **l) {
-        char **p, **q;
-
-        STRV_FOREACH(p, l) {
-                if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p))
-                        return false;
-
-                STRV_FOREACH(q, p + 1)
-                        if (streq(*p, *q))
-                                return false;
-        }
-
-        return true;
-}
-
-static int env_append(char **r, char ***k, char **a) {
-        assert(r);
-        assert(k);
-
-        if (!a)
-                return 0;
-
-        /* Add the entries of a to *k unless they already exist in *r
-         * in which case they are overridden instead. This assumes
-         * there is enough space in the r array. */
-
-        for (; *a; a++) {
-                char **j;
-                size_t n;
-
-                n = strcspn(*a, "=");
-
-                if ((*a)[n] == '=')
-                        n++;
-
-                for (j = r; j < *k; j++)
-                        if (strneq(*j, *a, n))
-                                break;
-
-                if (j >= *k)
-                        (*k)++;
-                else
-                        free(*j);
-
-                *j = strdup(*a);
-                if (!*j)
-                        return -ENOMEM;
-        }
-
-        return 0;
-}
-
-char **strv_env_merge(size_t n_lists, ...) {
-        size_t n = 0;
-        char **l, **k, **r;
-        va_list ap;
-        size_t i;
-
-        /* Merges an arbitrary number of environment sets */
-
-        va_start(ap, n_lists);
-        for (i = 0; i < n_lists; i++) {
-                l = va_arg(ap, char**);
-                n += strv_length(l);
-        }
-        va_end(ap);
-
-        r = new(char*, n+1);
-        if (!r)
-                return NULL;
-
-        k = r;
-
-        va_start(ap, n_lists);
-        for (i = 0; i < n_lists; i++) {
-                l = va_arg(ap, char**);
-                if (env_append(r, &k, l) < 0)
-                        goto fail;
-        }
-        va_end(ap);
-
-        *k = NULL;
-
-        return r;
-
-fail:
-        va_end(ap);
-        strv_free(r);
-
-        return NULL;
-}
-
-static bool env_match(const char *t, const char *pattern) {
-        assert(t);
-        assert(pattern);
-
-        /* pattern a matches string a
-         *         a matches a=
-         *         a matches a=b
-         *         a= matches a=
-         *         a=b matches a=b
-         *         a= does not match a
-         *         a=b does not match a=
-         *         a=b does not match a
-         *         a=b does not match a=c */
-
-        if (streq(t, pattern))
-                return true;
-
-        if (!strchr(pattern, '=')) {
-                size_t l = strlen(pattern);
-
-                return strneq(t, pattern, l) && t[l] == '=';
-        }
-
-        return false;
-}
-
-static bool env_entry_has_name(const char *entry, const char *name) {
-        const char *t;
-
-        assert(entry);
-        assert(name);
-
-        t = startswith(entry, name);
-        if (!t)
-                return false;
-
-        return *t == '=';
-}
-
-char **strv_env_delete(char **x, size_t n_lists, ...) {
-        size_t n, i = 0;
-        char **k, **r;
-        va_list ap;
-
-        /* Deletes every entry from x that is mentioned in the other
-         * string lists */
-
-        n = strv_length(x);
-
-        r = new(char*, n+1);
-        if (!r)
-                return NULL;
-
-        STRV_FOREACH(k, x) {
-                size_t v;
-
-                va_start(ap, n_lists);
-                for (v = 0; v < n_lists; v++) {
-                        char **l, **j;
-
-                        l = va_arg(ap, char**);
-                        STRV_FOREACH(j, l)
-                                if (env_match(*k, *j))
-                                        goto skip;
-                }
-                va_end(ap);
-
-                r[i] = strdup(*k);
-                if (!r[i]) {
-                        strv_free(r);
-                        return NULL;
-                }
-
-                i++;
-                continue;
-
-        skip:
-                va_end(ap);
-        }
-
-        r[i] = NULL;
-
-        assert(i <= n);
-
-        return r;
-}
-
-char **strv_env_unset(char **l, const char *p) {
-
-        char **f, **t;
-
-        if (!l)
-                return NULL;
-
-        assert(p);
-
-        /* Drops every occurrence of the env var setting p in the
-         * string list. Edits in-place. */
-
-        for (f = t = l; *f; f++) {
-
-                if (env_match(*f, p)) {
-                        free(*f);
-                        continue;
-                }
-
-                *(t++) = *f;
-        }
-
-        *t = NULL;
-        return l;
-}
-
-char **strv_env_unset_many(char **l, ...) {
-
-        char **f, **t;
-
-        if (!l)
-                return NULL;
-
-        /* Like strv_env_unset() but applies many at once. Edits in-place. */
-
-        for (f = t = l; *f; f++) {
-                bool found = false;
-                const char *p;
-                va_list ap;
-
-                va_start(ap, l);
-
-                while ((p = va_arg(ap, const char*))) {
-                        if (env_match(*f, p)) {
-                                found = true;
-                                break;
-                        }
-                }
-
-                va_end(ap);
-
-                if (found) {
-                        free(*f);
-                        continue;
-                }
-
-                *(t++) = *f;
-        }
-
-        *t = NULL;
-        return l;
-}
-
-int strv_env_replace(char ***l, char *p) {
-        char **f;
-        const char *t, *name;
-
-        assert(p);
-
-        /* Replace first occurrence of the env var or add a new one in the
-         * string list. Drop other occurences. Edits in-place. Does not copy p.
-         * p must be a valid key=value assignment.
-         */
-
-        t = strchr(p, '=');
-        assert(t);
-
-        name = strndupa(p, t - p);
-
-        for (f = *l; f && *f; f++)
-                if (env_entry_has_name(*f, name)) {
-                        free_and_replace(*f, p);
-                        strv_env_unset(f + 1, *f);
-                        return 0;
-                }
-
-        /* We didn't find a match, we need to append p or create a new strv */
-        if (strv_push(l, p) < 0)
-                return -ENOMEM;
-        return 1;
-}
-
-char **strv_env_set(char **x, const char *p) {
-
-        char **k;
-        _cleanup_strv_free_ char **r = NULL;
-        char* m[2] = { (char*) p, NULL };
-
-        /* Overrides the env var setting of p, returns a new copy */
-
-        r = new(char*, strv_length(x)+2);
-        if (!r)
-                return NULL;
-
-        k = r;
-        if (env_append(r, &k, x) < 0)
-                return NULL;
-
-        if (env_append(r, &k, m) < 0)
-                return NULL;
-
-        *k = NULL;
-
-        return TAKE_PTR(r);
-}
-
-char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) {
-        char **i;
-
-        assert(name);
-
-        if (k <= 0)
-                return NULL;
-
-        STRV_FOREACH_BACKWARDS(i, l)
-                if (strneq(*i, name, k) &&
-                    (*i)[k] == '=')
-                        return *i + k + 1;
-
-        if (flags & REPLACE_ENV_USE_ENVIRONMENT) {
-                const char *t;
-
-                t = strndupa(name, k);
-                return getenv(t);
-        };
-
-        return NULL;
-}
-
-char *strv_env_get(char **l, const char *name) {
-        assert(name);
-
-        return strv_env_get_n(l, name, strlen(name), 0);
-}
-
-char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) {
-        char **p, **q;
-        int k = 0;
-
-        STRV_FOREACH(p, e) {
-                size_t n;
-                bool duplicate = false;
-
-                if (!env_assignment_is_valid(*p)) {
-                        if (invalid_callback)
-                                invalid_callback(*p, userdata);
-                        free(*p);
-                        continue;
-                }
-
-                n = strcspn(*p, "=");
-                STRV_FOREACH(q, p + 1)
-                        if (strneq(*p, *q, n) && (*q)[n] == '=') {
-                                duplicate = true;
-                                break;
-                        }
-
-                if (duplicate) {
-                        free(*p);
-                        continue;
-                }
-
-                e[k++] = *p;
-        }
-
-        if (e)
-                e[k] = NULL;
-
-        return e;
-}
-
-char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
-        enum {
-                WORD,
-                CURLY,
-                VARIABLE,
-                VARIABLE_RAW,
-                TEST,
-                DEFAULT_VALUE,
-                ALTERNATE_VALUE,
-        } state = WORD;
-
-        const char *e, *word = format, *test_value;
-        char *k;
-        _cleanup_free_ char *r = NULL;
-        size_t i, len;
-        int nest = 0;
-
-        assert(format);
-
-        for (e = format, i = 0; *e && i < n; e ++, i ++)
-                switch (state) {
-
-                case WORD:
-                        if (*e == '$')
-                                state = CURLY;
-                        break;
-
-                case CURLY:
-                        if (*e == '{') {
-                                k = strnappend(r, word, e-word-1);
-                                if (!k)
-                                        return NULL;
-
-                                free_and_replace(r, k);
-
-                                word = e-1;
-                                state = VARIABLE;
-                                nest++;
-                        } else if (*e == '$') {
-                                k = strnappend(r, word, e-word);
-                                if (!k)
-                                        return NULL;
-
-                                free_and_replace(r, k);
-
-                                word = e+1;
-                                state = WORD;
-
-                        } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_CHARS_ENV_NAME, *e)) {
-                                k = strnappend(r, word, e-word-1);
-                                if (!k)
-                                        return NULL;
-
-                                free_and_replace(r, k);
-
-                                word = e-1;
-                                state = VARIABLE_RAW;
-
-                        } else
-                                state = WORD;
-                        break;
-
-                case VARIABLE:
-                        if (*e == '}') {
-                                const char *t;
-
-                                t = strv_env_get_n(env, word+2, e-word-2, flags);
-
-                                k = strappend(r, t);
-                                if (!k)
-                                        return NULL;
-
-                                free_and_replace(r, k);
-
-                                word = e+1;
-                                state = WORD;
-                        } else if (*e == ':') {
-                                if (!(flags & REPLACE_ENV_ALLOW_EXTENDED))
-                                        /* Treat this as unsupported syntax, i.e. do no replacement */
-                                        state = WORD;
-                                else {
-                                        len = e-word-2;
-                                        state = TEST;
-                                }
-                        }
-                        break;
-
-                case TEST:
-                        if (*e == '-')
-                                state = DEFAULT_VALUE;
-                        else if (*e == '+')
-                                state = ALTERNATE_VALUE;
-                        else {
-                                state = WORD;
-                                break;
-                        }
-
-                        test_value = e+1;
-                        break;
-
-                case DEFAULT_VALUE: /* fall through */
-                case ALTERNATE_VALUE:
-                        assert(flags & REPLACE_ENV_ALLOW_EXTENDED);
-
-                        if (*e == '{') {
-                                nest++;
-                                break;
-                        }
-
-                        if (*e != '}')
-                                break;
-
-                        nest--;
-                        if (nest == 0) {
-                                const char *t;
-                                _cleanup_free_ char *v = NULL;
-
-                                t = strv_env_get_n(env, word+2, len, flags);
-
-                                if (t && state == ALTERNATE_VALUE)
-                                        t = v = replace_env_n(test_value, e-test_value, env, flags);
-                                else if (!t && state == DEFAULT_VALUE)
-                                        t = v = replace_env_n(test_value, e-test_value, env, flags);
-
-                                k = strappend(r, t);
-                                if (!k)
-                                        return NULL;
-
-                                free_and_replace(r, k);
-
-                                word = e+1;
-                                state = WORD;
-                        }
-                        break;
-
-                case VARIABLE_RAW:
-                        assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
-
-                        if (!strchr(VALID_CHARS_ENV_NAME, *e)) {
-                                const char *t;
-
-                                t = strv_env_get_n(env, word+1, e-word-1, flags);
-
-                                k = strappend(r, t);
-                                if (!k)
-                                        return NULL;
-
-                                free_and_replace(r, k);
-
-                                word = e--;
-                                i--;
-                                state = WORD;
-                        }
-                        break;
-                }
-
-        if (state == VARIABLE_RAW) {
-                const char *t;
-
-                assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
-
-                t = strv_env_get_n(env, word+1, e-word-1, flags);
-                return strappend(r, t);
-        } else
-                return strnappend(r, word, e-word);
-}
-
-char **replace_env_argv(char **argv, char **env) {
-        char **ret, **i;
-        size_t k = 0, l = 0;
-
-        l = strv_length(argv);
-
-        ret = new(char*, l+1);
-        if (!ret)
-                return NULL;
-
-        STRV_FOREACH(i, argv) {
-
-                /* If $FOO appears as single word, replace it by the split up variable */
-                if ((*i)[0] == '$' && !IN_SET((*i)[1], '{', '$')) {
-                        char *e;
-                        char **w, **m = NULL;
-                        size_t q;
-
-                        e = strv_env_get(env, *i+1);
-                        if (e) {
-                                int r;
-
-                                r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES);
-                                if (r < 0) {
-                                        ret[k] = NULL;
-                                        strv_free(ret);
-                                        return NULL;
-                                }
-                        } else
-                                m = NULL;
-
-                        q = strv_length(m);
-                        l = l + q - 1;
-
-                        w = reallocarray(ret, l + 1, sizeof(char *));
-                        if (!w) {
-                                ret[k] = NULL;
-                                strv_free(ret);
-                                strv_free(m);
-                                return NULL;
-                        }
-
-                        ret = w;
-                        if (m) {
-                                memcpy(ret + k, m, q * sizeof(char*));
-                                free(m);
-                        }
-
-                        k += q;
-                        continue;
-                }
-
-                /* If ${FOO} appears as part of a word, replace it by the variable as-is */
-                ret[k] = replace_env(*i, env, 0);
-                if (!ret[k]) {
-                        strv_free(ret);
-                        return NULL;
-                }
-                k++;
-        }
-
-        ret[k] = NULL;
-        return ret;
-}
-#endif /* NM_IGNORED */
-
-int getenv_bool(const char *p) {
-        const char *e;
-
-        e = getenv(p);
-        if (!e)
-                return -ENXIO;
-
-        return parse_boolean(e);
-}
-
-#if 0 /* NM_IGNORED */
-int getenv_bool_secure(const char *p) {
-        const char *e;
-
-        e = secure_getenv(p);
-        if (!e)
-                return -ENXIO;
-
-        return parse_boolean(e);
-}
-
-int serialize_environment(FILE *f, char **environment) {
-        char **e;
-
-        STRV_FOREACH(e, environment) {
-                _cleanup_free_ char *ce;
-
-                ce = cescape(*e);
-                if (!ce)
-                        return -ENOMEM;
-
-                fprintf(f, "env=%s\n", ce);
-        }
-
-        /* caller should call ferror() */
-
-        return 0;
-}
-
-int deserialize_environment(char ***environment, const char *line) {
-        char *uce;
-        int r;
-
-        assert(line);
-        assert(environment);
-
-        assert(startswith(line, "env="));
-        r = cunescape(line + 4, 0, &uce);
-        if (r < 0)
-                return r;
-
-        return strv_env_replace(environment, uce);
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/env-util.h b/src/systemd/src/basic/env-util.h
deleted file mode 100644
index 174433ea..00000000
--- a/src/systemd/src/basic/env-util.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdio.h>
-
-#include "macro.h"
-#include "string.h"
-
-bool env_name_is_valid(const char *e);
-bool env_value_is_valid(const char *e);
-bool env_assignment_is_valid(const char *e);
-
-enum {
-        REPLACE_ENV_USE_ENVIRONMENT = 1u,
-        REPLACE_ENV_ALLOW_BRACELESS = 2u,
-        REPLACE_ENV_ALLOW_EXTENDED  = 4u,
-};
-
-char *replace_env_n(const char *format, size_t n, char **env, unsigned flags);
-char **replace_env_argv(char **argv, char **env);
-
-static inline char *replace_env(const char *format, char **env, unsigned flags) {
-        return replace_env_n(format, strlen(format), env, flags);
-}
-
-bool strv_env_is_valid(char **e);
-#define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL)
-char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata);
-
-bool strv_env_name_is_valid(char **l);
-bool strv_env_name_or_assignment_is_valid(char **l);
-
-char **strv_env_merge(size_t n_lists, ...);
-char **strv_env_delete(char **x, size_t n_lists, ...); /* New copy */
-
-char **strv_env_set(char **x, const char *p); /* New copy ... */
-char **strv_env_unset(char **l, const char *p); /* In place ... */
-char **strv_env_unset_many(char **l, ...) _sentinel_;
-int strv_env_replace(char ***l, char *p); /* In place ... */
-
-char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_;
-char *strv_env_get(char **x, const char *n) _pure_;
-
-int getenv_bool(const char *p);
-int getenv_bool_secure(const char *p);
-
-int serialize_environment(FILE *f, char **environment);
-int deserialize_environment(char ***environment, const char *line);
diff --git a/src/systemd/src/basic/escape.c b/src/systemd/src/basic/escape.c
deleted file mode 100644
index 5c82a545..00000000
--- a/src/systemd/src/basic/escape.c
+++ /dev/null
@@ -1,509 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "alloc-util.h"
-#include "escape.h"
-#include "hexdecoct.h"
-#include "macro.h"
-#include "utf8.h"
-
-int cescape_char(char c, char *buf) {
-        char *buf_old = buf;
-
-        /* Needs space for 4 characters in the buffer */
-
-        switch (c) {
-
-                case '\a':
-                        *(buf++) = '\\';
-                        *(buf++) = 'a';
-                        break;
-                case '\b':
-                        *(buf++) = '\\';
-                        *(buf++) = 'b';
-                        break;
-                case '\f':
-                        *(buf++) = '\\';
-                        *(buf++) = 'f';
-                        break;
-                case '\n':
-                        *(buf++) = '\\';
-                        *(buf++) = 'n';
-                        break;
-                case '\r':
-                        *(buf++) = '\\';
-                        *(buf++) = 'r';
-                        break;
-                case '\t':
-                        *(buf++) = '\\';
-                        *(buf++) = 't';
-                        break;
-                case '\v':
-                        *(buf++) = '\\';
-                        *(buf++) = 'v';
-                        break;
-                case '\\':
-                        *(buf++) = '\\';
-                        *(buf++) = '\\';
-                        break;
-                case '"':
-                        *(buf++) = '\\';
-                        *(buf++) = '"';
-                        break;
-                case '\'':
-                        *(buf++) = '\\';
-                        *(buf++) = '\'';
-                        break;
-
-                default:
-                        /* For special chars we prefer octal over
-                         * hexadecimal encoding, simply because glib's
-                         * g_strescape() does the same */
-                        if ((c < ' ') || (c >= 127)) {
-                                *(buf++) = '\\';
-                                *(buf++) = octchar((unsigned char) c >> 6);
-                                *(buf++) = octchar((unsigned char) c >> 3);
-                                *(buf++) = octchar((unsigned char) c);
-                        } else
-                                *(buf++) = c;
-                        break;
-        }
-
-        return buf - buf_old;
-}
-
-char *cescape_length(const char *s, size_t n) {
-        const char *f;
-        char *r, *t;
-
-        assert(s || n == 0);
-
-        /* Does C style string escaping. May be reversed with
-         * cunescape(). */
-
-        r = new(char, n*4 + 1);
-        if (!r)
-                return NULL;
-
-        for (f = s, t = r; f < s + n; f++)
-                t += cescape_char(*f, t);
-
-        *t = 0;
-
-        return r;
-}
-
-char *cescape(const char *s) {
-        assert(s);
-
-        return cescape_length(s, strlen(s));
-}
-
-int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) {
-        int r = 1;
-
-        assert(p);
-        assert(*p);
-        assert(ret);
-
-        /* Unescapes C style. Returns the unescaped character in ret.
-         * Sets *eight_bit to true if the escaped sequence either fits in
-         * one byte in UTF-8 or is a non-unicode literal byte and should
-         * instead be copied directly.
-         */
-
-        if (length != (size_t) -1 && length < 1)
-                return -EINVAL;
-
-        switch (p[0]) {
-
-        case 'a':
-                *ret = '\a';
-                break;
-        case 'b':
-                *ret = '\b';
-                break;
-        case 'f':
-                *ret = '\f';
-                break;
-        case 'n':
-                *ret = '\n';
-                break;
-        case 'r':
-                *ret = '\r';
-                break;
-        case 't':
-                *ret = '\t';
-                break;
-        case 'v':
-                *ret = '\v';
-                break;
-        case '\\':
-                *ret = '\\';
-                break;
-        case '"':
-                *ret = '"';
-                break;
-        case '\'':
-                *ret = '\'';
-                break;
-
-        case 's':
-                /* This is an extension of the XDG syntax files */
-                *ret = ' ';
-                break;
-
-        case 'x': {
-                /* hexadecimal encoding */
-                int a, b;
-
-                if (length != (size_t) -1 && length < 3)
-                        return -EINVAL;
-
-                a = unhexchar(p[1]);
-                if (a < 0)
-                        return -EINVAL;
-
-                b = unhexchar(p[2]);
-                if (b < 0)
-                        return -EINVAL;
-
-                /* Don't allow NUL bytes */
-                if (a == 0 && b == 0)
-                        return -EINVAL;
-
-                *ret = (a << 4U) | b;
-                *eight_bit = true;
-                r = 3;
-                break;
-        }
-
-        case 'u': {
-                /* C++11 style 16bit unicode */
-
-                int a[4];
-                size_t i;
-                uint32_t c;
-
-                if (length != (size_t) -1 && length < 5)
-                        return -EINVAL;
-
-                for (i = 0; i < 4; i++) {
-                        a[i] = unhexchar(p[1 + i]);
-                        if (a[i] < 0)
-                                return a[i];
-                }
-
-                c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3];
-
-                /* Don't allow 0 chars */
-                if (c == 0)
-                        return -EINVAL;
-
-                *ret = c;
-                r = 5;
-                break;
-        }
-
-        case 'U': {
-                /* C++11 style 32bit unicode */
-
-                int a[8];
-                size_t i;
-                char32_t c;
-
-                if (length != (size_t) -1 && length < 9)
-                        return -EINVAL;
-
-                for (i = 0; i < 8; i++) {
-                        a[i] = unhexchar(p[1 + i]);
-                        if (a[i] < 0)
-                                return a[i];
-                }
-
-                c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) |
-                    ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] <<  8U) | ((uint32_t) a[6] <<  4U) |  (uint32_t) a[7];
-
-                /* Don't allow 0 chars */
-                if (c == 0)
-                        return -EINVAL;
-
-                /* Don't allow invalid code points */
-                if (!unichar_is_valid(c))
-                        return -EINVAL;
-
-                *ret = c;
-                r = 9;
-                break;
-        }
-
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7': {
-                /* octal encoding */
-                int a, b, c;
-                char32_t m;
-
-                if (length != (size_t) -1 && length < 3)
-                        return -EINVAL;
-
-                a = unoctchar(p[0]);
-                if (a < 0)
-                        return -EINVAL;
-
-                b = unoctchar(p[1]);
-                if (b < 0)
-                        return -EINVAL;
-
-                c = unoctchar(p[2]);
-                if (c < 0)
-                        return -EINVAL;
-
-                /* don't allow NUL bytes */
-                if (a == 0 && b == 0 && c == 0)
-                        return -EINVAL;
-
-                /* Don't allow bytes above 255 */
-                m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c;
-                if (m > 255)
-                        return -EINVAL;
-
-                *ret = m;
-                *eight_bit = true;
-                r = 3;
-                break;
-        }
-
-        default:
-                return -EINVAL;
-        }
-
-        return r;
-}
-
-int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) {
-        char *r, *t;
-        const char *f;
-        size_t pl;
-
-        assert(s);
-        assert(ret);
-
-        /* Undoes C style string escaping, and optionally prefixes it. */
-
-        pl = strlen_ptr(prefix);
-
-        r = new(char, pl+length+1);
-        if (!r)
-                return -ENOMEM;
-
-        if (prefix)
-                memcpy(r, prefix, pl);
-
-        for (f = s, t = r + pl; f < s + length; f++) {
-                size_t remaining;
-                bool eight_bit = false;
-                char32_t u;
-                int k;
-
-                remaining = s + length - f;
-                assert(remaining > 0);
-
-                if (*f != '\\') {
-                        /* A literal, copy verbatim */
-                        *(t++) = *f;
-                        continue;
-                }
-
-                if (remaining == 1) {
-                        if (flags & UNESCAPE_RELAX) {
-                                /* A trailing backslash, copy verbatim */
-                                *(t++) = *f;
-                                continue;
-                        }
-
-                        free(r);
-                        return -EINVAL;
-                }
-
-                k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit);
-                if (k < 0) {
-                        if (flags & UNESCAPE_RELAX) {
-                                /* Invalid escape code, let's take it literal then */
-                                *(t++) = '\\';
-                                continue;
-                        }
-
-                        free(r);
-                        return k;
-                }
-
-                f += k;
-                if (eight_bit)
-                        /* One byte? Set directly as specified */
-                        *(t++) = u;
-                else
-                        /* Otherwise encode as multi-byte UTF-8 */
-                        t += utf8_encode_unichar(t, u);
-        }
-
-        *t = 0;
-
-        *ret = r;
-        return t - r;
-}
-
-int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) {
-        return cunescape_length_with_prefix(s, length, NULL, flags, ret);
-}
-
-int cunescape(const char *s, UnescapeFlags flags, char **ret) {
-        return cunescape_length(s, strlen(s), flags, ret);
-}
-
-char *xescape(const char *s, const char *bad) {
-        char *r, *t;
-        const char *f;
-
-        /* Escapes all chars in bad, in addition to \ and all special
-         * chars, in \xFF style escaping. May be reversed with
-         * cunescape(). */
-
-        r = new(char, strlen(s) * 4 + 1);
-        if (!r)
-                return NULL;
-
-        for (f = s, t = r; *f; f++) {
-
-                if ((*f < ' ') || (*f >= 127) ||
-                    (*f == '\\') || strchr(bad, *f)) {
-                        *(t++) = '\\';
-                        *(t++) = 'x';
-                        *(t++) = hexchar(*f >> 4);
-                        *(t++) = hexchar(*f);
-                } else
-                        *(t++) = *f;
-        }
-
-        *t = 0;
-
-        return r;
-}
-
-char *octescape(const char *s, size_t len) {
-        char *r, *t;
-        const char *f;
-
-        /* Escapes all chars in bad, in addition to \ and " chars,
-         * in \nnn style escaping. */
-
-        r = new(char, len * 4 + 1);
-        if (!r)
-                return NULL;
-
-        for (f = s, t = r; f < s + len; f++) {
-
-                if (*f < ' ' || *f >= 127 || IN_SET(*f, '\\', '"')) {
-                        *(t++) = '\\';
-                        *(t++) = '0' + (*f >> 6);
-                        *(t++) = '0' + ((*f >> 3) & 8);
-                        *(t++) = '0' + (*f & 8);
-                } else
-                        *(t++) = *f;
-        }
-
-        *t = 0;
-
-        return r;
-
-}
-
-static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad, bool escape_tab_nl) {
-        assert(bad);
-
-        for (; *s; s++) {
-                if (escape_tab_nl && IN_SET(*s, '\n', '\t')) {
-                        *(t++) = '\\';
-                        *(t++) = *s == '\n' ? 'n' : 't';
-                        continue;
-                }
-
-                if (*s == '\\' || strchr(bad, *s))
-                        *(t++) = '\\';
-
-                *(t++) = *s;
-        }
-
-        return t;
-}
-
-char *shell_escape(const char *s, const char *bad) {
-        char *r, *t;
-
-        r = new(char, strlen(s)*2+1);
-        if (!r)
-                return NULL;
-
-        t = strcpy_backslash_escaped(r, s, bad, false);
-        *t = 0;
-
-        return r;
-}
-
-char* shell_maybe_quote(const char *s, EscapeStyle style) {
-        const char *p;
-        char *r, *t;
-
-        assert(s);
-
-        /* Encloses a string in quotes if necessary to make it OK as a shell
-         * string. Note that we treat benign UTF-8 characters as needing
-         * escaping too, but that should be OK. */
-
-        for (p = s; *p; p++)
-                if (*p <= ' ' ||
-                    *p >= 127 ||
-                    strchr(SHELL_NEED_QUOTES, *p))
-                        break;
-
-        if (!*p)
-                return strdup(s);
-
-        r = new(char, (style == ESCAPE_POSIX) + 1 + strlen(s)*2 + 1 + 1);
-        if (!r)
-                return NULL;
-
-        t = r;
-        if (style == ESCAPE_BACKSLASH)
-                *(t++) = '"';
-        else if (style == ESCAPE_POSIX) {
-                *(t++) = '$';
-                *(t++) = '\'';
-        } else
-                assert_not_reached("Bad EscapeStyle");
-
-        t = mempcpy(t, s, p - s);
-
-        if (style == ESCAPE_BACKSLASH)
-                t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE, false);
-        else
-                t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE_POSIX, true);
-
-        if (style == ESCAPE_BACKSLASH)
-                *(t++) = '"';
-        else
-                *(t++) = '\'';
-        *t = 0;
-
-        return r;
-}
diff --git a/src/systemd/src/basic/escape.h b/src/systemd/src/basic/escape.h
deleted file mode 100644
index c612a7c0..00000000
--- a/src/systemd/src/basic/escape.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <inttypes.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/types.h>
-#if 0 /* NM_IGNORED */
-#include <uchar.h>
-#endif /* NM_IGNORED */
-
-#include "string-util.h"
-#include "missing.h"
-
-/* What characters are special in the shell? */
-/* must be escaped outside and inside double-quotes */
-#define SHELL_NEED_ESCAPE "\"\\`$"
-
-/* Those that can be escaped or double-quoted.
- *
- * Stricly speaking, ! does not need to be escaped, except in interactive
- * mode, but let's be extra nice to the user and quote ! in case this
- * output is ever used in interactive mode. */
-#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;!"
-
-/* Note that we assume control characters would need to be escaped too in
- * addition to the "special" characters listed here, if they appear in the
- * string. Current users disallow control characters. Also '"' shall not
- * be escaped.
- */
-#define SHELL_NEED_ESCAPE_POSIX "\\\'"
-
-typedef enum UnescapeFlags {
-        UNESCAPE_RELAX = 1,
-} UnescapeFlags;
-
-typedef enum EscapeStyle {
-        ESCAPE_BACKSLASH = 1,
-        ESCAPE_POSIX = 2,
-} EscapeStyle;
-
-char *cescape(const char *s);
-char *cescape_length(const char *s, size_t n);
-int cescape_char(char c, char *buf);
-
-int cunescape(const char *s, UnescapeFlags flags, char **ret);
-int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret);
-int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret);
-int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit);
-
-char *xescape(const char *s, const char *bad);
-char *octescape(const char *s, size_t len);
-
-char *shell_escape(const char *s, const char *bad);
-char* shell_maybe_quote(const char *s, EscapeStyle style);
diff --git a/src/systemd/src/basic/ether-addr-util.c b/src/systemd/src/basic/ether-addr-util.c
deleted file mode 100644
index ed92bc60..00000000
--- a/src/systemd/src/basic/ether-addr-util.c
+++ /dev/null
@@ -1,119 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <net/ethernet.h>
-#include <stdio.h>
-#include <sys/types.h>
-
-#include "ether-addr-util.h"
-#include "macro.h"
-#include "string-util.h"
-
-char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]) {
-        assert(addr);
-        assert(buffer);
-
-        /* Like ether_ntoa() but uses %02x instead of %x to print
-         * ethernet addresses, which makes them look less funny. Also,
-         * doesn't use a static buffer. */
-
-        sprintf(buffer, "%02x:%02x:%02x:%02x:%02x:%02x",
-                addr->ether_addr_octet[0],
-                addr->ether_addr_octet[1],
-                addr->ether_addr_octet[2],
-                addr->ether_addr_octet[3],
-                addr->ether_addr_octet[4],
-                addr->ether_addr_octet[5]);
-
-        return buffer;
-}
-
-int ether_addr_compare(const void *a, const void *b) {
-        assert(a);
-        assert(b);
-
-        return memcmp(a, b, ETH_ALEN);
-}
-
-static void ether_addr_hash_func(const void *p, struct siphash *state) {
-        siphash24_compress(p, sizeof(struct ether_addr), state);
-}
-
-const struct hash_ops ether_addr_hash_ops = {
-        .hash = ether_addr_hash_func,
-        .compare = ether_addr_compare
-};
-
-int ether_addr_from_string(const char *s, struct ether_addr *ret) {
-        size_t pos = 0, n, field;
-        char sep = '\0';
-        const char *hex = HEXDIGITS, *hexoff;
-        size_t x;
-        bool touched;
-
-#define parse_fields(v)                                         \
-        for (field = 0; field < ELEMENTSOF(v); field++) {       \
-                touched = false;                                \
-                for (n = 0; n < (2 * sizeof(v[0])); n++) {      \
-                        if (s[pos] == '\0')                     \
-                                break;                          \
-                        hexoff = strchr(hex, s[pos]);           \
-                        if (!hexoff)                            \
-                                break;                          \
-                        assert(hexoff >= hex);                  \
-                        x = hexoff - hex;                       \
-                        if (x >= 16)                            \
-                                x -= 6; /* A-F */               \
-                        assert(x < 16);                         \
-                        touched = true;                         \
-                        v[field] <<= 4;                         \
-                        v[field] += x;                          \
-                        pos++;                                  \
-                }                                               \
-                if (!touched)                                   \
-                        return -EINVAL;                         \
-                if (field < (ELEMENTSOF(v)-1)) {                \
-                        if (s[pos] != sep)                      \
-                                return -EINVAL;                 \
-                        else                                    \
-                                pos++;                          \
-                }                                               \
-        }
-
-        assert(s);
-        assert(ret);
-
-        s += strspn(s, WHITESPACE);
-        sep = s[strspn(s, hex)];
-
-        if (sep == '.') {
-                uint16_t shorts[3] = { 0 };
-
-                parse_fields(shorts);
-
-                if (s[pos] != '\0')
-                        return -EINVAL;
-
-                for (n = 0; n < ELEMENTSOF(shorts); n++) {
-                        ret->ether_addr_octet[2*n] = ((shorts[n] & (uint16_t)0xff00) >> 8);
-                        ret->ether_addr_octet[2*n + 1] = (shorts[n] & (uint16_t)0x00ff);
-                }
-
-        } else if (IN_SET(sep, ':', '-')) {
-                struct ether_addr out = ETHER_ADDR_NULL;
-
-                parse_fields(out.ether_addr_octet);
-
-                if (s[pos] != '\0')
-                        return -EINVAL;
-
-                for (n = 0; n < ELEMENTSOF(out.ether_addr_octet); n++)
-                        ret->ether_addr_octet[n] = out.ether_addr_octet[n];
-
-        } else
-                return -EINVAL;
-
-        return 0;
-}
diff --git a/src/systemd/src/basic/ether-addr-util.h b/src/systemd/src/basic/ether-addr-util.h
deleted file mode 100644
index 3be03700..00000000
--- a/src/systemd/src/basic/ether-addr-util.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <net/ethernet.h>
-#include <stdbool.h>
-
-#include "hash-funcs.h"
-
-#define ETHER_ADDR_FORMAT_STR "%02X%02X%02X%02X%02X%02X"
-#define ETHER_ADDR_FORMAT_VAL(x) (x).ether_addr_octet[0], (x).ether_addr_octet[1], (x).ether_addr_octet[2], (x).ether_addr_octet[3], (x).ether_addr_octet[4], (x).ether_addr_octet[5]
-
-#define ETHER_ADDR_TO_STRING_MAX (3*6)
-char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]);
-
-int ether_addr_compare(const void *a, const void *b);
-static inline bool ether_addr_equal(const struct ether_addr *a, const struct ether_addr *b) {
-        return ether_addr_compare(a, b) == 0;
-}
-
-#define ETHER_ADDR_NULL ((const struct ether_addr){})
-
-static inline bool ether_addr_is_null(const struct ether_addr *addr) {
-        return ether_addr_equal(addr, &ETHER_ADDR_NULL);
-}
-
-int ether_addr_from_string(const char *s, struct ether_addr *ret);
-
-extern const struct hash_ops ether_addr_hash_ops;
diff --git a/src/systemd/src/basic/extract-word.c b/src/systemd/src/basic/extract-word.c
deleted file mode 100644
index 404fe615..00000000
--- a/src/systemd/src/basic/extract-word.c
+++ /dev/null
@@ -1,289 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-
-#include "alloc-util.h"
-#include "escape.h"
-#include "extract-word.h"
-#include "log.h"
-#include "macro.h"
-#include "string-util.h"
-#include "utf8.h"
-
-int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) {
-        _cleanup_free_ char *s = NULL;
-        size_t allocated = 0, sz = 0;
-        char c;
-        int r;
-
-        char quote = 0;                 /* 0 or ' or " */
-        bool backslash = false;         /* whether we've just seen a backslash */
-
-        assert(p);
-        assert(ret);
-
-        /* Bail early if called after last value or with no input */
-        if (!*p)
-                goto finish;
-        c = **p;
-
-        if (!separators)
-                separators = WHITESPACE;
-
-        /* Parses the first word of a string, and returns it in
-         * *ret. Removes all quotes in the process. When parsing fails
-         * (because of an uneven number of quotes or similar), leaves
-         * the pointer *p at the first invalid character. */
-
-        if (flags & EXTRACT_DONT_COALESCE_SEPARATORS)
-                if (!GREEDY_REALLOC(s, allocated, sz+1))
-                        return -ENOMEM;
-
-        for (;; (*p)++, c = **p) {
-                if (c == 0)
-                        goto finish_force_terminate;
-                else if (strchr(separators, c)) {
-                        if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
-                                (*p)++;
-                                goto finish_force_next;
-                        }
-                } else {
-                        /* We found a non-blank character, so we will always
-                         * want to return a string (even if it is empty),
-                         * allocate it here. */
-                        if (!GREEDY_REALLOC(s, allocated, sz+1))
-                                return -ENOMEM;
-                        break;
-                }
-        }
-
-        for (;; (*p)++, c = **p) {
-                if (backslash) {
-                        if (!GREEDY_REALLOC(s, allocated, sz+7))
-                                return -ENOMEM;
-
-                        if (c == 0) {
-                                if ((flags & EXTRACT_CUNESCAPE_RELAX) &&
-                                    (!quote || flags & EXTRACT_RELAX)) {
-                                        /* If we find an unquoted trailing backslash and we're in
-                                         * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the
-                                         * output.
-                                         *
-                                         * Unbalanced quotes will only be allowed in EXTRACT_RELAX
-                                         * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them.
-                                         */
-                                        s[sz++] = '\\';
-                                        goto finish_force_terminate;
-                                }
-                                if (flags & EXTRACT_RELAX)
-                                        goto finish_force_terminate;
-                                return -EINVAL;
-                        }
-
-                        if (flags & EXTRACT_CUNESCAPE) {
-                                bool eight_bit = false;
-                                char32_t u;
-
-                                r = cunescape_one(*p, (size_t) -1, &u, &eight_bit);
-                                if (r < 0) {
-                                        if (flags & EXTRACT_CUNESCAPE_RELAX) {
-                                                s[sz++] = '\\';
-                                                s[sz++] = c;
-                                        } else
-                                                return -EINVAL;
-                                } else {
-                                        (*p) += r - 1;
-
-                                        if (eight_bit)
-                                                s[sz++] = u;
-                                        else
-                                                sz += utf8_encode_unichar(s + sz, u);
-                                }
-                        } else
-                                s[sz++] = c;
-
-                        backslash = false;
-
-                } else if (quote) {     /* inside either single or double quotes */
-                        for (;; (*p)++, c = **p) {
-                                if (c == 0) {
-                                        if (flags & EXTRACT_RELAX)
-                                                goto finish_force_terminate;
-                                        return -EINVAL;
-                                } else if (c == quote) {        /* found the end quote */
-                                        quote = 0;
-                                        break;
-                                } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
-                                        backslash = true;
-                                        break;
-                                } else {
-                                        if (!GREEDY_REALLOC(s, allocated, sz+2))
-                                                return -ENOMEM;
-
-                                        s[sz++] = c;
-                                }
-                        }
-
-                } else {
-                        for (;; (*p)++, c = **p) {
-                                if (c == 0)
-                                        goto finish_force_terminate;
-                                else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_QUOTES)) {
-                                        quote = c;
-                                        break;
-                                } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
-                                        backslash = true;
-                                        break;
-                                } else if (strchr(separators, c)) {
-                                        if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
-                                                (*p)++;
-                                                goto finish_force_next;
-                                        }
-                                        /* Skip additional coalesced separators. */
-                                        for (;; (*p)++, c = **p) {
-                                                if (c == 0)
-                                                        goto finish_force_terminate;
-                                                if (!strchr(separators, c))
-                                                        break;
-                                        }
-                                        goto finish;
-
-                                } else {
-                                        if (!GREEDY_REALLOC(s, allocated, sz+2))
-                                                return -ENOMEM;
-
-                                        s[sz++] = c;
-                                }
-                        }
-                }
-        }
-
-finish_force_terminate:
-        *p = NULL;
-finish:
-        if (!s) {
-                *p = NULL;
-                *ret = NULL;
-                return 0;
-        }
-
-finish_force_next:
-        s[sz] = 0;
-        *ret = TAKE_PTR(s);
-
-        return 1;
-}
-
-#if 0 /* NM_IGNORED */
-int extract_first_word_and_warn(
-                const char **p,
-                char **ret,
-                const char *separators,
-                ExtractFlags flags,
-                const char *unit,
-                const char *filename,
-                unsigned line,
-                const char *rvalue) {
-
-        /* Try to unquote it, if it fails, warn about it and try again
-         * but this time using EXTRACT_CUNESCAPE_RELAX to keep the
-         * backslashes verbatim in invalid escape sequences. */
-
-        const char *save;
-        int r;
-
-        save = *p;
-        r = extract_first_word(p, ret, separators, flags);
-        if (r >= 0)
-                return r;
-
-        if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) {
-
-                /* Retry it with EXTRACT_CUNESCAPE_RELAX. */
-                *p = save;
-                r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX);
-                if (r >= 0) {
-                        /* It worked this time, hence it must have been an invalid escape sequence. */
-                        log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Ignoring unknown escape sequences: \"%s\"", *ret);
-                        return r;
-                }
-
-                /* If it's still EINVAL; then it must be unbalanced quoting, report this. */
-                if (r == -EINVAL)
-                        return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue);
-        }
-
-        /* Can be any error, report it */
-        return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue);
-}
-
-/* We pass ExtractFlags as unsigned int (to avoid undefined behaviour when passing
- * an object that undergoes default argument promotion as an argument to va_start).
- * Let's make sure that ExtractFlags fits into an unsigned int. */
-assert_cc(sizeof(enum ExtractFlags) <= sizeof(unsigned));
-
-int extract_many_words(const char **p, const char *separators, unsigned flags, ...) {
-        va_list ap;
-        char **l;
-        int n = 0, i, c, r;
-
-        /* Parses a number of words from a string, stripping any
-         * quotes if necessary. */
-
-        assert(p);
-
-        /* Count how many words are expected */
-        va_start(ap, flags);
-        for (;;) {
-                if (!va_arg(ap, char **))
-                        break;
-                n++;
-        }
-        va_end(ap);
-
-        if (n <= 0)
-                return 0;
-
-        /* Read all words into a temporary array */
-        l = newa0(char*, n);
-        for (c = 0; c < n; c++) {
-
-                r = extract_first_word(p, &l[c], separators, flags);
-                if (r < 0) {
-                        int j;
-
-                        for (j = 0; j < c; j++)
-                                free(l[j]);
-
-                        return r;
-                }
-
-                if (r == 0)
-                        break;
-        }
-
-        /* If we managed to parse all words, return them in the passed
-         * in parameters */
-        va_start(ap, flags);
-        for (i = 0; i < n; i++) {
-                char **v;
-
-                v = va_arg(ap, char **);
-                assert(v);
-
-                *v = l[i];
-        }
-        va_end(ap);
-
-        return c;
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/extract-word.h b/src/systemd/src/basic/extract-word.h
deleted file mode 100644
index 8c63b7c3..00000000
--- a/src/systemd/src/basic/extract-word.h
+++ /dev/null
@@ -1,17 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include "macro.h"
-
-typedef enum ExtractFlags {
-        EXTRACT_RELAX                    = 1,
-        EXTRACT_CUNESCAPE                = 2,
-        EXTRACT_CUNESCAPE_RELAX          = 4,
-        EXTRACT_QUOTES                   = 8,
-        EXTRACT_DONT_COALESCE_SEPARATORS = 16,
-        EXTRACT_RETAIN_ESCAPE            = 32,
-} ExtractFlags;
-
-int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags);
-int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue);
-int extract_many_words(const char **p, const char *separators, unsigned flags, ...) _sentinel_;
diff --git a/src/systemd/src/basic/fd-util.c b/src/systemd/src/basic/fd-util.c
deleted file mode 100644
index 71babe2f..00000000
--- a/src/systemd/src/basic/fd-util.c
+++ /dev/null
@@ -1,959 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/resource.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include "alloc-util.h"
-#include "copy.h"
-#include "dirent-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "fs-util.h"
-#include "io-util.h"
-#include "macro.h"
-#include "memfd-util.h"
-#include "missing.h"
-#include "parse-util.h"
-#include "path-util.h"
-#include "process-util.h"
-#include "socket-util.h"
-#include "stdio-util.h"
-#include "util.h"
-
-int close_nointr(int fd) {
-        assert(fd >= 0);
-
-        if (close(fd) >= 0)
-                return 0;
-
-        /*
-         * Just ignore EINTR; a retry loop is the wrong thing to do on
-         * Linux.
-         *
-         * http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html
-         * https://bugzilla.gnome.org/show_bug.cgi?id=682819
-         * http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR
-         * https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain
-         */
-        if (errno == EINTR)
-                return 0;
-
-        return -errno;
-}
-
-int safe_close(int fd) {
-
-        /*
-         * Like close_nointr() but cannot fail. Guarantees errno is
-         * unchanged. Is a NOP with negative fds passed, and returns
-         * -1, so that it can be used in this syntax:
-         *
-         * fd = safe_close(fd);
-         */
-
-        if (fd >= 0) {
-                PROTECT_ERRNO;
-
-                /* The kernel might return pretty much any error code
-                 * via close(), but the fd will be closed anyway. The
-                 * only condition we want to check for here is whether
-                 * the fd was invalid at all... */
-
-                assert_se(close_nointr(fd) != -EBADF);
-        }
-
-        return -1;
-}
-
-void safe_close_pair(int p[]) {
-        assert(p);
-
-        if (p[0] == p[1]) {
-                /* Special case pairs which use the same fd in both
-                 * directions... */
-                p[0] = p[1] = safe_close(p[0]);
-                return;
-        }
-
-        p[0] = safe_close(p[0]);
-        p[1] = safe_close(p[1]);
-}
-
-void close_many(const int fds[], size_t n_fd) {
-        size_t i;
-
-        assert(fds || n_fd <= 0);
-
-        for (i = 0; i < n_fd; i++)
-                safe_close(fds[i]);
-}
-
-int fclose_nointr(FILE *f) {
-        assert(f);
-
-        /* Same as close_nointr(), but for fclose() */
-
-        if (fclose(f) == 0)
-                return 0;
-
-        if (errno == EINTR)
-                return 0;
-
-        return -errno;
-}
-
-FILE* safe_fclose(FILE *f) {
-
-        /* Same as safe_close(), but for fclose() */
-
-        if (f) {
-                PROTECT_ERRNO;
-
-                assert_se(fclose_nointr(f) != EBADF);
-        }
-
-        return NULL;
-}
-
-DIR* safe_closedir(DIR *d) {
-
-        if (d) {
-                PROTECT_ERRNO;
-
-                assert_se(closedir(d) >= 0 || errno != EBADF);
-        }
-
-        return NULL;
-}
-
-int fd_nonblock(int fd, bool nonblock) {
-        int flags, nflags;
-
-        assert(fd >= 0);
-
-        flags = fcntl(fd, F_GETFL, 0);
-        if (flags < 0)
-                return -errno;
-
-        if (nonblock)
-                nflags = flags | O_NONBLOCK;
-        else
-                nflags = flags & ~O_NONBLOCK;
-
-        if (nflags == flags)
-                return 0;
-
-        if (fcntl(fd, F_SETFL, nflags) < 0)
-                return -errno;
-
-        return 0;
-}
-
-int fd_cloexec(int fd, bool cloexec) {
-        int flags, nflags;
-
-        assert(fd >= 0);
-
-        flags = fcntl(fd, F_GETFD, 0);
-        if (flags < 0)
-                return -errno;
-
-        if (cloexec)
-                nflags = flags | FD_CLOEXEC;
-        else
-                nflags = flags & ~FD_CLOEXEC;
-
-        if (nflags == flags)
-                return 0;
-
-        if (fcntl(fd, F_SETFD, nflags) < 0)
-                return -errno;
-
-        return 0;
-}
-
-#if 0 /* NM_IGNORED */
-_pure_ static bool fd_in_set(int fd, const int fdset[], size_t n_fdset) {
-        size_t i;
-
-        assert(n_fdset == 0 || fdset);
-
-        for (i = 0; i < n_fdset; i++)
-                if (fdset[i] == fd)
-                        return true;
-
-        return false;
-}
-
-int close_all_fds(const int except[], size_t n_except) {
-        _cleanup_closedir_ DIR *d = NULL;
-        struct dirent *de;
-        int r = 0;
-
-        assert(n_except == 0 || except);
-
-        d = opendir("/proc/self/fd");
-        if (!d) {
-                struct rlimit rl;
-                int fd, max_fd;
-
-                /* When /proc isn't available (for example in chroots) the fallback is brute forcing through the fd
-                 * table */
-
-                assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0);
-
-                if (rl.rlim_max == 0)
-                        return -EINVAL;
-
-                /* Let's take special care if the resource limit is set to unlimited, or actually larger than the range
-                 * of 'int'. Let's avoid implicit overflows. */
-                max_fd = (rl.rlim_max == RLIM_INFINITY || rl.rlim_max > INT_MAX) ? INT_MAX : (int) (rl.rlim_max - 1);
-
-                for (fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) {
-                        int q;
-
-                        if (fd_in_set(fd, except, n_except))
-                                continue;
-
-                        q = close_nointr(fd);
-                        if (q < 0 && q != -EBADF && r >= 0)
-                                r = q;
-                }
-
-                return r;
-        }
-
-        FOREACH_DIRENT(de, d, return -errno) {
-                int fd = -1, q;
-
-                if (safe_atoi(de->d_name, &fd) < 0)
-                        /* Let's better ignore this, just in case */
-                        continue;
-
-                if (fd < 3)
-                        continue;
-
-                if (fd == dirfd(d))
-                        continue;
-
-                if (fd_in_set(fd, except, n_except))
-                        continue;
-
-                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;
-}
-
-int same_fd(int a, int b) {
-        struct stat sta, stb;
-        pid_t pid;
-        int r, fa, fb;
-
-        assert(a >= 0);
-        assert(b >= 0);
-
-        /* Compares two file descriptors. Note that semantics are
-         * quite different depending on whether we have kcmp() or we
-         * don't. If we have kcmp() this will only return true for
-         * dup()ed file descriptors, but not otherwise. If we don't
-         * have kcmp() this will also return true for two fds of the same
-         * file, created by separate open() calls. Since we use this
-         * call mostly for filtering out duplicates in the fd store
-         * this difference hopefully doesn't matter too much. */
-
-        if (a == b)
-                return true;
-
-        /* Try to use kcmp() if we have it. */
-        pid = getpid_cached();
-        r = kcmp(pid, pid, KCMP_FILE, a, b);
-        if (r == 0)
-                return true;
-        if (r > 0)
-                return false;
-        if (errno != ENOSYS)
-                return -errno;
-
-        /* We don't have kcmp(), use fstat() instead. */
-        if (fstat(a, &sta) < 0)
-                return -errno;
-
-        if (fstat(b, &stb) < 0)
-                return -errno;
-
-        if ((sta.st_mode & S_IFMT) != (stb.st_mode & S_IFMT))
-                return false;
-
-        /* We consider all device fds different, since two device fds
-         * might refer to quite different device contexts even though
-         * they share the same inode and backing dev_t. */
-
-        if (S_ISCHR(sta.st_mode) || S_ISBLK(sta.st_mode))
-                return false;
-
-        if (sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino)
-                return false;
-
-        /* The fds refer to the same inode on disk, let's also check
-         * if they have the same fd flags. This is useful to
-         * distinguish the read and write side of a pipe created with
-         * pipe(). */
-        fa = fcntl(a, F_GETFL);
-        if (fa < 0)
-                return -errno;
-
-        fb = fcntl(b, F_GETFL);
-        if (fb < 0)
-                return -errno;
-
-        return fa == fb;
-}
-#endif /* NM_IGNORED */
-
-void cmsg_close_all(struct msghdr *mh) {
-        struct cmsghdr *cmsg;
-
-        assert(mh);
-
-        CMSG_FOREACH(cmsg, mh)
-                if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
-                        close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int));
-}
-
-bool fdname_is_valid(const char *s) {
-        const char *p;
-
-        /* Validates a name for $LISTEN_FDNAMES. We basically allow
-         * everything ASCII that's not a control character. Also, as
-         * special exception the ":" character is not allowed, as we
-         * use that as field separator in $LISTEN_FDNAMES.
-         *
-         * Note that the empty string is explicitly allowed
-         * here. However, we limit the length of the names to 255
-         * characters. */
-
-        if (!s)
-                return false;
-
-        for (p = s; *p; p++) {
-                if (*p < ' ')
-                        return false;
-                if (*p >= 127)
-                        return false;
-                if (*p == ':')
-                        return false;
-        }
-
-        return p - s < 256;
-}
-
-int fd_get_path(int fd, char **ret) {
-        _cleanup_close_ int dir = -1;
-        char fdname[DECIMAL_STR_MAX(int)];
-        int r;
-
-        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;
-
-        xsprintf(fdname, "%i", fd);
-
-        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;
-}
-
-/* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */
-#define DATA_FD_MEMORY_LIMIT (64U*1024U)
-
-/* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */
-#define DATA_FD_TMP_LIMIT (1024U*1024U)
-
-int fd_duplicate_data_fd(int fd) {
-
-        _cleanup_close_ int copy_fd = -1, tmp_fd = -1;
-        _cleanup_free_ void *remains = NULL;
-        size_t remains_size = 0;
-        const char *td;
-        struct stat st;
-        int r;
-
-        /* Creates a 'data' fd from the specified source fd, containing all the same data in a read-only fashion, but
-         * independent of it (i.e. the source fd can be closed and unmounted after this call succeeded). Tries to be
-         * somewhat smart about where to place the data. In the best case uses a memfd(). If memfd() are not supported
-         * uses a pipe instead. For larger data will use an unlinked file in /tmp, and for even larger data one in
-         * /var/tmp. */
-
-        if (fstat(fd, &st) < 0)
-                return -errno;
-
-        /* For now, let's only accept regular files, sockets, pipes and char devices */
-        if (S_ISDIR(st.st_mode))
-                return -EISDIR;
-        if (S_ISLNK(st.st_mode))
-                return -ELOOP;
-        if (!S_ISREG(st.st_mode) && !S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode) && !S_ISCHR(st.st_mode))
-                return -EBADFD;
-
-        /* If we have reason to believe the data is bounded in size, then let's use memfds or pipes as backing fd. Note
-         * that we use the reported regular file size only as a hint, given that there are plenty special files in
-         * /proc and /sys which report a zero file size but can be read from. */
-
-        if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) {
-
-                /* Try a memfd first */
-                copy_fd = memfd_new("data-fd");
-                if (copy_fd >= 0) {
-                        off_t f;
-
-                        r = copy_bytes(fd, copy_fd, DATA_FD_MEMORY_LIMIT, 0);
-                        if (r < 0)
-                                return r;
-
-                        f = lseek(copy_fd, 0, SEEK_SET);
-                        if (f != 0)
-                                return -errno;
-
-                        if (r == 0) {
-                                /* Did it fit into the limit? If so, we are done. */
-                                r = memfd_set_sealed(copy_fd);
-                                if (r < 0)
-                                        return r;
-
-                                return TAKE_FD(copy_fd);
-                        }
-
-                        /* Hmm, pity, this didn't fit. Let's fall back to /tmp then, see below */
-
-                } else {
-                        _cleanup_(close_pairp) int pipefds[2] = { -1, -1 };
-                        int isz;
-
-                        /* If memfds aren't available, use a pipe. Set O_NONBLOCK so that we will get EAGAIN rather
-                         * then block indefinitely when we hit the pipe size limit */
-
-                        if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0)
-                                return -errno;
-
-                        isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
-                        if (isz < 0)
-                                return -errno;
-
-                        /* Try to enlarge the pipe size if necessary */
-                        if ((size_t) isz < DATA_FD_MEMORY_LIMIT) {
-
-                                (void) fcntl(pipefds[1], F_SETPIPE_SZ, DATA_FD_MEMORY_LIMIT);
-
-                                isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
-                                if (isz < 0)
-                                        return -errno;
-                        }
-
-                        if ((size_t) isz >= DATA_FD_MEMORY_LIMIT) {
-
-                                r = copy_bytes_full(fd, pipefds[1], DATA_FD_MEMORY_LIMIT, 0, &remains, &remains_size);
-                                if (r < 0 && r != -EAGAIN)
-                                        return r; /* If we get EAGAIN it could be because of the source or because of
-                                                   * the destination fd, we can't know, as sendfile() and friends won't
-                                                   * tell us. Hence, treat this as reason to fall back, just to be
-                                                   * sure. */
-                                if (r == 0) {
-                                        /* Everything fit in, yay! */
-                                        (void) fd_nonblock(pipefds[0], false);
-
-                                        return TAKE_FD(pipefds[0]);
-                                }
-
-                                /* Things didn't fit in. But we read data into the pipe, let's remember that, so that
-                                 * when writing the new file we incorporate this first. */
-                                copy_fd = TAKE_FD(pipefds[0]);
-                        }
-                }
-        }
-
-        /* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */
-        if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) &&
-            (DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) {
-                off_t f;
-
-                tmp_fd = open_tmpfile_unlinkable(NULL /* NULL as directory means /tmp */, O_RDWR|O_CLOEXEC);
-                if (tmp_fd < 0)
-                        return tmp_fd;
-
-                if (copy_fd >= 0) {
-                        /* If we tried a memfd/pipe first and it ended up being too large, then copy this into the
-                         * temporary file first. */
-
-                        r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, 0);
-                        if (r < 0)
-                                return r;
-
-                        assert(r == 0);
-                }
-
-                if (remains_size > 0) {
-                        /* If there were remaining bytes (i.e. read into memory, but not written out yet) from the
-                         * failed copy operation, let's flush them out next. */
-
-                        r = loop_write(tmp_fd, remains, remains_size, false);
-                        if (r < 0)
-                                return r;
-                }
-
-                r = copy_bytes(fd, tmp_fd, DATA_FD_TMP_LIMIT - DATA_FD_MEMORY_LIMIT - remains_size, COPY_REFLINK);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        goto finish;  /* Yay, it fit in */
-
-                /* It didn't fit in. Let's not forget to use what we already used */
-                f = lseek(tmp_fd, 0, SEEK_SET);
-                if (f != 0)
-                        return -errno;
-
-                safe_close(copy_fd);
-                copy_fd = TAKE_FD(tmp_fd);
-
-                remains = mfree(remains);
-                remains_size = 0;
-        }
-
-        /* As last fallback use /var/tmp */
-        r = var_tmp_dir(&td);
-        if (r < 0)
-                return r;
-
-        tmp_fd = open_tmpfile_unlinkable(td, O_RDWR|O_CLOEXEC);
-        if (tmp_fd < 0)
-                return tmp_fd;
-
-        if (copy_fd >= 0) {
-                /* If we tried a memfd/pipe first, or a file in /tmp, and it ended up being too large, than copy this
-                 * into the temporary file first. */
-                r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
-                if (r < 0)
-                        return r;
-
-                assert(r == 0);
-        }
-
-        if (remains_size > 0) {
-                /* Then, copy in any read but not yet written bytes. */
-                r = loop_write(tmp_fd, remains, remains_size, false);
-                if (r < 0)
-                        return r;
-        }
-
-        /* Copy in the rest */
-        r = copy_bytes(fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
-        if (r < 0)
-                return r;
-
-        assert(r == 0);
-
-finish:
-        /* Now convert the O_RDWR file descriptor into an O_RDONLY one (and as side effect seek to the beginning of the
-         * file again */
-
-        return fd_reopen(tmp_fd, O_RDONLY|O_CLOEXEC);
-}
-#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;
-}
-
-int read_nr_open(void) {
-        _cleanup_free_ char *nr_open = NULL;
-        int r;
-
-        /* Returns the kernel's current fd limit, either by reading it of /proc/sys if that works, or using the
-         * hard-coded default compiled-in value of current kernels (1M) if not. This call will never fail. */
-
-        r = read_one_line_file("/proc/sys/fs/nr_open", &nr_open);
-        if (r < 0)
-                log_debug_errno(r, "Failed to read /proc/sys/fs/nr_open, ignoring: %m");
-        else {
-                int v;
-
-                r = safe_atoi(nr_open, &v);
-                if (r < 0)
-                        log_debug_errno(r, "Failed to parse /proc/sys/fs/nr_open value '%s', ignoring: %m", nr_open);
-                else
-                        return v;
-        }
-
-        /* If we fail, fallback to the hard-coded kernel limit of 1024 * 1024. */
-        return 1024 * 1024;
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/fd-util.h b/src/systemd/src/basic/fd-util.h
deleted file mode 100644
index 00303a7e..00000000
--- a/src/systemd/src/basic/fd-util.h
+++ /dev/null
@@ -1,110 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <dirent.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <sys/socket.h>
-
-#include "macro.h"
-
-/* Make sure we can distinguish fd 0 and NULL */
-#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1)
-#define PTR_TO_FD(p) (PTR_TO_INT(p)-1)
-
-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[], size_t n_fd);
-
-int fclose_nointr(FILE *f);
-FILE* safe_fclose(FILE *f);
-DIR* safe_closedir(DIR *f);
-
-static inline void closep(int *fd) {
-        safe_close(*fd);
-}
-
-static inline void close_pairp(int (*p)[2]) {
-        safe_close_pair(*p);
-}
-
-static inline void fclosep(FILE **f) {
-        safe_fclose(*f);
-}
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose);
-DEFINE_TRIVIAL_CLEANUP_FUNC(DIR*, closedir);
-
-#define _cleanup_close_ _cleanup_(closep)
-#define _cleanup_fclose_ _cleanup_(fclosep)
-#define _cleanup_pclose_ _cleanup_(pclosep)
-#define _cleanup_closedir_ _cleanup_(closedirp)
-#define _cleanup_close_pair_ _cleanup_(close_pairp)
-
-int fd_nonblock(int fd, bool nonblock);
-int fd_cloexec(int fd, bool cloexec);
-
-int close_all_fds(const int except[], size_t n_except);
-
-int same_fd(int a, int b);
-
-void cmsg_close_all(struct msghdr *mh);
-
-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);
-
-int fd_duplicate_data_fd(int fd);
-
-/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */
-/* The kernel sends e.g., EHOSTUNREACH or ENONET to userspace in some ICMP error cases.
- * See the icmp_err_convert[] in net/ipv4/icmp.c in the kernel sources */
-#define ERRNO_IS_DISCONNECT(r)                                          \
-        IN_SET(r,                                                       \
-               ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, \
-               ENETUNREACH, EHOSTUNREACH, ENOPROTOOPT, EHOSTDOWN, ENONET)
-
-/* Resource exhaustion, could be our fault or general system trouble */
-#define ERRNO_IS_RESOURCE(r) \
-        IN_SET(r, ENOMEM, EMFILE, ENFILE)
-
-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);
-
-int read_nr_open(void);
diff --git a/src/systemd/src/basic/fileio.c b/src/systemd/src/basic/fileio.c
deleted file mode 100644
index 3978c3a7..00000000
--- a/src/systemd/src/basic/fileio.c
+++ /dev/null
@@ -1,1671 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#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>
-
-#include "alloc-util.h"
-#include "ctype.h"
-#include "def.h"
-#include "env-util.h"
-#include "escape.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "fs-util.h"
-#include "hexdecoct.h"
-#include "log.h"
-#include "macro.h"
-#include "missing.h"
-#include "parse-util.h"
-#include "path-util.h"
-#include "process-util.h"
-#include "random-util.h"
-#include "stdio-util.h"
-#include "string-util.h"
-#include "strv.h"
-#include "time-util.h"
-#include "umask-util.h"
-#include "utf8.h"
-
-#define READ_FULL_BYTES_MAX (4U*1024U*1024U)
-
-#if 0 /* NM_IGNORED */
-int write_string_stream_ts(
-                FILE *f,
-                const char *line,
-                WriteStringFileFlags flags,
-                struct timespec *ts) {
-
-        bool needs_nl;
-        int r;
-
-        assert(f);
-        assert(line);
-
-        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 (flags & WRITE_STRING_FILE_SYNC)
-                r = fflush_sync_and_check(f);
-        else
-                r = fflush_and_check(f);
-        if (r < 0)
-                return r;
-
-        if (ts) {
-                struct timespec twice[2] = {*ts, *ts};
-
-                if (futimens(fileno(f), twice) < 0)
-                        return -errno;
-        }
-
-        return 0;
-}
-
-static int write_string_file_atomic(
-                const char *fn,
-                const char *line,
-                WriteStringFileFlags flags,
-                struct timespec *ts) {
-
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_free_ char *p = NULL;
-        int r;
-
-        assert(fn);
-        assert(line);
-
-        r = fopen_temporary(fn, &f, &p);
-        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);
-        if (r < 0)
-                goto fail;
-
-        if (rename(p, fn) < 0) {
-                r = -errno;
-                goto fail;
-        }
-
-        return 0;
-
-fail:
-        (void) unlink(p);
-        return r;
-}
-
-int write_string_file_ts(
-                const char *fn,
-                const char *line,
-                WriteStringFileFlags flags,
-                struct timespec *ts) {
-
-        _cleanup_fclose_ FILE *f = NULL;
-        int q, r;
-
-        assert(fn);
-        assert(line);
-
-        /* We don't know how to verify whether the file contents was already on-disk. */
-        assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC)));
-
-        if (flags & WRITE_STRING_FILE_ATOMIC) {
-                assert(flags & WRITE_STRING_FILE_CREATE);
-
-                r = write_string_file_atomic(fn, line, flags, ts);
-                if (r < 0)
-                        goto fail;
-
-                return r;
-        } else
-                assert(!ts);
-
-        if (flags & WRITE_STRING_FILE_CREATE) {
-                f = fopen(fn, "we");
-                if (!f) {
-                        r = -errno;
-                        goto fail;
-                }
-        } else {
-                int fd;
-
-                /* We manually build our own version of fopen(..., "we") that
-                 * works without O_CREAT */
-                fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY);
-                if (fd < 0) {
-                        r = -errno;
-                        goto fail;
-                }
-
-                f = fdopen(fd, "we");
-                if (!f) {
-                        r = -errno;
-                        safe_close(fd);
-                        goto fail;
-                }
-        }
-
-        (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;
-
-        return 0;
-
-fail:
-        if (!(flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE))
-                return r;
-
-        f = safe_fclose(f);
-
-        /* OK, the operation failed, but let's see if the right
-         * contents in place already. If so, eat up the error. */
-
-        q = verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE));
-        if (q <= 0)
-                return r;
-
-        return 0;
-}
-
-int write_string_filef(
-                const char *fn,
-                WriteStringFileFlags flags,
-                const char *format, ...) {
-
-        _cleanup_free_ char *p = NULL;
-        va_list ap;
-        int r;
-
-        va_start(ap, format);
-        r = vasprintf(&p, format, ap);
-        va_end(ap);
-
-        if (r < 0)
-                return -ENOMEM;
-
-        return write_string_file(fn, p, flags);
-}
-
-int read_one_line_file(const char *fn, char **line) {
-        _cleanup_fclose_ FILE *f = NULL;
-        int r;
-
-        assert(fn);
-        assert(line);
-
-        f = fopen(fn, "re");
-        if (!f)
-                return -errno;
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-        r = read_line(f, LONG_LINE_MAX, line);
-        return r < 0 ? r : 0;
-}
-
-int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_free_ char *buf = NULL;
-        size_t l, k;
-
-        assert(fn);
-        assert(blob);
-
-        l = strlen(blob);
-
-        if (accept_extra_nl && endswith(blob, "\n"))
-                accept_extra_nl = false;
-
-        buf = malloc(l + accept_extra_nl + 1);
-        if (!buf)
-                return -ENOMEM;
-
-        f = fopen(fn, "re");
-        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);
-        if (ferror(f))
-                return errno > 0 ? -errno : -EIO;
-
-        if (k != l && k != l + accept_extra_nl)
-                return 0;
-        if (memcmp(buf, blob, l) != 0)
-                return 0;
-        if (k > l && buf[l] != '\n')
-                return 0;
-
-        return 1;
-}
-#endif /* NM_IGNORED */
-
-int read_full_stream(FILE *f, char **contents, size_t *size) {
-        _cleanup_free_ char *buf = NULL;
-        struct stat st;
-        size_t n, l;
-        int fd;
-
-        assert(f);
-        assert(contents);
-
-        n = LINE_MAX;
-
-        fd = fileno(f);
-        if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen(), let's
-                        * optimize our buffering) */
-
-                if (fstat(fileno(f), &st) < 0)
-                        return -errno;
-
-                if (S_ISREG(st.st_mode)) {
-
-                        /* Safety check */
-                        if (st.st_size > READ_FULL_BYTES_MAX)
-                                return -E2BIG;
-
-                        /* Start with the right file size, but be prepared for files from /proc which generally report a file
-                         * size of 0. Note that we increase the size to read here by one, so that the first read attempt
-                         * already makes us notice the EOF. */
-                        if (st.st_size > 0)
-                                n = st.st_size + 1;
-                }
-        }
-
-        l = 0;
-        for (;;) {
-                char *t;
-                size_t k;
-
-                t = realloc(buf, n + 1);
-                if (!t)
-                        return -ENOMEM;
-
-                buf = t;
-                errno = 0;
-                k = fread(buf + l, 1, n - l, f);
-                if (k > 0)
-                        l += k;
-
-                if (ferror(f))
-                        return errno > 0 ? -errno : -EIO;
-
-                if (feof(f))
-                        break;
-
-                /* We aren't expecting fread() to return a short read outside
-                 * of (error && eof), assert buffer is full and enlarge buffer.
-                 */
-                assert(l == n);
-
-                /* Safety check */
-                if (n >= READ_FULL_BYTES_MAX)
-                        return -E2BIG;
-
-                n = MIN(n * 2, READ_FULL_BYTES_MAX);
-        }
-
-        buf[l] = 0;
-        *contents = TAKE_PTR(buf);
-
-        if (size)
-                *size = l;
-
-        return 0;
-}
-
-int read_full_file(const char *fn, char **contents, size_t *size) {
-        _cleanup_fclose_ FILE *f = NULL;
-
-        assert(fn);
-        assert(contents);
-
-        f = fopen(fn, "re");
-        if (!f)
-                return -errno;
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-        return read_full_stream(f, contents, size);
-}
-
-static int parse_env_file_internal(
-                FILE *f,
-                const char *fname,
-                const char *newline,
-                int (*push) (const char *filename, unsigned line,
-                             const char *key, char *value, void *userdata, int *n_pushed),
-                void *userdata,
-                int *n_pushed) {
-
-        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;
-        _cleanup_free_ char *contents = NULL, *key = NULL, *value = NULL;
-        unsigned line = 1;
-        char *p;
-        int r;
-
-        enum {
-                PRE_KEY,
-                KEY,
-                PRE_VALUE,
-                VALUE,
-                VALUE_ESCAPE,
-                SINGLE_QUOTE_VALUE,
-                SINGLE_QUOTE_VALUE_ESCAPE,
-                DOUBLE_QUOTE_VALUE,
-                DOUBLE_QUOTE_VALUE_ESCAPE,
-                COMMENT,
-                COMMENT_ESCAPE
-        } state = PRE_KEY;
-
-        assert(newline);
-
-        if (f)
-                r = read_full_stream(f, &contents, NULL);
-        else
-                r = read_full_file(fname, &contents, NULL);
-        if (r < 0)
-                return r;
-
-        for (p = contents; *p; p++) {
-                char c = *p;
-
-                switch (state) {
-
-                case PRE_KEY:
-                        if (strchr(COMMENTS, c))
-                                state = COMMENT;
-                        else if (!strchr(WHITESPACE, c)) {
-                                state = KEY;
-                                last_key_whitespace = (size_t) -1;
-
-                                if (!GREEDY_REALLOC(key, key_alloc, n_key+2))
-                                        return -ENOMEM;
-
-                                key[n_key++] = c;
-                        }
-                        break;
-
-                case KEY:
-                        if (strchr(newline, c)) {
-                                state = PRE_KEY;
-                                line++;
-                                n_key = 0;
-                        } else if (c == '=') {
-                                state = PRE_VALUE;
-                                last_value_whitespace = (size_t) -1;
-                        } else {
-                                if (!strchr(WHITESPACE, c))
-                                        last_key_whitespace = (size_t) -1;
-                                else if (last_key_whitespace == (size_t) -1)
-                                         last_key_whitespace = n_key;
-
-                                if (!GREEDY_REALLOC(key, key_alloc, n_key+2))
-                                        return -ENOMEM;
-
-                                key[n_key++] = c;
-                        }
-
-                        break;
-
-                case PRE_VALUE:
-                        if (strchr(newline, c)) {
-                                state = PRE_KEY;
-                                line++;
-                                key[n_key] = 0;
-
-                                if (value)
-                                        value[n_value] = 0;
-
-                                /* strip trailing whitespace from key */
-                                if (last_key_whitespace != (size_t) -1)
-                                        key[last_key_whitespace] = 0;
-
-                                r = push(fname, line, key, value, userdata, n_pushed);
-                                if (r < 0)
-                                        return r;
-
-                                n_key = 0;
-                                value = NULL;
-                                value_alloc = n_value = 0;
-
-                        } else if (c == '\'')
-                                state = SINGLE_QUOTE_VALUE;
-                        else if (c == '\"')
-                                state = DOUBLE_QUOTE_VALUE;
-                        else if (c == '\\')
-                                state = VALUE_ESCAPE;
-                        else if (!strchr(WHITESPACE, c)) {
-                                state = VALUE;
-
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
-                                        return  -ENOMEM;
-
-                                value[n_value++] = c;
-                        }
-
-                        break;
-
-                case VALUE:
-                        if (strchr(newline, c)) {
-                                state = PRE_KEY;
-                                line++;
-
-                                key[n_key] = 0;
-
-                                if (value)
-                                        value[n_value] = 0;
-
-                                /* Chomp off trailing whitespace from value */
-                                if (last_value_whitespace != (size_t) -1)
-                                        value[last_value_whitespace] = 0;
-
-                                /* strip trailing whitespace from key */
-                                if (last_key_whitespace != (size_t) -1)
-                                        key[last_key_whitespace] = 0;
-
-                                r = push(fname, line, key, value, userdata, n_pushed);
-                                if (r < 0)
-                                        return r;
-
-                                n_key = 0;
-                                value = NULL;
-                                value_alloc = n_value = 0;
-
-                        } else if (c == '\\') {
-                                state = VALUE_ESCAPE;
-                                last_value_whitespace = (size_t) -1;
-                        } else {
-                                if (!strchr(WHITESPACE, c))
-                                        last_value_whitespace = (size_t) -1;
-                                else if (last_value_whitespace == (size_t) -1)
-                                        last_value_whitespace = n_value;
-
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
-                                        return -ENOMEM;
-
-                                value[n_value++] = c;
-                        }
-
-                        break;
-
-                case VALUE_ESCAPE:
-                        state = VALUE;
-
-                        if (!strchr(newline, c)) {
-                                /* Escaped newlines we eat up entirely */
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
-                                        return -ENOMEM;
-
-                                value[n_value++] = c;
-                        }
-                        break;
-
-                case SINGLE_QUOTE_VALUE:
-                        if (c == '\'')
-                                state = PRE_VALUE;
-                        else if (c == '\\')
-                                state = SINGLE_QUOTE_VALUE_ESCAPE;
-                        else {
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
-                                        return -ENOMEM;
-
-                                value[n_value++] = c;
-                        }
-
-                        break;
-
-                case SINGLE_QUOTE_VALUE_ESCAPE:
-                        state = SINGLE_QUOTE_VALUE;
-
-                        if (!strchr(newline, c)) {
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
-                                        return -ENOMEM;
-
-                                value[n_value++] = c;
-                        }
-                        break;
-
-                case DOUBLE_QUOTE_VALUE:
-                        if (c == '\"')
-                                state = PRE_VALUE;
-                        else if (c == '\\')
-                                state = DOUBLE_QUOTE_VALUE_ESCAPE;
-                        else {
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
-                                        return -ENOMEM;
-
-                                value[n_value++] = c;
-                        }
-
-                        break;
-
-                case DOUBLE_QUOTE_VALUE_ESCAPE:
-                        state = DOUBLE_QUOTE_VALUE;
-
-                        if (!strchr(newline, c)) {
-                                if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
-                                        return -ENOMEM;
-
-                                value[n_value++] = c;
-                        }
-                        break;
-
-                case COMMENT:
-                        if (c == '\\')
-                                state = COMMENT_ESCAPE;
-                        else if (strchr(newline, c)) {
-                                state = PRE_KEY;
-                                line++;
-                        }
-                        break;
-
-                case COMMENT_ESCAPE:
-                        state = COMMENT;
-                        break;
-                }
-        }
-
-        if (IN_SET(state,
-                   PRE_VALUE,
-                   VALUE,
-                   VALUE_ESCAPE,
-                   SINGLE_QUOTE_VALUE,
-                   SINGLE_QUOTE_VALUE_ESCAPE,
-                   DOUBLE_QUOTE_VALUE,
-                   DOUBLE_QUOTE_VALUE_ESCAPE)) {
-
-                key[n_key] = 0;
-
-                if (value)
-                        value[n_value] = 0;
-
-                if (state == VALUE)
-                        if (last_value_whitespace != (size_t) -1)
-                                value[last_value_whitespace] = 0;
-
-                /* strip trailing whitespace from key */
-                if (last_key_whitespace != (size_t) -1)
-                        key[last_key_whitespace] = 0;
-
-                r = push(fname, line, key, value, userdata, n_pushed);
-                if (r < 0)
-                        return r;
-
-                value = NULL;
-        }
-
-        return 0;
-}
-
-static int check_utf8ness_and_warn(
-                const char *filename, unsigned line,
-                const char *key, char *value) {
-
-        if (!utf8_is_valid(key)) {
-                _cleanup_free_ char *p = NULL;
-
-                p = utf8_escape_invalid(key);
-                log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p);
-                return -EINVAL;
-        }
-
-        if (value && !utf8_is_valid(value)) {
-                _cleanup_free_ char *p = NULL;
-
-                p = utf8_escape_invalid(value);
-                log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, p);
-                return -EINVAL;
-        }
-
-        return 0;
-}
-
-static int parse_env_file_push(
-                const char *filename, unsigned line,
-                const char *key, char *value,
-                void *userdata,
-                int *n_pushed) {
-
-        const char *k;
-        va_list aq, *ap = userdata;
-        int r;
-
-        r = check_utf8ness_and_warn(filename, line, key, value);
-        if (r < 0)
-                return r;
-
-        va_copy(aq, *ap);
-
-        while ((k = va_arg(aq, const char *))) {
-                char **v;
-
-                v = va_arg(aq, char **);
-
-                if (streq(key, k)) {
-                        va_end(aq);
-                        free(*v);
-                        *v = value;
-
-                        if (n_pushed)
-                                (*n_pushed)++;
-
-                        return 1;
-                }
-        }
-
-        va_end(aq);
-        free(value);
-
-        return 0;
-}
-
-int parse_env_filev(
-                FILE *f,
-                const char *fname,
-                const char *newline,
-                va_list ap) {
-
-        int r, n_pushed = 0;
-        va_list aq;
-
-        if (!newline)
-                newline = NEWLINE;
-
-        va_copy(aq, ap);
-        r = parse_env_file_internal(f, fname, newline, parse_env_file_push, &aq, &n_pushed);
-        va_end(aq);
-        if (r < 0)
-                return r;
-
-        return n_pushed;
-}
-
-int parse_env_file(
-                FILE *f,
-                const char *fname,
-                const char *newline,
-                ...) {
-
-        va_list ap;
-        int r;
-
-        va_start(ap, newline);
-        r = parse_env_filev(f, fname, newline, ap);
-        va_end(ap);
-
-        return r;
-}
-
-#if 0 /* NM_IGNORED */
-static int load_env_file_push(
-                const char *filename, unsigned line,
-                const char *key, char *value,
-                void *userdata,
-                int *n_pushed) {
-        char ***m = userdata;
-        char *p;
-        int r;
-
-        r = check_utf8ness_and_warn(filename, line, key, value);
-        if (r < 0)
-                return r;
-
-        p = strjoin(key, "=", value);
-        if (!p)
-                return -ENOMEM;
-
-        r = strv_env_replace(m, p);
-        if (r < 0) {
-                free(p);
-                return r;
-        }
-
-        if (n_pushed)
-                (*n_pushed)++;
-
-        free(value);
-        return 0;
-}
-
-int load_env_file(FILE *f, const char *fname, const char *newline, char ***rl) {
-        char **m = NULL;
-        int r;
-
-        if (!newline)
-                newline = NEWLINE;
-
-        r = parse_env_file_internal(f, fname, newline, load_env_file_push, &m, NULL);
-        if (r < 0) {
-                strv_free(m);
-                return r;
-        }
-
-        *rl = m;
-        return 0;
-}
-
-static int load_env_file_push_pairs(
-                const char *filename, unsigned line,
-                const char *key, char *value,
-                void *userdata,
-                int *n_pushed) {
-        char ***m = userdata;
-        int r;
-
-        r = check_utf8ness_and_warn(filename, line, key, value);
-        if (r < 0)
-                return r;
-
-        r = strv_extend(m, key);
-        if (r < 0)
-                return -ENOMEM;
-
-        if (!value) {
-                r = strv_extend(m, "");
-                if (r < 0)
-                        return -ENOMEM;
-        } else {
-                r = strv_push(m, value);
-                if (r < 0)
-                        return r;
-        }
-
-        if (n_pushed)
-                (*n_pushed)++;
-
-        return 0;
-}
-
-int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ***rl) {
-        char **m = NULL;
-        int r;
-
-        if (!newline)
-                newline = NEWLINE;
-
-        r = parse_env_file_internal(f, fname, newline, load_env_file_push_pairs, &m, NULL);
-        if (r < 0) {
-                strv_free(m);
-                return r;
-        }
-
-        *rl = m;
-        return 0;
-}
-
-static int merge_env_file_push(
-                const char *filename, unsigned line,
-                const char *key, char *value,
-                void *userdata,
-                int *n_pushed) {
-
-        char ***env = userdata;
-        char *expanded_value;
-
-        assert(env);
-
-        if (!value) {
-                log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key);
-                return 0;
-        }
-
-        if (!env_name_is_valid(key)) {
-                log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename), line, key);
-                free(value);
-                return 0;
-        }
-
-        expanded_value = replace_env(value, *env,
-                                     REPLACE_ENV_USE_ENVIRONMENT|
-                                     REPLACE_ENV_ALLOW_BRACELESS|
-                                     REPLACE_ENV_ALLOW_EXTENDED);
-        if (!expanded_value)
-                return -ENOMEM;
-
-        free_and_replace(value, expanded_value);
-
-        return load_env_file_push(filename, line, key, value, env, n_pushed);
-}
-
-int merge_env_file(
-                char ***env,
-                FILE *f,
-                const char *fname) {
-
-        /* NOTE: this function supports braceful and braceless variable expansions,
-         * plus "extended" substitutions, unlike other exported parsing functions.
-         */
-
-        return parse_env_file_internal(f, fname, NEWLINE, merge_env_file_push, env, NULL);
-}
-
-static void write_env_var(FILE *f, const char *v) {
-        const char *p;
-
-        p = strchr(v, '=');
-        if (!p) {
-                /* Fallback */
-                fputs_unlocked(v, f);
-                fputc_unlocked('\n', f);
-                return;
-        }
-
-        p++;
-        fwrite_unlocked(v, 1, p-v, f);
-
-        if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) {
-                fputc_unlocked('\"', f);
-
-                for (; *p; p++) {
-                        if (strchr(SHELL_NEED_ESCAPE, *p))
-                                fputc_unlocked('\\', f);
-
-                        fputc_unlocked(*p, f);
-                }
-
-                fputc_unlocked('\"', f);
-        } else
-                fputs_unlocked(p, f);
-
-        fputc_unlocked('\n', f);
-}
-
-int write_env_file(const char *fname, char **l) {
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_free_ char *p = NULL;
-        char **i;
-        int r;
-
-        assert(fname);
-
-        r = fopen_temporary(fname, &f, &p);
-        if (r < 0)
-                return r;
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-        (void) fchmod_umask(fileno(f), 0644);
-
-        STRV_FOREACH(i, l)
-                write_env_var(f, *i);
-
-        r = fflush_and_check(f);
-        if (r >= 0) {
-                if (rename(p, fname) >= 0)
-                        return 0;
-
-                r = -errno;
-        }
-
-        unlink(p);
-        return r;
-}
-
-int executable_is_script(const char *path, char **interpreter) {
-        _cleanup_free_ char *line = NULL;
-        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;
-
-        if (!startswith(line, "#!"))
-                return 0;
-
-        ans = strstrip(line + 2);
-        len = strcspn(ans, " \t");
-
-        if (len == 0)
-                return 0;
-
-        ans = strndup(ans, len);
-        if (!ans)
-                return -ENOMEM;
-
-        *interpreter = ans;
-        return 1;
-}
-#endif /* NM_IGNORED */
-
-/**
- * Retrieve one field from a file like /proc/self/status.  pattern
- * should not include whitespace or the delimiter (':'). pattern matches only
- * the beginning of a line. Whitespace before ':' is skipped. Whitespace and
- * zeros after the ':' will be skipped. field must be freed afterwards.
- * terminator specifies the terminating characters of the field value (not
- * included in the value).
- */
-int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field) {
-        _cleanup_free_ char *status = NULL;
-        char *t, *f;
-        size_t len;
-        int r;
-
-        assert(terminator);
-        assert(filename);
-        assert(pattern);
-        assert(field);
-
-        r = read_full_file(filename, &status, NULL);
-        if (r < 0)
-                return r;
-
-        t = status;
-
-        do {
-                bool pattern_ok;
-
-                do {
-                        t = strstr(t, pattern);
-                        if (!t)
-                                return -ENOENT;
-
-                        /* Check that pattern occurs in beginning of line. */
-                        pattern_ok = (t == status || t[-1] == '\n');
-
-                        t += strlen(pattern);
-
-                } while (!pattern_ok);
-
-                t += strspn(t, " \t");
-                if (!*t)
-                        return -ENOENT;
-
-        } while (*t != ':');
-
-        t++;
-
-        if (*t) {
-                t += strspn(t, " \t");
-
-                /* Also skip zeros, because when this is used for
-                 * capabilities, we don't want the zeros. This way the
-                 * same capability set always maps to the same string,
-                 * irrespective of the total capability set size. For
-                 * other numbers it shouldn't matter. */
-                t += strspn(t, "0");
-                /* Back off one char if there's nothing but whitespace
-                   and zeros */
-                if (!*t || isspace(*t))
-                        t--;
-        }
-
-        len = strcspn(t, terminator);
-
-        f = strndup(t, len);
-        if (!f)
-                return -ENOMEM;
-
-        *field = f;
-        return 0;
-}
-
-DIR *xopendirat(int fd, const char *name, int flags) {
-        int nfd;
-        DIR *d;
-
-        assert(!(flags & O_CREAT));
-
-        nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0);
-        if (nfd < 0)
-                return NULL;
-
-        d = fdopendir(nfd);
-        if (!d) {
-                safe_close(nfd);
-                return NULL;
-        }
-
-        return d;
-}
-
-#if 0 /* NM_IGNORED */
-static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) {
-        char **i;
-
-        assert(path);
-        assert(mode);
-        assert(_f);
-
-        if (!path_strv_resolve_uniq(search, root))
-                return -ENOMEM;
-
-        STRV_FOREACH(i, search) {
-                _cleanup_free_ char *p = NULL;
-                FILE *f;
-
-                if (root)
-                        p = strjoin(root, *i, "/", path);
-                else
-                        p = strjoin(*i, "/", path);
-                if (!p)
-                        return -ENOMEM;
-
-                f = fopen(p, mode);
-                if (f) {
-                        *_f = f;
-                        return 0;
-                }
-
-                if (errno != ENOENT)
-                        return -errno;
-        }
-
-        return -ENOENT;
-}
-
-int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f) {
-        _cleanup_strv_free_ char **copy = NULL;
-
-        assert(path);
-        assert(mode);
-        assert(_f);
-
-        if (path_is_absolute(path)) {
-                FILE *f;
-
-                f = fopen(path, mode);
-                if (f) {
-                        *_f = f;
-                        return 0;
-                }
-
-                return -errno;
-        }
-
-        copy = strv_copy((char**) search);
-        if (!copy)
-                return -ENOMEM;
-
-        return search_and_fopen_internal(path, mode, root, copy, _f);
-}
-
-int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f) {
-        _cleanup_strv_free_ char **s = NULL;
-
-        if (path_is_absolute(path)) {
-                FILE *f;
-
-                f = fopen(path, mode);
-                if (f) {
-                        *_f = f;
-                        return 0;
-                }
-
-                return -errno;
-        }
-
-        s = strv_split_nulstr(search);
-        if (!s)
-                return -ENOMEM;
-
-        return search_and_fopen_internal(path, mode, root, s, _f);
-}
-#endif /* NM_IGNORED */
-
-int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
-        FILE *f;
-        char *t;
-        int r, fd;
-
-        assert(path);
-        assert(_f);
-        assert(_temp_path);
-
-        r = tempfn_xxxxxx(path, NULL, &t);
-        if (r < 0)
-                return r;
-
-        fd = mkostemp_safe(t);
-        if (fd < 0) {
-                free(t);
-                return -errno;
-        }
-
-        f = fdopen(fd, "we");
-        if (!f) {
-                unlink_noerrno(t);
-                free(t);
-                safe_close(fd);
-                return -errno;
-        }
-
-        *_f = f;
-        *_temp_path = t;
-
-        return 0;
-}
-
-int fflush_and_check(FILE *f) {
-        assert(f);
-
-        errno = 0;
-        fflush(f);
-
-        if (ferror(f))
-                return errno > 0 ? -errno : -EIO;
-
-        return 0;
-}
-
-#if 0 /* NM_IGNORED */
-int fflush_sync_and_check(FILE *f) {
-        int r;
-
-        assert(f);
-
-        r = fflush_and_check(f);
-        if (r < 0)
-                return r;
-
-        if (fsync(fileno(f)) < 0)
-                return -errno;
-
-        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) {
-        _cleanup_umask_ mode_t u = 0;
-        int fd;
-
-        assert(pattern);
-
-        u = umask(077);
-
-        fd = mkostemp(pattern, O_CLOEXEC);
-        if (fd < 0)
-                return -errno;
-
-        return fd;
-}
-
-int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
-        const char *fn;
-        char *t;
-
-        assert(ret);
-
-        if (isempty(p))
-                return -EINVAL;
-        if (path_equal(p, "/"))
-                return -EINVAL;
-
-        /*
-         * Turns this:
-         *         /foo/bar/waldo
-         *
-         * Into this:
-         *         /foo/bar/.#<extra>waldoXXXXXX
-         */
-
-        fn = basename(p);
-        if (!filename_is_valid(fn))
-                return -EINVAL;
-
-        extra = strempty(extra);
-
-        t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1);
-        if (!t)
-                return -ENOMEM;
-
-        strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX");
-
-        *ret = path_simplify(t, false);
-        return 0;
-}
-
-#if 0 /* NM_IGNORED */
-int tempfn_random(const char *p, const char *extra, char **ret) {
-        const char *fn;
-        char *t, *x;
-        uint64_t u;
-        unsigned i;
-
-        assert(ret);
-
-        if (isempty(p))
-                return -EINVAL;
-        if (path_equal(p, "/"))
-                return -EINVAL;
-
-        /*
-         * Turns this:
-         *         /foo/bar/waldo
-         *
-         * Into this:
-         *         /foo/bar/.#<extra>waldobaa2a261115984a9
-         */
-
-        fn = basename(p);
-        if (!filename_is_valid(fn))
-                return -EINVAL;
-
-        extra = strempty(extra);
-
-        t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1);
-        if (!t)
-                return -ENOMEM;
-
-        x = stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn);
-
-        u = random_u64();
-        for (i = 0; i < 16; i++) {
-                *(x++) = hexchar(u & 0xF);
-                u >>= 4;
-        }
-
-        *x = 0;
-
-        *ret = path_simplify(t, false);
-        return 0;
-}
-
-int tempfn_random_child(const char *p, const char *extra, char **ret) {
-        char *t, *x;
-        uint64_t u;
-        unsigned i;
-        int r;
-
-        assert(ret);
-
-        /* Turns this:
-         *         /foo/bar/waldo
-         * Into this:
-         *         /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0
-         */
-
-        if (!p) {
-                r = tmp_dir(&p);
-                if (r < 0)
-                        return r;
-        }
-
-        extra = strempty(extra);
-
-        t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1);
-        if (!t)
-                return -ENOMEM;
-
-        if (isempty(p))
-                x = stpcpy(stpcpy(t, ".#"), extra);
-        else
-                x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra);
-
-        u = random_u64();
-        for (i = 0; i < 16; i++) {
-                *(x++) = hexchar(u & 0xF);
-                u >>= 4;
-        }
-
-        *x = 0;
-
-        *ret = path_simplify(t, false);
-        return 0;
-}
-
-int write_timestamp_file_atomic(const char *fn, usec_t n) {
-        char ln[DECIMAL_STR_MAX(n)+2];
-
-        /* Creates a "timestamp" file, that contains nothing but a
-         * usec_t timestamp, formatted in ASCII. */
-
-        if (n <= 0 || n >= USEC_INFINITY)
-                return -ERANGE;
-
-        xsprintf(ln, USEC_FMT "\n", n);
-
-        return write_string_file(fn, ln, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
-}
-
-int read_timestamp_file(const char *fn, usec_t *ret) {
-        _cleanup_free_ char *ln = NULL;
-        uint64_t t;
-        int r;
-
-        r = read_one_line_file(fn, &ln);
-        if (r < 0)
-                return r;
-
-        r = safe_atou64(ln, &t);
-        if (r < 0)
-                return r;
-
-        if (t <= 0 || t >= (uint64_t) USEC_INFINITY)
-                return -ERANGE;
-
-        *ret = (usec_t) t;
-        return 0;
-}
-#endif /* NM_IGNORED */
-
-int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) {
-        int r;
-
-        assert(s);
-
-        /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. The *space parameter
-         * when specified shall initially point to a boolean variable initialized to false. It is set to true after the
-         * first invocation. This call is supposed to be use in loops, where a separator shall be inserted between each
-         * element, but not before the first one. */
-
-        if (!f)
-                f = stdout;
-
-        if (space) {
-                if (!separator)
-                        separator = " ";
-
-                if (*space) {
-                        r = fputs(separator, f);
-                        if (r < 0)
-                                return r;
-                }
-
-                *space = true;
-        }
-
-        return fputs(s, f);
-}
-
-#if 0 /* NM_IGNORED */
-int open_tmpfile_unlinkable(const char *directory, int flags) {
-        char *p;
-        int fd, r;
-
-        if (!directory) {
-                r = tmp_dir(&directory);
-                if (r < 0)
-                        return r;
-        } else if (isempty(directory))
-                return -EINVAL;
-
-        /* Returns an unlinked temporary file that cannot be linked into the file system anymore */
-
-        /* Try O_TMPFILE first, if it is supported */
-        fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR);
-        if (fd >= 0)
-                return fd;
-
-        /* Fall back to unguessable name + unlinking */
-        p = strjoina(directory, "/systemd-tmp-XXXXXX");
-
-        fd = mkostemp_safe(p);
-        if (fd < 0)
-                return fd;
-
-        (void) unlink(p);
-
-        return fd;
-}
-
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
-        _cleanup_free_ char *tmp = NULL;
-        int r, fd;
-
-        assert(target);
-        assert(ret_path);
-
-        /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */
-        assert((flags & O_EXCL) == 0);
-
-        /* Creates a temporary file, that shall be renamed to "target" later. If possible, this uses O_TMPFILE – in
-         * which case "ret_path" will be returned as NULL. If not possible a the tempoary path name used is returned in
-         * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
-
-        fd = open_parent(target, O_TMPFILE|flags, 0640);
-        if (fd >= 0) {
-                *ret_path = NULL;
-                return fd;
-        }
-
-        log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target);
-
-        r = tempfn_random(target, NULL, &tmp);
-        if (r < 0)
-                return r;
-
-        fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
-        if (fd < 0)
-                return -errno;
-
-        *ret_path = TAKE_PTR(tmp);
-
-        return fd;
-}
-
-int open_serialization_fd(const char *ident) {
-        int fd = -1;
-
-        fd = memfd_create(ident, MFD_CLOEXEC);
-        if (fd < 0) {
-                const char *path;
-
-                path = getpid_cached() == 1 ? "/run/systemd" : "/tmp";
-                fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC);
-                if (fd < 0)
-                        return fd;
-
-                log_debug("Serializing %s to %s.", ident, path);
-        } else
-                log_debug("Serializing %s to memfd.", ident);
-
-        return fd;
-}
-
-int link_tmpfile(int fd, const char *path, const char *target) {
-
-        assert(fd >= 0);
-        assert(target);
-
-        /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd
-         * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported
-         * on the directory, and renameat2() is used instead.
-         *
-         * Note that in both cases we will not replace existing files. This is because linkat() does not support this
-         * operation currently (renameat2() does), and there is no nice way to emulate this. */
-
-        if (path) {
-                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];
-
-                xsprintf(proc_fd_path, "/proc/self/fd/%i", fd);
-
-                if (linkat(AT_FDCWD, proc_fd_path, AT_FDCWD, target, AT_SYMLINK_FOLLOW) < 0)
-                        return -errno;
-        }
-
-        return 0;
-}
-
-int read_nul_string(FILE *f, char **ret) {
-        _cleanup_free_ char *x = NULL;
-        size_t allocated = 0, n = 0;
-
-        assert(f);
-        assert(ret);
-
-        /* Reads a NUL-terminated string from the specified file. */
-
-        for (;;) {
-                int c;
-
-                if (!GREEDY_REALLOC(x, allocated, n+2))
-                        return -ENOMEM;
-
-                c = fgetc(f);
-                if (c == 0) /* Terminate at NUL byte */
-                        break;
-                if (c == EOF) {
-                        if (ferror(f))
-                                return -errno;
-                        break; /* Terminate at EOF */
-                }
-
-                x[n++] = (char) c;
-        }
-
-        if (x)
-                x[n] = 0;
-        else {
-                x = new0(char, 1);
-                if (!x)
-                        return -ENOMEM;
-        }
-
-        *ret = TAKE_PTR(x);
-
-        return 0;
-}
-
-int mkdtemp_malloc(const char *template, char **ret) {
-        _cleanup_free_ char *p = NULL;
-        int r;
-
-        assert(ret);
-
-        if (template)
-                p = strdup(template);
-        else {
-                const char *tmp;
-
-                r = tmp_dir(&tmp);
-                if (r < 0)
-                        return r;
-
-                p = strjoin(tmp, "/XXXXXX");
-        }
-        if (!p)
-                return -ENOMEM;
-
-        if (!mkdtemp(p))
-                return -errno;
-
-        *ret = TAKE_PTR(p);
-        return 0;
-}
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile);
-
-int read_line(FILE *f, size_t limit, char **ret) {
-        _cleanup_free_ char *buffer = NULL;
-        size_t n = 0, allocated = 0, count = 0;
-
-        assert(f);
-
-        /* Something like a bounded version of getline().
-         *
-         * Considers EOF, \n and \0 end of line delimiters, and does not include these delimiters in the string
-         * returned.
-         *
-         * Returns the number of bytes read from the files (i.e. including delimiters — this hence usually differs from
-         * the number of characters in the returned string). When EOF is hit, 0 is returned.
-         *
-         * The input parameter limit is the maximum numbers of characters in the returned string, i.e. excluding
-         * delimiters. If the limit is hit we fail and return -ENOBUFS.
-         *
-         * If a line shall be skipped ret may be initialized as NULL. */
-
-        if (ret) {
-                if (!GREEDY_REALLOC(buffer, allocated, 1))
-                        return -ENOMEM;
-        }
-
-        {
-                _unused_ _cleanup_(funlockfilep) FILE *flocked = f;
-                flockfile(f);
-
-                for (;;) {
-                        int c;
-
-                        if (n >= limit)
-                                return -ENOBUFS;
-
-                        errno = 0;
-                        c = fgetc_unlocked(f);
-                        if (c == EOF) {
-                                /* if we read an error, and have no data to return, then propagate the error */
-                                if (ferror_unlocked(f) && n == 0)
-                                        return errno > 0 ? -errno : -EIO;
-
-                                break;
-                        }
-
-                        count++;
-
-                        if (IN_SET(c, '\n', 0)) /* Reached a delimiter */
-                                break;
-
-                        if (ret) {
-                                if (!GREEDY_REALLOC(buffer, allocated, n + 2))
-                                        return -ENOMEM;
-
-                                buffer[n] = (char) c;
-                        }
-
-                        n++;
-                }
-        }
-
-        if (ret) {
-                buffer[n] = 0;
-
-                *ret = TAKE_PTR(buffer);
-        }
-
-        return (int) count;
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/fileio.h b/src/systemd/src/basic/fileio.h
deleted file mode 100644
index 77e6206e..00000000
--- a/src/systemd/src/basic/fileio.h
+++ /dev/null
@@ -1,96 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <dirent.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <sys/types.h>
-
-#include "macro.h"
-#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_VERIFY_ON_FAILURE = 1 << 3,
-        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);
-static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) {
-        return write_string_stream_ts(f, line, flags, NULL);
-}
-int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, struct timespec *ts);
-static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) {
-        return write_string_file_ts(fn, line, flags, NULL);
-}
-
-int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4);
-
-int read_one_line_file(const char *fn, char **line);
-int read_full_file(const char *fn, char **contents, size_t *size);
-int read_full_stream(FILE *f, char **contents, size_t *size);
-
-int verify_file(const char *fn, const char *blob, bool accept_extra_nl);
-
-int parse_env_filev(FILE *f, const char *fname, const char *separator, va_list ap);
-int parse_env_file(FILE *f, const char *fname, const char *separator, ...) _sentinel_;
-int load_env_file(FILE *f, const char *fname, const char *separator, char ***l);
-int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l);
-
-int merge_env_file(char ***env, FILE *f, const char *fname);
-
-int write_env_file(const char *fname, char **l);
-
-int executable_is_script(const char *path, char **interpreter);
-
-int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field);
-
-DIR *xopendirat(int dirfd, const char *name, int flags);
-
-int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f);
-int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f);
-
-#define FOREACH_LINE(line, f, on_error)                         \
-        for (;;)                                                \
-                if (!fgets(line, sizeof(line), f)) {            \
-                        if (ferror(f)) {                        \
-                                on_error;                       \
-                        }                                       \
-                        break;                                  \
-                } else
-
-int fflush_and_check(FILE *f);
-int fflush_sync_and_check(FILE *f);
-
-int fopen_temporary(const char *path, FILE **_f, char **_temp_path);
-int mkostemp_safe(char *pattern);
-
-int tempfn_xxxxxx(const char *p, const char *extra, char **ret);
-int tempfn_random(const char *p, const char *extra, char **ret);
-int tempfn_random_child(const char *p, const char *extra, char **ret);
-
-int write_timestamp_file_atomic(const char *fn, usec_t n);
-int read_timestamp_file(const char *fn, usec_t *ret);
-
-int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space);
-
-int open_tmpfile_unlinkable(const char *directory, int flags);
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path);
-int open_serialization_fd(const char *ident);
-
-int link_tmpfile(int fd, const char *path, const char *target);
-
-int read_nul_string(FILE *f, char **ret);
-
-int mkdtemp_malloc(const char *template, char **ret);
-
-int read_line(FILE *f, size_t limit, char **ret);
diff --git a/src/systemd/src/basic/fs-util.c b/src/systemd/src/basic/fs-util.c
deleted file mode 100644
index 8c19e9c7..00000000
--- a/src/systemd/src/basic/fs-util.c
+++ /dev/null
@@ -1,1256 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <linux/magic.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "alloc-util.h"
-#include "dirent-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "fs-util.h"
-#include "log.h"
-#include "macro.h"
-#include "missing.h"
-#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"
-#include "strv.h"
-#include "time-util.h"
-#include "user-util.h"
-#include "util.h"
-
-int unlink_noerrno(const char *path) {
-        PROTECT_ERRNO;
-        int r;
-
-        r = unlink(path);
-        if (r < 0)
-                return -errno;
-
-        return 0;
-}
-
-#if 0 /* NM_IGNORED */
-int rmdir_parents(const char *path, const char *stop) {
-        size_t l;
-        int r = 0;
-
-        assert(path);
-        assert(stop);
-
-        l = strlen(path);
-
-        /* Skip trailing slashes */
-        while (l > 0 && path[l-1] == '/')
-                l--;
-
-        while (l > 0) {
-                char *t;
-
-                /* Skip last component */
-                while (l > 0 && path[l-1] != '/')
-                        l--;
-
-                /* Skip trailing slashes */
-                while (l > 0 && path[l-1] == '/')
-                        l--;
-
-                if (l <= 0)
-                        break;
-
-                t = strndup(path, l);
-                if (!t)
-                        return -ENOMEM;
-
-                if (path_startswith(stop, t)) {
-                        free(t);
-                        return 0;
-                }
-
-                r = rmdir(t);
-                free(t);
-
-                if (r < 0)
-                        if (errno != ENOENT)
-                                return -errno;
-        }
-
-        return 0;
-}
-
-int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
-        struct stat buf;
-        int ret;
-
-        ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE);
-        if (ret >= 0)
-                return 0;
-
-        /* renameat2() exists since Linux 3.15, btrfs added support for it later.
-         * If it is not implemented, fallback to another method. */
-        if (!IN_SET(errno, EINVAL, ENOSYS))
-                return -errno;
-
-        /* The link()/unlink() fallback does not work on directories. But
-         * renameat() without RENAME_NOREPLACE gives the same semantics on
-         * directories, except when newpath is an *empty* directory. This is
-         * good enough. */
-        ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW);
-        if (ret >= 0 && S_ISDIR(buf.st_mode)) {
-                ret = renameat(olddirfd, oldpath, newdirfd, newpath);
-                return ret >= 0 ? 0 : -errno;
-        }
-
-        /* If it is not a directory, use the link()/unlink() fallback. */
-        ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0);
-        if (ret < 0)
-                return -errno;
-
-        ret = unlinkat(olddirfd, oldpath, 0);
-        if (ret < 0) {
-                /* backup errno before the following unlinkat() alters it */
-                ret = errno;
-                (void) unlinkat(newdirfd, newpath, 0);
-                errno = ret;
-                return -errno;
-        }
-
-        return 0;
-}
-#endif /* NM_IGNORED */
-
-int readlinkat_malloc(int fd, const char *p, char **ret) {
-        size_t l = 100;
-        int r;
-
-        assert(p);
-        assert(ret);
-
-        for (;;) {
-                char *c;
-                ssize_t n;
-
-                c = new(char, l);
-                if (!c)
-                        return -ENOMEM;
-
-                n = readlinkat(fd, p, c, l-1);
-                if (n < 0) {
-                        r = -errno;
-                        free(c);
-                        return r;
-                }
-
-                if ((size_t) n < l-1) {
-                        c[n] = 0;
-                        *ret = c;
-                        return 0;
-                }
-
-                free(c);
-                l *= 2;
-        }
-}
-
-int readlink_malloc(const char *p, char **ret) {
-        return readlinkat_malloc(AT_FDCWD, p, ret);
-}
-
-#if 0 /* NM_IGNORED */
-int readlink_value(const char *p, char **ret) {
-        _cleanup_free_ char *link = NULL;
-        char *value;
-        int r;
-
-        r = readlink_malloc(p, &link);
-        if (r < 0)
-                return r;
-
-        value = basename(link);
-        if (!value)
-                return -ENOENT;
-
-        value = strdup(value);
-        if (!value)
-                return -ENOMEM;
-
-        *ret = value;
-
-        return 0;
-}
-
-int readlink_and_make_absolute(const char *p, char **r) {
-        _cleanup_free_ char *target = NULL;
-        char *k;
-        int j;
-
-        assert(p);
-        assert(r);
-
-        j = readlink_malloc(p, &target);
-        if (j < 0)
-                return j;
-
-        k = file_in_same_dir(p, target);
-        if (!k)
-                return -ENOMEM;
-
-        *r = k;
-        return 0;
-}
-
-int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
-        assert(path);
-
-        /* Under the assumption that we are running privileged we
-         * first change the access mode and only then hand out
-         * ownership to avoid a window where access is too open. */
-
-        if (mode != MODE_INVALID)
-                if (chmod(path, mode) < 0)
-                        return -errno;
-
-        if (uid != UID_INVALID || gid != GID_INVALID)
-                if (chown(path, uid, gid) < 0)
-                        return -errno;
-
-        return 0;
-}
-
-int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) {
-        /* Under the assumption that we are running privileged we
-         * first change the access mode and only then hand out
-         * ownership to avoid a window where access is too open. */
-
-        if (mode != MODE_INVALID)
-                if (fchmod(fd, mode) < 0)
-                        return -errno;
-
-        if (uid != UID_INVALID || gid != GID_INVALID)
-                if (fchown(fd, uid, gid) < 0)
-                        return -errno;
-
-        return 0;
-}
-#endif /* NM_IGNORED */
-
-int fchmod_umask(int fd, mode_t m) {
-        mode_t u;
-        int r;
-
-        u = umask(0777);
-        r = fchmod(fd, m & (~u)) < 0 ? -errno : 0;
-        umask(u);
-
-        return r;
-}
-
-#if 0 /* NM_IGNORED */
-int fchmod_opath(int fd, mode_t m) {
-        char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
-
-        /* This function operates also on fd that might have been opened with
-         * O_PATH. Indeed fchmodat() doesn't have the AT_EMPTY_PATH flag like
-         * fchownat() does. */
-
-        xsprintf(procfs_path, "/proc/self/fd/%i", fd);
-
-        if (chmod(procfs_path, m) < 0)
-                return -errno;
-
-        return 0;
-}
-
-int fd_warn_permissions(const char *path, int fd) {
-        struct stat st;
-
-        if (fstat(fd, &st) < 0)
-                return -errno;
-
-        if (st.st_mode & 0111)
-                log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path);
-
-        if (st.st_mode & 0002)
-                log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
-
-        if (getpid_cached() == 1 && (st.st_mode & 0044) != 0044)
-                log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path);
-
-        return 0;
-}
-
-int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
-        char fdpath[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
-        _cleanup_close_ int fd = -1;
-        int r, ret = 0;
-
-        assert(path);
-
-        /* 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 (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 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 = utimensat(AT_FDCWD, fdpath, ts, 0);
-        } else
-                r = utimensat(AT_FDCWD, fdpath, NULL, 0);
-        if (r < 0 && ret >= 0)
-                return -errno;
-
-        return ret;
-}
-
-int touch(const char *path) {
-        return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID);
-}
-
-int symlink_idempotent(const char *from, const char *to) {
-        int r;
-
-        assert(from);
-        assert(to);
-
-        if (symlink(from, to) < 0) {
-                _cleanup_free_ char *p = NULL;
-
-                if (errno != EEXIST)
-                        return -errno;
-
-                r = readlink_malloc(to, &p);
-                if (r == -EINVAL) /* Not a symlink? In that case return the original error we encountered: -EEXIST */
-                        return -EEXIST;
-                if (r < 0) /* Any other error? In that case propagate it as is */
-                        return r;
-
-                if (!streq(p, from)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
-                        return -EEXIST;
-        }
-
-        return 0;
-}
-
-int symlink_atomic(const char *from, const char *to) {
-        _cleanup_free_ char *t = NULL;
-        int r;
-
-        assert(from);
-        assert(to);
-
-        r = tempfn_random(to, NULL, &t);
-        if (r < 0)
-                return r;
-
-        if (symlink(from, t) < 0)
-                return -errno;
-
-        if (rename(t, to) < 0) {
-                unlink_noerrno(t);
-                return -errno;
-        }
-
-        return 0;
-}
-
-int mknod_atomic(const char *path, mode_t mode, dev_t dev) {
-        _cleanup_free_ char *t = NULL;
-        int r;
-
-        assert(path);
-
-        r = tempfn_random(path, NULL, &t);
-        if (r < 0)
-                return r;
-
-        if (mknod(t, mode, dev) < 0)
-                return -errno;
-
-        if (rename(t, path) < 0) {
-                unlink_noerrno(t);
-                return -errno;
-        }
-
-        return 0;
-}
-
-int mkfifo_atomic(const char *path, mode_t mode) {
-        _cleanup_free_ char *t = NULL;
-        int r;
-
-        assert(path);
-
-        r = tempfn_random(path, NULL, &t);
-        if (r < 0)
-                return r;
-
-        if (mkfifo(t, mode) < 0)
-                return -errno;
-
-        if (rename(t, path) < 0) {
-                unlink_noerrno(t);
-                return -errno;
-        }
-
-        return 0;
-}
-
-int mkfifoat_atomic(int dirfd, const char *path, mode_t mode) {
-        _cleanup_free_ char *t = NULL;
-        int r;
-
-        assert(path);
-
-        if (path_is_absolute(path))
-                return mkfifo_atomic(path, mode);
-
-        /* We're only interested in the (random) filename.  */
-        r = tempfn_random_child("", NULL, &t);
-        if (r < 0)
-                return r;
-
-        if (mkfifoat(dirfd, t, mode) < 0)
-                return -errno;
-
-        if (renameat(dirfd, t, dirfd, path) < 0) {
-                unlink_noerrno(t);
-                return -errno;
-        }
-
-        return 0;
-}
-
-int get_files_in_directory(const char *path, char ***list) {
-        _cleanup_closedir_ DIR *d = NULL;
-        struct dirent *de;
-        size_t bufsize = 0, n = 0;
-        _cleanup_strv_free_ char **l = NULL;
-
-        assert(path);
-
-        /* Returns all files in a directory in *list, and the number
-         * of files as return value. If list is NULL returns only the
-         * number. */
-
-        d = opendir(path);
-        if (!d)
-                return -errno;
-
-        FOREACH_DIRENT_ALL(de, d, return -errno) {
-                dirent_ensure_type(d, de);
-
-                if (!dirent_is_file(de))
-                        continue;
-
-                if (list) {
-                        /* one extra slot is needed for the terminating NULL */
-                        if (!GREEDY_REALLOC(l, bufsize, n + 2))
-                                return -ENOMEM;
-
-                        l[n] = strdup(de->d_name);
-                        if (!l[n])
-                                return -ENOMEM;
-
-                        l[++n] = NULL;
-                } else
-                        n++;
-        }
-
-        if (list)
-                *list = TAKE_PTR(l);
-
-        return n;
-}
-
-static int getenv_tmp_dir(const char **ret_path) {
-        const char *n;
-        int r, ret = 0;
-
-        assert(ret_path);
-
-        /* We use the same order of environment variables python uses in tempfile.gettempdir():
-         * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */
-        FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP") {
-                const char *e;
-
-                e = secure_getenv(n);
-                if (!e)
-                        continue;
-                if (!path_is_absolute(e)) {
-                        r = -ENOTDIR;
-                        goto next;
-                }
-                if (!path_is_normalized(e)) {
-                        r = -EPERM;
-                        goto next;
-                }
-
-                r = is_dir(e, true);
-                if (r < 0)
-                        goto next;
-                if (r == 0) {
-                        r = -ENOTDIR;
-                        goto next;
-                }
-
-                *ret_path = e;
-                return 1;
-
-        next:
-                /* Remember first error, to make this more debuggable */
-                if (ret >= 0)
-                        ret = r;
-        }
-
-        if (ret < 0)
-                return ret;
-
-        *ret_path = NULL;
-        return ret;
-}
-
-static int tmp_dir_internal(const char *def, const char **ret) {
-        const char *e;
-        int r, k;
-
-        assert(def);
-        assert(ret);
-
-        r = getenv_tmp_dir(&e);
-        if (r > 0) {
-                *ret = e;
-                return 0;
-        }
-
-        k = is_dir(def, true);
-        if (k == 0)
-                k = -ENOTDIR;
-        if (k < 0)
-                return r < 0 ? r : k;
-
-        *ret = def;
-        return 0;
-}
-
-int var_tmp_dir(const char **ret) {
-
-        /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus
-         * even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is
-         * returned preferably however. Note that both this function and tmp_dir() below are affected by $TMPDIR,
-         * making it a variable that overrides all temporary file storage locations. */
-
-        return tmp_dir_internal("/var/tmp", ret);
-}
-
-int tmp_dir(const char **ret) {
-
-        /* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually
-         * backed by an in-memory file system: /tmp. */
-
-        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;
-}
-#endif /* NM_IGNORED */
-
-int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
-        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 */
-        xsprintf(path, "/proc/self/fd/%i", what);
-
-        r = inotify_add_watch(fd, path, mask);
-        if (r < 0)
-                return -errno;
-
-        return r;
-}
-
-#if 0 /* NM_IGNORED */
-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 = CHASE_SYMLINKS_MAX; /* 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_SET(flags, CHASE_NONEXISTENT | CHASE_OPEN))
-                return -EINVAL;
-
-        if (FLAGS_SET(flags, CHASE_STEP | 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.
-         *
-         * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following
-         * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is
-         * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first
-         * prefixed accordingly.
-         *
-         * Algorithmically this operates on two path buffers: "done" are the components of the path we already
-         * processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to
-         * process. On each iteration, we move one component from "todo" to "done", processing it's special meaning
-         * each time. The "todo" path always starts with at least one slash, the "done" path always ends in no
-         * slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races
-         * at a minimum.
-         *
-         * 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.
-         *
-         * There are three ways to invoke this function:
-         *
-         * 1. Without CHASE_STEP or CHASE_OPEN: in this case the path is resolved and the normalized path is returned
-         *    in `ret`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set 0 is returned if the file
-         *    doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set >= 0 is returned if the destination was
-         *    found, -ENOENT if it doesn't.
-         *
-         * 2. With CHASE_OPEN: in this case the destination is opened after chasing it as O_PATH and this file
-         *    descriptor is returned as return value. This is useful to open files relative to some root
-         *    directory. Note that the returned O_PATH file descriptors must be converted into a regular one (using
-         *    fd_reopen() or such) before it can be used for reading/writing. CHASE_OPEN may not be combined with
-         *    CHASE_NONEXISTENT.
-         *
-         * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only the first
-         *    symlink or ".." component of the path is resolved, and the resulting path is returned. This is useful if
-         *    a caller wants to trace the a path through the file system verbosely. Returns < 0 on error, > 0 if the
-         *    path is fully normalized, and == 0 for each normalization step. This may be combined with
-         *    CHASE_NONEXISTENT, in which case 1 is returned when a component is not found.
-         *
-         * */
-
-        /* A root directory of "/" or "" is identical to none */
-        if (empty_or_root(original_root))
-                original_root = NULL;
-
-        if (!original_root && !ret && (flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_OPEN|CHASE_STEP)) == CHASE_OPEN) {
-                /* Shortcut the CHASE_OPEN case if the caller isn't interested in the actual path and has no root set
-                 * and doesn't care about any of the other special features we provide either. */
-                r = open(path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
-                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) {
-
-                        /* 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);
-        if (r < 0)
-                return r;
-
-        fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
-        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;
-                _cleanup_close_ int child = -1;
-                struct stat st;
-                size_t n, m;
-
-                /* Determine length of first component in the path */
-                n = strspn(todo, "/");                  /* The slashes */
-                m = n + strcspn(todo + n, "/");         /* The entire length of the component */
-
-                /* Extract the first component. */
-                first = strndup(todo, m);
-                if (!first)
-                        return -ENOMEM;
-
-                todo += m;
-
-                /* Empty? Then we reached the end. */
-                if (isempty(first))
-                        break;
-
-                /* Just a single slash? Then we reached the end. */
-                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, "/."))
-                        continue;
-
-                /* Two dots? Then chop off the last bit of what we already found out. */
-                if (path_equal(first, "/..")) {
-                        _cleanup_free_ char *parent = NULL;
-                        _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. */
-                        if (empty_or_root(done))
-                                continue;
-
-                        parent = dirname_malloc(done);
-                        if (!parent)
-                                return -ENOMEM;
-
-                        /* Don't allow this to leave the root dir.  */
-                        if (root &&
-                            path_startswith(done, root) &&
-                            !path_startswith(parent, root))
-                                continue;
-
-                        free_and_replace(done, parent);
-
-                        if (flags & CHASE_STEP)
-                                goto chased_one;
-
-                        fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH);
-                        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 = TAKE_FD(fd_parent);
-
-                        continue;
-                }
-
-                /* Otherwise let's see what this is. */
-                child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH);
-                if (child < 0) {
-
-                        if (errno == ENOENT &&
-                            (flags & CHASE_NONEXISTENT) &&
-                            (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;
-
-                                exists = false;
-                                break;
-                        }
-
-                        return -errno;
-                }
-
-                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_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
-                        return -EREMOTE;
-
-                if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
-                        char *joined;
-
-                        _cleanup_free_ char *destination = NULL;
-
-                        /* This is a symlink, in this case read the destination. But let's make sure we don't follow
-                         * symlinks without bounds. */
-                        if (--max_follow <= 0)
-                                return -ELOOP;
-
-                        r = readlinkat_malloc(fd, first + n, &destination);
-                        if (r < 0)
-                                return r;
-                        if (isempty(destination))
-                                return -EINVAL;
-
-                        if (path_is_absolute(destination)) {
-
-                                /* An absolute destination. Start the loop from the beginning, but use the root
-                                 * directory as base. */
-
-                                safe_close(fd);
-                                fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
-                                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. */
-                                if (isempty(root))
-                                        done = NULL;
-                                else {
-                                        done = strdup(root);
-                                        if (!done)
-                                                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);
-                        } else
-                                joined = strjoin("/", destination, todo);
-                        if (!joined)
-                                return -ENOMEM;
-
-                        free(buffer);
-                        todo = buffer = joined;
-
-                        if (flags & CHASE_STEP)
-                                goto chased_one;
-
-                        continue;
-                }
-
-                /* If this is not a symlink, then let's just add the name we read to what we already verified. */
-                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 = TAKE_FD(child);
-        }
-
-        if (!done) {
-                /* Special case, turn the empty string into "/", to indicate the root directory. */
-                done = strdup("/");
-                if (!done)
-                        return -ENOMEM;
-        }
-
-        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);
-        }
-
-        if (flags & CHASE_STEP)
-                return 1;
-
-        return exists;
-
-chased_one:
-        if (ret) {
-                char *c;
-
-                c = strjoin(strempty(done), todo);
-                if (!c)
-                        return -ENOMEM;
-
-                *ret = c;
-        }
-
-        return 0;
-}
-
-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 (empty_or_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 (empty_or_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 chase_symlinks_and_stat(
-                const char *path,
-                const char *root,
-                unsigned chase_flags,
-                char **ret_path,
-                struct stat *ret_stat) {
-
-        _cleanup_close_ int path_fd = -1;
-        _cleanup_free_ char *p = NULL;
-
-        assert(path);
-        assert(ret_stat);
-
-        if (chase_flags & CHASE_NONEXISTENT)
-                return -EINVAL;
-
-        if (empty_or_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 */
-                if (stat(path, ret_stat) < 0)
-                        return -errno;
-
-                return 1;
-        }
-
-        path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
-        if (path_fd < 0)
-                return path_fd;
-
-        if (fstat(path_fd, ret_stat) < 0)
-                return -errno;
-
-        if (ret_path)
-                *ret_path = TAKE_PTR(p);
-
-        if (chase_flags & CHASE_OPEN)
-                return TAKE_FD(path_fd);
-
-        return 1;
-}
-
-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;
-}
-
-void unlink_tempfilep(char (*p)[]) {
-        /* If the file is created with mkstemp(), it will (almost always)
-         * change the suffix. Treat this as a sign that the file was
-         * successfully created. We ignore both the rare case where the
-         * original suffix is used and unlink failures. */
-        if (!endswith(*p, ".XXXXXX"))
-                (void) unlink_noerrno(*p);
-}
-
-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;
-        _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_errno(r, "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;
-
-        dfd = open_parent(path, O_CLOEXEC, 0);
-        if (dfd < 0)
-                return dfd;
-
-        if (fsync(dfd) < 0)
-                return -errno;
-
-        return 0;
-}
-
-int open_parent(const char *path, int flags, mode_t mode) {
-        _cleanup_free_ char *parent = NULL;
-        int fd;
-
-        if (isempty(path))
-                return -EINVAL;
-        if (path_equal(path, "/")) /* requesting the parent of the root dir is fishy, let's prohibit that */
-                return -EINVAL;
-
-        parent = dirname_malloc(path);
-        if (!parent)
-                return -ENOMEM;
-
-        /* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
-         * O_TMPFILE file, because in that case we are actually create a regular file below the parent directory. */
-
-        if ((flags & O_PATH) == O_PATH)
-                flags |= O_DIRECTORY;
-        else if ((flags & O_TMPFILE) != O_TMPFILE)
-                flags |= O_DIRECTORY|O_RDONLY;
-
-        fd = open(parent, flags, mode);
-        if (fd < 0)
-                return -errno;
-
-        return fd;
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/fs-util.h b/src/systemd/src/basic/fs-util.h
deleted file mode 100644
index 4b656258..00000000
--- a/src/systemd/src/basic/fs-util.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <dirent.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <sys/inotify.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "time-util.h"
-#include "util.h"
-
-int unlink_noerrno(const char *path);
-
-int rmdir_parents(const char *path, const char *stop);
-
-int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
-
-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 chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
-int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid);
-
-int fchmod_umask(int fd, mode_t mode);
-int fchmod_opath(int fd, mode_t m);
-
-int fd_warn_permissions(const char *path, int fd);
-
-#define laccess(path, mode) faccessat(AT_FDCWD, (path), (mode), AT_SYMLINK_NOFOLLOW)
-
-int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode);
-int touch(const char *path);
-
-int symlink_idempotent(const char *from, const char *to);
-
-int symlink_atomic(const char *from, const char *to);
-int mknod_atomic(const char *path, mode_t mode, dev_t dev);
-int mkfifo_atomic(const char *path, mode_t mode);
-int mkfifoat_atomic(int dir_fd, const char *path, mode_t mode);
-
-int get_files_in_directory(const char *path, char ***list);
-
-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) \
-        for ((e) = &buffer.ev;                                \
-             (uint8_t*) (e) < (uint8_t*) (buffer.raw) + (sz); \
-             (e) = (struct inotify_event*) ((uint8_t*) (e) + sizeof(struct inotify_event) + (e)->len))
-
-union inotify_event_buffer {
-        struct inotify_event ev;
-        uint8_t raw[INOTIFY_EVENT_MAX];
-};
-
-int inotify_add_watch_fd(int fd, int what, uint32_t mask);
-
-enum {
-        CHASE_PREFIX_ROOT = 1 << 0, /* If set, the specified path will be prefixed by the specified root before beginning the iteration */
-        CHASE_NONEXISTENT = 1 << 1, /* If set, it's OK if the path doesn't actually exist. */
-        CHASE_NO_AUTOFS   = 1 << 2, /* If set, return -EREMOTE if autofs mount point found */
-        CHASE_SAFE        = 1 << 3, /* If set, return EPERM if we ever traverse from unprivileged to privileged files or directories */
-        CHASE_OPEN        = 1 << 4, /* If set, return an O_PATH object to the final component */
-        CHASE_TRAIL_SLASH = 1 << 5, /* If set, any trailing slash will be preserved */
-        CHASE_STEP        = 1 << 6, /* If set, just execute a single step of the normalization */
-        CHASE_NOFOLLOW    = 1 << 7, /* Only valid with CHASE_OPEN: when the path's right-most component refers to symlink return O_PATH fd of the symlink, rather than following it. */
-};
-
-/* How many iterations to execute before returning -ELOOP */
-#define CHASE_SYMLINKS_MAX 32
-
-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);
-int chase_symlinks_and_stat(const char *path, const char *root, unsigned chase_flags, char **ret_path, struct stat *ret_stat);
-
-/* 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_noerrno(p);
-        free(p);
-}
-DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free);
-
-int access_fd(int fd, int mode);
-
-void unlink_tempfilep(char (*p)[]);
-int unlinkat_deallocate(int fd, const char *name, int flags);
-
-int fsync_directory_of_file(int fd);
-
-int open_parent(const char *path, int flags, mode_t mode);
diff --git a/src/systemd/src/basic/hash-funcs.c b/src/systemd/src/basic/hash-funcs.c
deleted file mode 100644
index fc2c4f78..00000000
--- a/src/systemd/src/basic/hash-funcs.c
+++ /dev/null
@@ -1,120 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#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);
-}
-
-const struct hash_ops string_hash_ops = {
-        .hash = string_hash_func,
-        .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);
-}
-
-int trivial_compare_func(const void *a, const void *b) {
-        return CMP(a, b);
-}
-
-const struct hash_ops trivial_hash_ops = {
-        .hash = trivial_hash_func,
-        .compare = trivial_compare_func
-};
-
-void uint64_hash_func(const void *p, struct siphash *state) {
-        siphash24_compress(p, sizeof(uint64_t), state);
-}
-
-int uint64_compare_func(const void *_a, const void *_b) {
-        uint64_t a, b;
-        a = *(const uint64_t*) _a;
-        b = *(const uint64_t*) _b;
-        return CMP(a, b);
-}
-
-const struct hash_ops uint64_hash_ops = {
-        .hash = uint64_hash_func,
-        .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);
-}
-
-int devt_compare_func(const void *_a, const void *_b) {
-        dev_t a, b;
-        a = *(const dev_t*) _a;
-        b = *(const dev_t*) _b;
-        return CMP(a, b);
-}
-
-const struct hash_ops devt_hash_ops = {
-        .hash = devt_hash_func,
-        .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
deleted file mode 100644
index fa45cfe2..00000000
--- a/src/systemd/src/basic/hash-funcs.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include "macro.h"
-#include "siphash24.h"
-
-typedef void (*hash_func_t)(const void *p, struct siphash *state);
-typedef int (*compare_func_t)(const void *a, const void *b);
-
-struct hash_ops {
-        hash_func_t hash;
-        compare_func_t compare;
-};
-
-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;
-
-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. */
-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! */
-#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;
-#else
-#define devt_hash_func uint64_hash_func
-#define devt_compare_func uint64_compare_func
-#define devt_hash_ops uint64_hash_ops
-#endif
diff --git a/src/systemd/src/basic/hashmap.c b/src/systemd/src/basic/hashmap.c
deleted file mode 100644
index cfddeafe..00000000
--- a/src/systemd/src/basic/hashmap.c
+++ /dev/null
@@ -1,1988 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "alloc-util.h"
-#include "env-util.h"
-#include "fileio.h"
-#include "hashmap.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"
-
-#if ENABLE_DEBUG_HASHMAP
-#include <pthread.h>
-#include "list.h"
-#endif
-
-/*
- * Implementation of hashmaps.
- * Addressing: open
- *   - uses less RAM compared to closed addressing (chaining), because
- *     our entries are small (especially in Sets, which tend to contain
- *     the majority of entries in systemd).
- * Collision resolution: Robin Hood
- *   - tends to equalize displacement of entries from their optimal buckets.
- * Probe sequence: linear
- *   - though theoretically worse than random probing/uniform hashing/double
- *     hashing, it is good for cache locality.
- *
- * References:
- * Celis, P. 1986. Robin Hood Hashing.
- * Ph.D. Dissertation. University of Waterloo, Waterloo, Ont., Canada, Canada.
- * https://cs.uwaterloo.ca/research/tr/1986/CS-86-14.pdf
- * - The results are derived for random probing. Suggests deletion with
- *   tombstones and two mean-centered search methods. None of that works
- *   well for linear probing.
- *
- * Janson, S. 2005. Individual displacements for linear probing hashing with different insertion policies.
- * ACM Trans. Algorithms 1, 2 (October 2005), 177-213.
- * DOI=10.1145/1103963.1103964 http://doi.acm.org/10.1145/1103963.1103964
- * http://www.math.uu.se/~svante/papers/sj157.pdf
- * - Applies to Robin Hood with linear probing. Contains remarks on
- *   the unsuitability of mean-centered search with linear probing.
- *
- * Viola, A. 2005. Exact distribution of individual displacements in linear probing hashing.
- * ACM Trans. Algorithms 1, 2 (October 2005), 214-242.
- * DOI=10.1145/1103963.1103965 http://doi.acm.org/10.1145/1103963.1103965
- * - Similar to Janson. Note that Viola writes about C_{m,n} (number of probes
- *   in a successful search), and Janson writes about displacement. C = d + 1.
- *
- * Goossaert, E. 2013. Robin Hood hashing: backward shift deletion.
- * http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
- * - Explanation of backward shift deletion with pictures.
- *
- * Khuong, P. 2013. The Other Robin Hood Hashing.
- * http://www.pvk.ca/Blog/2013/11/26/the-other-robin-hood-hashing/
- * - Short summary of random vs. linear probing, and tombstones vs. backward shift.
- */
-
-/*
- * XXX Ideas for improvement:
- * For unordered hashmaps, randomize iteration order, similarly to Perl:
- * http://blog.booking.com/hardening-perls-hash-function.html
- */
-
-/* INV_KEEP_FREE = 1 / (1 - max_load_factor)
- * e.g. 1 / (1 - 0.8) = 5 ... keep one fifth of the buckets free. */
-#define INV_KEEP_FREE            5U
-
-/* Fields common to entries of all hashmap/set types */
-struct hashmap_base_entry {
-        const void *key;
-};
-
-/* Entry types for specific hashmap/set types
- * hashmap_base_entry must be at the beginning of each entry struct. */
-
-struct plain_hashmap_entry {
-        struct hashmap_base_entry b;
-        void *value;
-};
-
-struct ordered_hashmap_entry {
-        struct plain_hashmap_entry p;
-        unsigned iterate_next, iterate_previous;
-};
-
-struct set_entry {
-        struct hashmap_base_entry b;
-};
-
-/* In several functions it is advantageous to have the hash table extended
- * virtually by a couple of additional buckets. We reserve special index values
- * for these "swap" buckets. */
-#define _IDX_SWAP_BEGIN     (UINT_MAX - 3)
-#define IDX_PUT             (_IDX_SWAP_BEGIN + 0)
-#define IDX_TMP             (_IDX_SWAP_BEGIN + 1)
-#define _IDX_SWAP_END       (_IDX_SWAP_BEGIN + 2)
-
-#define IDX_FIRST           (UINT_MAX - 1) /* special index for freshly initialized iterators */
-#define IDX_NIL             UINT_MAX       /* special index value meaning "none" or "end" */
-
-assert_cc(IDX_FIRST == _IDX_SWAP_END);
-assert_cc(IDX_FIRST == _IDX_ITERATOR_FIRST);
-
-/* Storage space for the "swap" buckets.
- * All entry types can fit into a ordered_hashmap_entry. */
-struct swap_entries {
-        struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN];
-};
-
-/* Distance from Initial Bucket */
-typedef uint8_t dib_raw_t;
-#define DIB_RAW_OVERFLOW ((dib_raw_t)0xfdU)   /* indicates DIB value is greater than representable */
-#define DIB_RAW_REHASH   ((dib_raw_t)0xfeU)   /* entry yet to be rehashed during in-place resize */
-#define DIB_RAW_FREE     ((dib_raw_t)0xffU)   /* a free bucket */
-#define DIB_RAW_INIT     ((char)DIB_RAW_FREE) /* a byte to memset a DIB store with when initializing */
-
-#define DIB_FREE UINT_MAX
-
-#if ENABLE_DEBUG_HASHMAP
-struct hashmap_debug_info {
-        LIST_FIELDS(struct hashmap_debug_info, debug_list);
-        unsigned max_entries;  /* high watermark of n_entries */
-
-        /* who allocated this hashmap */
-        int line;
-        const char *file;
-        const char *func;
-
-        /* fields to detect modification while iterating */
-        unsigned put_count;    /* counts puts into the hashmap */
-        unsigned rem_count;    /* counts removals from hashmap */
-        unsigned last_rem_idx; /* remembers last removal index */
-};
-
-/* Tracks all existing hashmaps. Get at it from gdb. See sd_dump_hashmaps.py */
-static LIST_HEAD(struct hashmap_debug_info, hashmap_debug_list);
-static pthread_mutex_t hashmap_debug_list_mutex = PTHREAD_MUTEX_INITIALIZER;
-
-#define HASHMAP_DEBUG_FIELDS struct hashmap_debug_info debug;
-
-#else /* !ENABLE_DEBUG_HASHMAP */
-#define HASHMAP_DEBUG_FIELDS
-#endif /* ENABLE_DEBUG_HASHMAP */
-
-enum HashmapType {
-        HASHMAP_TYPE_PLAIN,
-        HASHMAP_TYPE_ORDERED,
-        HASHMAP_TYPE_SET,
-        _HASHMAP_TYPE_MAX
-};
-
-struct _packed_ indirect_storage {
-        void *storage;                     /* where buckets and DIBs are stored */
-        uint8_t  hash_key[HASH_KEY_SIZE];  /* hash key; changes during resize */
-
-        unsigned n_entries;                /* number of stored entries */
-        unsigned n_buckets;                /* number of buckets */
-
-        unsigned idx_lowest_entry;         /* Index below which all buckets are free.
-                                              Makes "while(hashmap_steal_first())" loops
-                                              O(n) instead of O(n^2) for unordered hashmaps. */
-        uint8_t  _pad[3];                  /* padding for the whole HashmapBase */
-        /* The bitfields in HashmapBase complete the alignment of the whole thing. */
-};
-
-struct direct_storage {
-        /* This gives us 39 bytes on 64bit, or 35 bytes on 32bit.
-         * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64bit,
-         *              or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32bit. */
-        uint8_t storage[sizeof(struct indirect_storage)];
-};
-
-#define DIRECT_BUCKETS(entry_t) \
-        (sizeof(struct direct_storage) / (sizeof(entry_t) + sizeof(dib_raw_t)))
-
-/* We should be able to store at least one entry directly. */
-assert_cc(DIRECT_BUCKETS(struct ordered_hashmap_entry) >= 1);
-
-/* We have 3 bits for n_direct_entries. */
-assert_cc(DIRECT_BUCKETS(struct set_entry) < (1 << 3));
-
-/* Hashmaps with directly stored entries all use this shared hash key.
- * It's no big deal if the key is guessed, because there can be only
- * a handful of directly stored entries in a hashmap. When a hashmap
- * outgrows direct storage, it gets its own key for indirect storage. */
-static uint8_t shared_hash_key[HASH_KEY_SIZE];
-static bool shared_hash_key_initialized;
-
-/* Fields that all hashmap/set types must have */
-struct HashmapBase {
-        const struct hash_ops *hash_ops;  /* hash and compare ops to use */
-
-        union _packed_ {
-                struct indirect_storage indirect; /* if  has_indirect */
-                struct direct_storage direct;     /* if !has_indirect */
-        };
-
-        enum HashmapType type:2;     /* HASHMAP_TYPE_* */
-        bool has_indirect:1;         /* whether indirect storage is used */
-        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 */
-};
-
-/* Specific hash types
- * HashmapBase must be at the beginning of each hashmap struct. */
-
-struct Hashmap {
-        struct HashmapBase b;
-};
-
-struct OrderedHashmap {
-        struct HashmapBase b;
-        unsigned iterate_list_head, iterate_list_tail;
-};
-
-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 */
-assert_cc(sizeof(Hashmap) == sizeof(Set));
-
-struct hashmap_type_info {
-        size_t head_size;
-        size_t entry_size;
-        struct mempool *mempool;
-        unsigned n_direct_buckets;
-};
-
-static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = {
-        [HASHMAP_TYPE_PLAIN] = {
-                .head_size        = sizeof(Hashmap),
-                .entry_size       = sizeof(struct plain_hashmap_entry),
-                .mempool          = &hashmap_pool,
-                .n_direct_buckets = DIRECT_BUCKETS(struct plain_hashmap_entry),
-        },
-        [HASHMAP_TYPE_ORDERED] = {
-                .head_size        = sizeof(OrderedHashmap),
-                .entry_size       = sizeof(struct ordered_hashmap_entry),
-                .mempool          = &ordered_hashmap_pool,
-                .n_direct_buckets = DIRECT_BUCKETS(struct ordered_hashmap_entry),
-        },
-        [HASHMAP_TYPE_SET] = {
-                .head_size        = sizeof(Set),
-                .entry_size       = sizeof(struct set_entry),
-                .mempool          = &hashmap_pool,
-                .n_direct_buckets = DIRECT_BUCKETS(struct set_entry),
-        },
-};
-
-#if 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;
-}
-
-static unsigned n_entries(HashmapBase *h) {
-        return h->has_indirect ? h->indirect.n_entries
-                               : h->n_direct_entries;
-}
-
-static void n_entries_inc(HashmapBase *h) {
-        if (h->has_indirect)
-                h->indirect.n_entries++;
-        else
-                h->n_direct_entries++;
-}
-
-static void n_entries_dec(HashmapBase *h) {
-        if (h->has_indirect)
-                h->indirect.n_entries--;
-        else
-                h->n_direct_entries--;
-}
-
-static void *storage_ptr(HashmapBase *h) {
-        return h->has_indirect ? h->indirect.storage
-                               : h->direct.storage;
-}
-
-static uint8_t *hash_key(HashmapBase *h) {
-        return h->has_indirect ? h->indirect.hash_key
-                               : shared_hash_key;
-}
-
-static unsigned base_bucket_hash(HashmapBase *h, const void *p) {
-        struct siphash state;
-        uint64_t hash;
-
-        siphash24_init(&state, hash_key(h));
-
-        h->hash_ops->hash(p, &state);
-
-        hash = siphash24_finalize(&state);
-
-        return (unsigned) (hash % n_buckets(h));
-}
-#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;
-
-        /* Returns a hash function key to use. In order to keep things
-         * fast we will not generate a new key each time we allocate a
-         * new hash table. Instead, we'll just reuse the most recently
-         * generated one, except if we never generated one or when we
-         * are rehashing an entire hash table because we reached a
-         * fill level */
-
-        if (!current_initialized || !reuse_is_ok) {
-                random_bytes(current, sizeof(current));
-                current_initialized = true;
-        }
-
-        memcpy(hash_key, current, sizeof(current));
-}
-
-static struct hashmap_base_entry *bucket_at(HashmapBase *h, unsigned idx) {
-        return (struct hashmap_base_entry*)
-                ((uint8_t*) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size);
-}
-
-static struct plain_hashmap_entry *plain_bucket_at(Hashmap *h, unsigned idx) {
-        return (struct plain_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx);
-}
-
-static struct ordered_hashmap_entry *ordered_bucket_at(OrderedHashmap *h, unsigned idx) {
-        return (struct ordered_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx);
-}
-
-static struct set_entry *set_bucket_at(Set *h, unsigned idx) {
-        return (struct set_entry*) bucket_at(HASHMAP_BASE(h), idx);
-}
-
-static struct ordered_hashmap_entry *bucket_at_swap(struct swap_entries *swap, unsigned idx) {
-        return &swap->e[idx - _IDX_SWAP_BEGIN];
-}
-
-/* Returns a pointer to the bucket at index idx.
- * Understands real indexes and swap indexes, hence "_virtual". */
-static struct hashmap_base_entry *bucket_at_virtual(HashmapBase *h, struct swap_entries *swap,
-                                                    unsigned idx) {
-        if (idx < _IDX_SWAP_BEGIN)
-                return bucket_at(h, idx);
-
-        if (idx < _IDX_SWAP_END)
-                return &bucket_at_swap(swap, idx)->p.b;
-
-        assert_not_reached("Invalid index");
-}
-
-static dib_raw_t *dib_raw_ptr(HashmapBase *h) {
-        return (dib_raw_t*)
-                ((uint8_t*) storage_ptr(h) + hashmap_type_info[h->type].entry_size * n_buckets(h));
-}
-
-static unsigned bucket_distance(HashmapBase *h, unsigned idx, unsigned from) {
-        return idx >= from ? idx - from
-                           : n_buckets(h) + idx - from;
-}
-
-static unsigned bucket_calculate_dib(HashmapBase *h, unsigned idx, dib_raw_t raw_dib) {
-        unsigned initial_bucket;
-
-        if (raw_dib == DIB_RAW_FREE)
-                return DIB_FREE;
-
-        if (_likely_(raw_dib < DIB_RAW_OVERFLOW))
-                return raw_dib;
-
-        /*
-         * Having an overflow DIB value is very unlikely. The hash function
-         * would have to be bad. For example, in a table of size 2^24 filled
-         * to load factor 0.9 the maximum observed DIB is only about 60.
-         * In theory (assuming I used Maxima correctly), for an infinite size
-         * hash table with load factor 0.8 the probability of a given entry
-         * having DIB > 40 is 1.9e-8.
-         * This returns the correct DIB value by recomputing the hash value in
-         * the unlikely case. XXX Hitting this case could be a hint to rehash.
-         */
-        initial_bucket = bucket_hash(h, bucket_at(h, idx)->key);
-        return bucket_distance(h, idx, initial_bucket);
-}
-
-static void bucket_set_dib(HashmapBase *h, unsigned idx, unsigned dib) {
-        dib_raw_ptr(h)[idx] = dib != DIB_FREE ? MIN(dib, DIB_RAW_OVERFLOW) : DIB_RAW_FREE;
-}
-
-static unsigned skip_free_buckets(HashmapBase *h, unsigned idx) {
-        dib_raw_t *dibs;
-
-        dibs = dib_raw_ptr(h);
-
-        for ( ; idx < n_buckets(h); idx++)
-                if (dibs[idx] != DIB_RAW_FREE)
-                        return idx;
-
-        return IDX_NIL;
-}
-
-static void bucket_mark_free(HashmapBase *h, unsigned idx) {
-        memzero(bucket_at(h, idx), hashmap_type_info[h->type].entry_size);
-        bucket_set_dib(h, idx, DIB_FREE);
-}
-
-static void bucket_move_entry(HashmapBase *h, struct swap_entries *swap,
-                              unsigned from, unsigned to) {
-        struct hashmap_base_entry *e_from, *e_to;
-
-        assert(from != to);
-
-        e_from = bucket_at_virtual(h, swap, from);
-        e_to   = bucket_at_virtual(h, swap, to);
-
-        memcpy(e_to, e_from, hashmap_type_info[h->type].entry_size);
-
-        if (h->type == HASHMAP_TYPE_ORDERED) {
-                OrderedHashmap *lh = (OrderedHashmap*) h;
-                struct ordered_hashmap_entry *le, *le_to;
-
-                le_to = (struct ordered_hashmap_entry*) e_to;
-
-                if (le_to->iterate_next != IDX_NIL) {
-                        le = (struct ordered_hashmap_entry*)
-                             bucket_at_virtual(h, swap, le_to->iterate_next);
-                        le->iterate_previous = to;
-                }
-
-                if (le_to->iterate_previous != IDX_NIL) {
-                        le = (struct ordered_hashmap_entry*)
-                             bucket_at_virtual(h, swap, le_to->iterate_previous);
-                        le->iterate_next = to;
-                }
-
-                if (lh->iterate_list_head == from)
-                        lh->iterate_list_head = to;
-                if (lh->iterate_list_tail == from)
-                        lh->iterate_list_tail = to;
-        }
-}
-
-static unsigned next_idx(HashmapBase *h, unsigned idx) {
-        return (idx + 1U) % n_buckets(h);
-}
-
-static unsigned prev_idx(HashmapBase *h, unsigned idx) {
-        return (n_buckets(h) + idx - 1U) % n_buckets(h);
-}
-
-static void *entry_value(HashmapBase *h, struct hashmap_base_entry *e) {
-        switch (h->type) {
-
-        case HASHMAP_TYPE_PLAIN:
-        case HASHMAP_TYPE_ORDERED:
-                return ((struct plain_hashmap_entry*)e)->value;
-
-        case HASHMAP_TYPE_SET:
-                return (void*) e->key;
-
-        default:
-                assert_not_reached("Unknown hashmap type");
-        }
-}
-
-static void base_remove_entry(HashmapBase *h, unsigned idx) {
-        unsigned left, right, prev, dib;
-        dib_raw_t raw_dib, *dibs;
-
-        dibs = dib_raw_ptr(h);
-        assert(dibs[idx] != DIB_RAW_FREE);
-
-#if ENABLE_DEBUG_HASHMAP
-        h->debug.rem_count++;
-        h->debug.last_rem_idx = idx;
-#endif
-
-        left = idx;
-        /* Find the stop bucket ("right"). It is either free or has DIB == 0. */
-        for (right = next_idx(h, left); ; right = next_idx(h, right)) {
-                raw_dib = dibs[right];
-                if (IN_SET(raw_dib, 0, DIB_RAW_FREE))
-                        break;
-
-                /* The buckets are not supposed to be all occupied and with DIB > 0.
-                 * That would mean we could make everyone better off by shifting them
-                 * backward. This scenario is impossible. */
-                assert(left != right);
-        }
-
-        if (h->type == HASHMAP_TYPE_ORDERED) {
-                OrderedHashmap *lh = (OrderedHashmap*) h;
-                struct ordered_hashmap_entry *le = ordered_bucket_at(lh, idx);
-
-                if (le->iterate_next != IDX_NIL)
-                        ordered_bucket_at(lh, le->iterate_next)->iterate_previous = le->iterate_previous;
-                else
-                        lh->iterate_list_tail = le->iterate_previous;
-
-                if (le->iterate_previous != IDX_NIL)
-                        ordered_bucket_at(lh, le->iterate_previous)->iterate_next = le->iterate_next;
-                else
-                        lh->iterate_list_head = le->iterate_next;
-        }
-
-        /* Now shift all buckets in the interval (left, right) one step backwards */
-        for (prev = left, left = next_idx(h, left); left != right;
-             prev = left, left = next_idx(h, left)) {
-                dib = bucket_calculate_dib(h, left, dibs[left]);
-                assert(dib != 0);
-                bucket_move_entry(h, NULL, left, prev);
-                bucket_set_dib(h, prev, dib - 1);
-        }
-
-        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)
-
-static unsigned hashmap_iterate_in_insertion_order(OrderedHashmap *h, Iterator *i) {
-        struct ordered_hashmap_entry *e;
-        unsigned idx;
-
-        assert(h);
-        assert(i);
-
-        if (i->idx == IDX_NIL)
-                goto at_end;
-
-        if (i->idx == IDX_FIRST && h->iterate_list_head == IDX_NIL)
-                goto at_end;
-
-        if (i->idx == IDX_FIRST) {
-                idx = h->iterate_list_head;
-                e = ordered_bucket_at(h, idx);
-        } else {
-                idx = i->idx;
-                e = ordered_bucket_at(h, idx);
-                /*
-                 * We allow removing the current entry while iterating, but removal may cause
-                 * a backward shift. The next entry may thus move one bucket to the left.
-                 * To detect when it happens, we remember the key pointer of the entry we were
-                 * going to iterate next. If it does not match, there was a backward shift.
-                 */
-                if (e->p.b.key != i->next_key) {
-                        idx = prev_idx(HASHMAP_BASE(h), idx);
-                        e = ordered_bucket_at(h, idx);
-                }
-                assert(e->p.b.key == i->next_key);
-        }
-
-#if ENABLE_DEBUG_HASHMAP
-        i->prev_idx = idx;
-#endif
-
-        if (e->iterate_next != IDX_NIL) {
-                struct ordered_hashmap_entry *n;
-                i->idx = e->iterate_next;
-                n = ordered_bucket_at(h, i->idx);
-                i->next_key = n->p.b.key;
-        } else
-                i->idx = IDX_NIL;
-
-        return idx;
-
-at_end:
-        i->idx = IDX_NIL;
-        return IDX_NIL;
-}
-
-static unsigned hashmap_iterate_in_internal_order(HashmapBase *h, Iterator *i) {
-        unsigned idx;
-
-        assert(h);
-        assert(i);
-
-        if (i->idx == IDX_NIL)
-                goto at_end;
-
-        if (i->idx == IDX_FIRST) {
-                /* fast forward to the first occupied bucket */
-                if (h->has_indirect) {
-                        i->idx = skip_free_buckets(h, h->indirect.idx_lowest_entry);
-                        h->indirect.idx_lowest_entry = i->idx;
-                } else
-                        i->idx = skip_free_buckets(h, 0);
-
-                if (i->idx == IDX_NIL)
-                        goto at_end;
-        } else {
-                struct hashmap_base_entry *e;
-
-                assert(i->idx > 0);
-
-                e = bucket_at(h, i->idx);
-                /*
-                 * We allow removing the current entry while iterating, but removal may cause
-                 * a backward shift. The next entry may thus move one bucket to the left.
-                 * To detect when it happens, we remember the key pointer of the entry we were
-                 * going to iterate next. If it does not match, there was a backward shift.
-                 */
-                if (e->key != i->next_key)
-                        e = bucket_at(h, --i->idx);
-
-                assert(e->key == i->next_key);
-        }
-
-        idx = i->idx;
-#if ENABLE_DEBUG_HASHMAP
-        i->prev_idx = idx;
-#endif
-
-        i->idx = skip_free_buckets(h, i->idx + 1);
-        if (i->idx != IDX_NIL)
-                i->next_key = bucket_at(h, i->idx)->key;
-        else
-                i->idx = IDX_NIL;
-
-        return idx;
-
-at_end:
-        i->idx = IDX_NIL;
-        return IDX_NIL;
-}
-
-static unsigned hashmap_iterate_entry(HashmapBase *h, Iterator *i) {
-        if (!h) {
-                i->idx = IDX_NIL;
-                return IDX_NIL;
-        }
-
-#if ENABLE_DEBUG_HASHMAP
-        if (i->idx == IDX_FIRST) {
-                i->put_count = h->debug.put_count;
-                i->rem_count = h->debug.rem_count;
-        } else {
-                /* While iterating, must not add any new entries */
-                assert(i->put_count == h->debug.put_count);
-                /* ... or remove entries other than the current one */
-                assert(i->rem_count == h->debug.rem_count ||
-                       (i->rem_count == h->debug.rem_count - 1 &&
-                        i->prev_idx == h->debug.last_rem_idx));
-                /* Reset our removals counter */
-                i->rem_count = h->debug.rem_count;
-        }
-#endif
-
-        return h->type == HASHMAP_TYPE_ORDERED ? hashmap_iterate_in_insertion_order((OrderedHashmap*) h, i)
-                                               : hashmap_iterate_in_internal_order(h, i);
-}
-
-bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key) {
-        struct hashmap_base_entry *e;
-        void *data;
-        unsigned idx;
-
-        idx = hashmap_iterate_entry(h, i);
-        if (idx == IDX_NIL) {
-                if (value)
-                        *value = NULL;
-                if (key)
-                        *key = NULL;
-
-                return false;
-        }
-
-        e = bucket_at(h, idx);
-        data = entry_value(h, e);
-        if (value)
-                *value = data;
-        if (key)
-                *key = e->key;
-
-        return true;
-}
-
-bool set_iterate(Set *s, Iterator *i, void **value) {
-        return internal_hashmap_iterate(HASHMAP_BASE(s), i, value, NULL);
-}
-
-#define HASHMAP_FOREACH_IDX(idx, h, i) \
-        for ((i) = ITERATOR_FIRST, (idx) = hashmap_iterate_entry((h), &(i)); \
-             (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;
-
-        assert(!h->has_indirect);
-
-        p = mempset(h->direct.storage, 0, hi->entry_size * hi->n_direct_buckets);
-        memset(p, DIB_RAW_INIT, sizeof(dib_raw_t) * hi->n_direct_buckets);
-}
-
-static bool use_pool(void) {
-        static int b = -1;
-
-        if (!is_main_thread())
-                return false;
-
-        if (b < 0)
-                b = getenv_bool("SYSTEMD_MEMPOOL") != 0;
-
-        return b;
-}
-
-static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) {
-        HashmapBase *h;
-        const struct hashmap_type_info *hi = &hashmap_type_info[type];
-        bool up;
-
-        up = use_pool();
-
-        h = up ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size);
-        if (!h)
-                return NULL;
-
-        h->type = type;
-        h->from_pool = up;
-        h->hash_ops = hash_ops ? hash_ops : &trivial_hash_ops;
-
-        if (type == HASHMAP_TYPE_ORDERED) {
-                OrderedHashmap *lh = (OrderedHashmap*)h;
-                lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL;
-        }
-
-        reset_direct_storage(h);
-
-        if (!shared_hash_key_initialized) {
-                random_bytes(shared_hash_key, sizeof(shared_hash_key));
-                shared_hash_key_initialized= true;
-        }
-
-#if ENABLE_DEBUG_HASHMAP
-        h->debug.func = func;
-        h->debug.file = file;
-        h->debug.line = line;
-        assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0);
-        LIST_PREPEND(debug_list, hashmap_debug_list, &h->debug);
-        assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0);
-#endif
-
-        return h;
-}
-
-Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
-        return (Hashmap*)        hashmap_base_new(hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS);
-}
-
-OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
-        return (OrderedHashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS);
-}
-
-Set *internal_set_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
-        return (Set*)            hashmap_base_new(hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS);
-}
-
-static int hashmap_base_ensure_allocated(HashmapBase **h, const struct hash_ops *hash_ops,
-                                         enum HashmapType type HASHMAP_DEBUG_PARAMS) {
-        HashmapBase *q;
-
-        assert(h);
-
-        if (*h)
-                return 0;
-
-        q = hashmap_base_new(hash_ops, type HASHMAP_DEBUG_PASS_ARGS);
-        if (!q)
-                return -ENOMEM;
-
-        *h = q;
-        return 0;
-}
-
-int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
-        return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS);
-}
-
-int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
-        return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS);
-}
-
-int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS) {
-        return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS);
-}
-
-static void hashmap_free_no_clear(HashmapBase *h) {
-        assert(!h->has_indirect);
-        assert(!h->n_direct_entries);
-
-#if ENABLE_DEBUG_HASHMAP
-        assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0);
-        LIST_REMOVE(debug_list, hashmap_debug_list, &h->debug);
-        assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0);
-#endif
-
-        if (h->from_pool) {
-                /* Ensure that the object didn't get migrated between threads. */
-                assert_se(is_main_thread());
-                mempool_free_tile(hashmap_type_info[h->type].mempool, h);
-        } else
-                free(h);
-}
-
-HashmapBase *internal_hashmap_free(HashmapBase *h) {
-
-        /* Free the hashmap, but nothing in it */
-
-        if (h) {
-                internal_hashmap_clear(h);
-                hashmap_free_no_clear(h);
-        }
-
-        return NULL;
-}
-
-HashmapBase *internal_hashmap_free_free(HashmapBase *h) {
-
-        /* Free the hashmap and all data objects in it, but not the
-         * keys */
-
-        if (h) {
-                internal_hashmap_clear_free(h);
-                hashmap_free_no_clear(h);
-        }
-
-        return NULL;
-}
-
-Hashmap *hashmap_free_free_free(Hashmap *h) {
-
-        /* Free the hashmap and all data and key objects in it */
-
-        if (h) {
-                hashmap_clear_free_free(h);
-                hashmap_free_no_clear(HASHMAP_BASE(h));
-        }
-
-        return NULL;
-}
-
-void internal_hashmap_clear(HashmapBase *h) {
-        if (!h)
-                return;
-
-        if (h->has_indirect) {
-                free(h->indirect.storage);
-                h->has_indirect = false;
-        }
-
-        h->n_direct_entries = 0;
-        reset_direct_storage(h);
-
-        if (h->type == HASHMAP_TYPE_ORDERED) {
-                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) {
-        unsigned idx;
-
-        if (!h)
-                return;
-
-        for (idx = skip_free_buckets(h, 0); idx != IDX_NIL;
-             idx = skip_free_buckets(h, idx + 1))
-                free(entry_value(h, bucket_at(h, idx)));
-
-        internal_hashmap_clear(h);
-}
-
-void hashmap_clear_free_free(Hashmap *h) {
-        unsigned idx;
-
-        if (!h)
-                return;
-
-        for (idx = skip_free_buckets(HASHMAP_BASE(h), 0); idx != IDX_NIL;
-             idx = skip_free_buckets(HASHMAP_BASE(h), idx + 1)) {
-                struct plain_hashmap_entry *e = plain_bucket_at(h, idx);
-                free((void*)e->b.key);
-                free(e->value);
-        }
-
-        internal_hashmap_clear(HASHMAP_BASE(h));
-}
-
-static int resize_buckets(HashmapBase *h, unsigned entries_add);
-
-/*
- * Finds an empty bucket to put an entry into, starting the scan at 'idx'.
- * Performs Robin Hood swaps as it goes. The entry to put must be placed
- * by the caller into swap slot IDX_PUT.
- * If used for in-place resizing, may leave a displaced entry in swap slot
- * IDX_PUT. Caller must rehash it next.
- * Returns: true if it left a displaced entry to rehash next in IDX_PUT,
- *          false otherwise.
- */
-static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx,
-                                   struct swap_entries *swap) {
-        dib_raw_t raw_dib, *dibs;
-        unsigned dib, distance;
-
-#if ENABLE_DEBUG_HASHMAP
-        h->debug.put_count++;
-#endif
-
-        dibs = dib_raw_ptr(h);
-
-        for (distance = 0; ; distance++) {
-                raw_dib = dibs[idx];
-                if (IN_SET(raw_dib, DIB_RAW_FREE, DIB_RAW_REHASH)) {
-                        if (raw_dib == DIB_RAW_REHASH)
-                                bucket_move_entry(h, swap, idx, IDX_TMP);
-
-                        if (h->has_indirect && h->indirect.idx_lowest_entry > idx)
-                                h->indirect.idx_lowest_entry = idx;
-
-                        bucket_set_dib(h, idx, distance);
-                        bucket_move_entry(h, swap, IDX_PUT, idx);
-                        if (raw_dib == DIB_RAW_REHASH) {
-                                bucket_move_entry(h, swap, IDX_TMP, IDX_PUT);
-                                return true;
-                        }
-
-                        return false;
-                }
-
-                dib = bucket_calculate_dib(h, idx, raw_dib);
-
-                if (dib < distance) {
-                        /* Found a wealthier entry. Go Robin Hood! */
-                        bucket_set_dib(h, idx, distance);
-
-                        /* swap the entries */
-                        bucket_move_entry(h, swap, idx, IDX_TMP);
-                        bucket_move_entry(h, swap, IDX_PUT, idx);
-                        bucket_move_entry(h, swap, IDX_TMP, IDX_PUT);
-
-                        distance = dib;
-                }
-
-                idx = next_idx(h, idx);
-        }
-}
-
-/*
- * Puts an entry into a hashmap, boldly - no check whether key already exists.
- * The caller must place the entry (only its key and value, not link indexes)
- * in swap slot IDX_PUT.
- * Caller must ensure: the key does not exist yet in the hashmap.
- *                     that resize is not needed if !may_resize.
- * Returns: 1 if entry was put successfully.
- *          -ENOMEM if may_resize==true and resize failed with -ENOMEM.
- *          Cannot return -ENOMEM if !may_resize.
- */
-static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx,
-                                   struct swap_entries *swap, bool may_resize) {
-        struct ordered_hashmap_entry *new_entry;
-        int r;
-
-        assert(idx < n_buckets(h));
-
-        new_entry = bucket_at_swap(swap, IDX_PUT);
-
-        if (may_resize) {
-                r = resize_buckets(h, 1);
-                if (r < 0)
-                        return r;
-                if (r > 0)
-                        idx = bucket_hash(h, new_entry->p.b.key);
-        }
-        assert(n_entries(h) < n_buckets(h));
-
-        if (h->type == HASHMAP_TYPE_ORDERED) {
-                OrderedHashmap *lh = (OrderedHashmap*) h;
-
-                new_entry->iterate_next = IDX_NIL;
-                new_entry->iterate_previous = lh->iterate_list_tail;
-
-                if (lh->iterate_list_tail != IDX_NIL) {
-                        struct ordered_hashmap_entry *old_tail;
-
-                        old_tail = ordered_bucket_at(lh, lh->iterate_list_tail);
-                        assert(old_tail->iterate_next == IDX_NIL);
-                        old_tail->iterate_next = IDX_PUT;
-                }
-
-                lh->iterate_list_tail = IDX_PUT;
-                if (lh->iterate_list_head == IDX_NIL)
-                        lh->iterate_list_head = IDX_PUT;
-        }
-
-        assert_se(hashmap_put_robin_hood(h, idx, swap) == false);
-
-        n_entries_inc(h);
-#if ENABLE_DEBUG_HASHMAP
-        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) \
-        hashmap_base_put_boldly(HASHMAP_BASE(h), idx, swap, may_resize)
-
-/*
- * Returns 0 if resize is not needed.
- *         1 if successfully resized.
- *         -ENOMEM on allocation failure.
- */
-static int resize_buckets(HashmapBase *h, unsigned entries_add) {
-        struct swap_entries swap;
-        void *new_storage;
-        dib_raw_t *old_dibs, *new_dibs;
-        const struct hashmap_type_info *hi;
-        unsigned idx, optimal_idx;
-        unsigned old_n_buckets, new_n_buckets, n_rehashed, new_n_entries;
-        uint8_t new_shift;
-        bool rehash_next;
-
-        assert(h);
-
-        hi = &hashmap_type_info[h->type];
-        new_n_entries = n_entries(h) + entries_add;
-
-        /* overflow? */
-        if (_unlikely_(new_n_entries < entries_add))
-                return -ENOMEM;
-
-        /* For direct storage we allow 100% load, because it's tiny. */
-        if (!h->has_indirect && new_n_entries <= hi->n_direct_buckets)
-                return 0;
-
-        /*
-         * Load factor = n/m = 1 - (1/INV_KEEP_FREE).
-         * From it follows: m = n + n/(INV_KEEP_FREE - 1)
-         */
-        new_n_buckets = new_n_entries + new_n_entries / (INV_KEEP_FREE - 1);
-        /* overflow? */
-        if (_unlikely_(new_n_buckets < new_n_entries))
-                return -ENOMEM;
-
-        if (_unlikely_(new_n_buckets > UINT_MAX / (hi->entry_size + sizeof(dib_raw_t))))
-                return -ENOMEM;
-
-        old_n_buckets = n_buckets(h);
-
-        if (_likely_(new_n_buckets <= old_n_buckets))
-                return 0;
-
-        new_shift = log2u_round_up(MAX(
-                        new_n_buckets * (hi->entry_size + sizeof(dib_raw_t)),
-                        2 * sizeof(struct direct_storage)));
-
-        /* Realloc storage (buckets and DIB array). */
-        new_storage = realloc(h->has_indirect ? h->indirect.storage : NULL,
-                              1U << new_shift);
-        if (!new_storage)
-                return -ENOMEM;
-
-        /* Must upgrade direct to indirect storage. */
-        if (!h->has_indirect) {
-                memcpy(new_storage, h->direct.storage,
-                       old_n_buckets * (hi->entry_size + sizeof(dib_raw_t)));
-                h->indirect.n_entries = h->n_direct_entries;
-                h->indirect.idx_lowest_entry = 0;
-                h->n_direct_entries = 0;
-        }
-
-        /* Get a new hash key. If we've just upgraded to indirect storage,
-         * allow reusing a previously generated key. It's still a different key
-         * from the shared one that we used for direct storage. */
-        get_hash_key(h->indirect.hash_key, !h->has_indirect);
-
-        h->has_indirect = true;
-        h->indirect.storage = new_storage;
-        h->indirect.n_buckets = (1U << new_shift) /
-                                (hi->entry_size + sizeof(dib_raw_t));
-
-        old_dibs = (dib_raw_t*)((uint8_t*) new_storage + hi->entry_size * old_n_buckets);
-        new_dibs = dib_raw_ptr(h);
-
-        /*
-         * Move the DIB array to the new place, replacing valid DIB values with
-         * DIB_RAW_REHASH to indicate all of the used buckets need rehashing.
-         * Note: Overlap is not possible, because we have at least doubled the
-         * number of buckets and dib_raw_t is smaller than any entry type.
-         */
-        for (idx = 0; idx < old_n_buckets; idx++) {
-                assert(old_dibs[idx] != DIB_RAW_REHASH);
-                new_dibs[idx] = old_dibs[idx] == DIB_RAW_FREE ? DIB_RAW_FREE
-                                                              : DIB_RAW_REHASH;
-        }
-
-        /* Zero the area of newly added entries (including the old DIB area) */
-        memzero(bucket_at(h, old_n_buckets),
-               (n_buckets(h) - old_n_buckets) * hi->entry_size);
-
-        /* The upper half of the new DIB array needs initialization */
-        memset(&new_dibs[old_n_buckets], DIB_RAW_INIT,
-               (n_buckets(h) - old_n_buckets) * sizeof(dib_raw_t));
-
-        /* Rehash entries that need it */
-        n_rehashed = 0;
-        for (idx = 0; idx < old_n_buckets; idx++) {
-                if (new_dibs[idx] != DIB_RAW_REHASH)
-                        continue;
-
-                optimal_idx = bucket_hash(h, bucket_at(h, idx)->key);
-
-                /*
-                 * Not much to do if by luck the entry hashes to its current
-                 * location. Just set its DIB.
-                 */
-                if (optimal_idx == idx) {
-                        new_dibs[idx] = 0;
-                        n_rehashed++;
-                        continue;
-                }
-
-                new_dibs[idx] = DIB_RAW_FREE;
-                bucket_move_entry(h, &swap, idx, IDX_PUT);
-                /* bucket_move_entry does not clear the source */
-                memzero(bucket_at(h, idx), hi->entry_size);
-
-                do {
-                        /*
-                         * Find the new bucket for the current entry. This may make
-                         * another entry homeless and load it into IDX_PUT.
-                         */
-                        rehash_next = hashmap_put_robin_hood(h, optimal_idx, &swap);
-                        n_rehashed++;
-
-                        /* Did the current entry displace another one? */
-                        if (rehash_next)
-                                optimal_idx = bucket_hash(h, bucket_at_swap(&swap, IDX_PUT)->p.b.key);
-                } while (rehash_next);
-        }
-
-        assert(n_rehashed == n_entries(h));
-
-        return 1;
-}
-
-/*
- * Finds an entry with a matching key
- * Returns: index of the found entry, or IDX_NIL if not found.
- */
-static unsigned base_bucket_scan(HashmapBase *h, unsigned idx, const void *key) {
-        struct hashmap_base_entry *e;
-        unsigned dib, distance;
-        dib_raw_t *dibs = dib_raw_ptr(h);
-
-        assert(idx < n_buckets(h));
-
-        for (distance = 0; ; distance++) {
-                if (dibs[idx] == DIB_RAW_FREE)
-                        return IDX_NIL;
-
-                dib = bucket_calculate_dib(h, idx, dibs[idx]);
-
-                if (dib < distance)
-                        return IDX_NIL;
-                if (dib == distance) {
-                        e = bucket_at(h, idx);
-                        if (h->hash_ops->compare(e->key, key) == 0)
-                                return idx;
-                }
-
-                idx = next_idx(h, idx);
-        }
-}
-#define bucket_scan(h, idx, key) base_bucket_scan(HASHMAP_BASE(h), idx, key)
-
-int hashmap_put(Hashmap *h, const void *key, void *value) {
-        struct swap_entries swap;
-        struct plain_hashmap_entry *e;
-        unsigned hash, idx;
-
-        assert(h);
-
-        hash = bucket_hash(h, key);
-        idx = bucket_scan(h, hash, key);
-        if (idx != IDX_NIL) {
-                e = plain_bucket_at(h, idx);
-                if (e->value == value)
-                        return 0;
-                return -EEXIST;
-        }
-
-        e = &bucket_at_swap(&swap, IDX_PUT)->p;
-        e->b.key = key;
-        e->value = value;
-        return hashmap_put_boldly(h, hash, &swap, true);
-}
-
-int set_put(Set *s, const void *key) {
-        struct swap_entries swap;
-        struct hashmap_base_entry *e;
-        unsigned hash, idx;
-
-        assert(s);
-
-        hash = bucket_hash(s, key);
-        idx = bucket_scan(s, hash, key);
-        if (idx != IDX_NIL)
-                return 0;
-
-        e = &bucket_at_swap(&swap, IDX_PUT)->p.b;
-        e->key = key;
-        return hashmap_put_boldly(s, hash, &swap, true);
-}
-
-int hashmap_replace(Hashmap *h, const void *key, void *value) {
-        struct swap_entries swap;
-        struct plain_hashmap_entry *e;
-        unsigned hash, idx;
-
-        assert(h);
-
-        hash = bucket_hash(h, key);
-        idx = bucket_scan(h, hash, key);
-        if (idx != IDX_NIL) {
-                e = plain_bucket_at(h, idx);
-#if ENABLE_DEBUG_HASHMAP
-                /* Although the key is equal, the key pointer may have changed,
-                 * and this would break our assumption for iterating. So count
-                 * this operation as incompatible with iteration. */
-                if (e->b.key != key) {
-                        h->b.debug.put_count++;
-                        h->b.debug.rem_count++;
-                        h->b.debug.last_rem_idx = idx;
-                }
-#endif
-                e->b.key = key;
-                e->value = value;
-                hashmap_set_dirty(h);
-
-                return 0;
-        }
-
-        e = &bucket_at_swap(&swap, IDX_PUT)->p;
-        e->b.key = key;
-        e->value = value;
-        return hashmap_put_boldly(h, hash, &swap, true);
-}
-
-int hashmap_update(Hashmap *h, const void *key, void *value) {
-        struct plain_hashmap_entry *e;
-        unsigned hash, idx;
-
-        assert(h);
-
-        hash = bucket_hash(h, key);
-        idx = bucket_scan(h, hash, key);
-        if (idx == IDX_NIL)
-                return -ENOENT;
-
-        e = plain_bucket_at(h, idx);
-        e->value = value;
-        hashmap_set_dirty(h);
-
-        return 0;
-}
-
-void *internal_hashmap_get(HashmapBase *h, const void *key) {
-        struct hashmap_base_entry *e;
-        unsigned hash, idx;
-
-        if (!h)
-                return NULL;
-
-        hash = bucket_hash(h, key);
-        idx = bucket_scan(h, hash, key);
-        if (idx == IDX_NIL)
-                return NULL;
-
-        e = bucket_at(h, idx);
-        return entry_value(h, e);
-}
-
-void *hashmap_get2(Hashmap *h, const void *key, void **key2) {
-        struct plain_hashmap_entry *e;
-        unsigned hash, idx;
-
-        if (!h)
-                return NULL;
-
-        hash = bucket_hash(h, key);
-        idx = bucket_scan(h, hash, key);
-        if (idx == IDX_NIL)
-                return NULL;
-
-        e = plain_bucket_at(h, idx);
-        if (key2)
-                *key2 = (void*) e->b.key;
-
-        return e->value;
-}
-
-bool internal_hashmap_contains(HashmapBase *h, const void *key) {
-        unsigned hash;
-
-        if (!h)
-                return false;
-
-        hash = bucket_hash(h, key);
-        return bucket_scan(h, hash, key) != IDX_NIL;
-}
-
-void *internal_hashmap_remove(HashmapBase *h, const void *key) {
-        struct hashmap_base_entry *e;
-        unsigned hash, idx;
-        void *data;
-
-        if (!h)
-                return NULL;
-
-        hash = bucket_hash(h, key);
-        idx = bucket_scan(h, hash, key);
-        if (idx == IDX_NIL)
-                return NULL;
-
-        e = bucket_at(h, idx);
-        data = entry_value(h, e);
-        remove_entry(h, idx);
-
-        return data;
-}
-
-void *hashmap_remove2(Hashmap *h, const void *key, void **rkey) {
-        struct plain_hashmap_entry *e;
-        unsigned hash, idx;
-        void *data;
-
-        if (!h) {
-                if (rkey)
-                        *rkey = NULL;
-                return NULL;
-        }
-
-        hash = bucket_hash(h, key);
-        idx = bucket_scan(h, hash, key);
-        if (idx == IDX_NIL) {
-                if (rkey)
-                        *rkey = NULL;
-                return NULL;
-        }
-
-        e = plain_bucket_at(h, idx);
-        data = e->value;
-        if (rkey)
-                *rkey = (void*) e->b.key;
-
-        remove_entry(h, idx);
-
-        return data;
-}
-
-int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) {
-        struct swap_entries swap;
-        struct plain_hashmap_entry *e;
-        unsigned old_hash, new_hash, idx;
-
-        if (!h)
-                return -ENOENT;
-
-        old_hash = bucket_hash(h, old_key);
-        idx = bucket_scan(h, old_hash, old_key);
-        if (idx == IDX_NIL)
-                return -ENOENT;
-
-        new_hash = bucket_hash(h, new_key);
-        if (bucket_scan(h, new_hash, new_key) != IDX_NIL)
-                return -EEXIST;
-
-        remove_entry(h, idx);
-
-        e = &bucket_at_swap(&swap, IDX_PUT)->p;
-        e->b.key = new_key;
-        e->value = value;
-        assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1);
-
-        return 0;
-}
-
-int set_remove_and_put(Set *s, const void *old_key, const void *new_key) {
-        struct swap_entries swap;
-        struct hashmap_base_entry *e;
-        unsigned old_hash, new_hash, idx;
-
-        if (!s)
-                return -ENOENT;
-
-        old_hash = bucket_hash(s, old_key);
-        idx = bucket_scan(s, old_hash, old_key);
-        if (idx == IDX_NIL)
-                return -ENOENT;
-
-        new_hash = bucket_hash(s, new_key);
-        if (bucket_scan(s, new_hash, new_key) != IDX_NIL)
-                return -EEXIST;
-
-        remove_entry(s, idx);
-
-        e = &bucket_at_swap(&swap, IDX_PUT)->p.b;
-        e->key = new_key;
-        assert_se(hashmap_put_boldly(s, new_hash, &swap, false) == 1);
-
-        return 0;
-}
-
-int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) {
-        struct swap_entries swap;
-        struct plain_hashmap_entry *e;
-        unsigned old_hash, new_hash, idx_old, idx_new;
-
-        if (!h)
-                return -ENOENT;
-
-        old_hash = bucket_hash(h, old_key);
-        idx_old = bucket_scan(h, old_hash, old_key);
-        if (idx_old == IDX_NIL)
-                return -ENOENT;
-
-        old_key = bucket_at(HASHMAP_BASE(h), idx_old)->key;
-
-        new_hash = bucket_hash(h, new_key);
-        idx_new = bucket_scan(h, new_hash, new_key);
-        if (idx_new != IDX_NIL)
-                if (idx_old != idx_new) {
-                        remove_entry(h, idx_new);
-                        /* Compensate for a possible backward shift. */
-                        if (old_key != bucket_at(HASHMAP_BASE(h), idx_old)->key)
-                                idx_old = prev_idx(HASHMAP_BASE(h), idx_old);
-                        assert(old_key == bucket_at(HASHMAP_BASE(h), idx_old)->key);
-                }
-
-        remove_entry(h, idx_old);
-
-        e = &bucket_at_swap(&swap, IDX_PUT)->p;
-        e->b.key = new_key;
-        e->value = value;
-        assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1);
-
-        return 0;
-}
-
-void *hashmap_remove_value(Hashmap *h, const void *key, void *value) {
-        struct plain_hashmap_entry *e;
-        unsigned hash, idx;
-
-        if (!h)
-                return NULL;
-
-        hash = bucket_hash(h, key);
-        idx = bucket_scan(h, hash, key);
-        if (idx == IDX_NIL)
-                return NULL;
-
-        e = plain_bucket_at(h, idx);
-        if (e->value != value)
-                return NULL;
-
-        remove_entry(h, idx);
-
-        return value;
-}
-
-static unsigned find_first_entry(HashmapBase *h) {
-        Iterator i = ITERATOR_FIRST;
-
-        if (!h || !n_entries(h))
-                return IDX_NIL;
-
-        return hashmap_iterate_entry(h, &i);
-}
-
-void *internal_hashmap_first(HashmapBase *h) {
-        unsigned idx;
-
-        idx = find_first_entry(h);
-        if (idx == IDX_NIL)
-                return NULL;
-
-        return entry_value(h, bucket_at(h, idx));
-}
-
-void *internal_hashmap_first_key(HashmapBase *h) {
-        struct hashmap_base_entry *e;
-        unsigned idx;
-
-        idx = find_first_entry(h);
-        if (idx == IDX_NIL)
-                return NULL;
-
-        e = bucket_at(h, idx);
-        return (void*) e->key;
-}
-
-void *internal_hashmap_steal_first(HashmapBase *h) {
-        struct hashmap_base_entry *e;
-        void *data;
-        unsigned idx;
-
-        idx = find_first_entry(h);
-        if (idx == IDX_NIL)
-                return NULL;
-
-        e = bucket_at(h, idx);
-        data = entry_value(h, e);
-        remove_entry(h, idx);
-
-        return data;
-}
-
-void *internal_hashmap_steal_first_key(HashmapBase *h) {
-        struct hashmap_base_entry *e;
-        void *key;
-        unsigned idx;
-
-        idx = find_first_entry(h);
-        if (idx == IDX_NIL)
-                return NULL;
-
-        e = bucket_at(h, idx);
-        key = (void*) e->key;
-        remove_entry(h, idx);
-
-        return key;
-}
-
-unsigned internal_hashmap_size(HashmapBase *h) {
-
-        if (!h)
-                return 0;
-
-        return n_entries(h);
-}
-
-unsigned internal_hashmap_buckets(HashmapBase *h) {
-
-        if (!h)
-                return 0;
-
-        return n_buckets(h);
-}
-
-int internal_hashmap_merge(Hashmap *h, Hashmap *other) {
-        Iterator i;
-        unsigned idx;
-
-        assert(h);
-
-        HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) {
-                struct plain_hashmap_entry *pe = plain_bucket_at(other, idx);
-                int r;
-
-                r = hashmap_put(h, pe->b.key, pe->value);
-                if (r < 0 && r != -EEXIST)
-                        return r;
-        }
-
-        return 0;
-}
-
-int set_merge(Set *s, Set *other) {
-        Iterator i;
-        unsigned idx;
-
-        assert(s);
-
-        HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) {
-                struct set_entry *se = set_bucket_at(other, idx);
-                int r;
-
-                r = set_put(s, se->b.key);
-                if (r < 0)
-                        return r;
-        }
-
-        return 0;
-}
-
-int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add) {
-        int r;
-
-        assert(h);
-
-        r = resize_buckets(h, entries_add);
-        if (r < 0)
-                return r;
-
-        return 0;
-}
-
-/*
- * The same as hashmap_merge(), but every new item from other is moved to h.
- * Keys already in h are skipped and stay in other.
- * Returns: 0 on success.
- *          -ENOMEM on alloc failure, in which case no move has been done.
- */
-int internal_hashmap_move(HashmapBase *h, HashmapBase *other) {
-        struct swap_entries swap;
-        struct hashmap_base_entry *e, *n;
-        Iterator i;
-        unsigned idx;
-        int r;
-
-        assert(h);
-
-        if (!other)
-                return 0;
-
-        assert(other->type == h->type);
-
-        /*
-         * This reserves buckets for the worst case, where none of other's
-         * entries are yet present in h. This is preferable to risking
-         * an allocation failure in the middle of the moving and having to
-         * rollback or return a partial result.
-         */
-        r = resize_buckets(h, n_entries(other));
-        if (r < 0)
-                return r;
-
-        HASHMAP_FOREACH_IDX(idx, other, i) {
-                unsigned h_hash;
-
-                e = bucket_at(other, idx);
-                h_hash = bucket_hash(h, e->key);
-                if (bucket_scan(h, h_hash, e->key) != IDX_NIL)
-                        continue;
-
-                n = &bucket_at_swap(&swap, IDX_PUT)->p.b;
-                n->key = e->key;
-                if (h->type != HASHMAP_TYPE_SET)
-                        ((struct plain_hashmap_entry*) n)->value =
-                                ((struct plain_hashmap_entry*) e)->value;
-                assert_se(hashmap_put_boldly(h, h_hash, &swap, false) == 1);
-
-                remove_entry(other, idx);
-        }
-
-        return 0;
-}
-
-int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key) {
-        struct swap_entries swap;
-        unsigned h_hash, other_hash, idx;
-        struct hashmap_base_entry *e, *n;
-        int r;
-
-        assert(h);
-
-        h_hash = bucket_hash(h, key);
-        if (bucket_scan(h, h_hash, key) != IDX_NIL)
-                return -EEXIST;
-
-        if (!other)
-                return -ENOENT;
-
-        assert(other->type == h->type);
-
-        other_hash = bucket_hash(other, key);
-        idx = bucket_scan(other, other_hash, key);
-        if (idx == IDX_NIL)
-                return -ENOENT;
-
-        e = bucket_at(other, idx);
-
-        n = &bucket_at_swap(&swap, IDX_PUT)->p.b;
-        n->key = e->key;
-        if (h->type != HASHMAP_TYPE_SET)
-                ((struct plain_hashmap_entry*) n)->value =
-                        ((struct plain_hashmap_entry*) e)->value;
-        r = hashmap_put_boldly(h, h_hash, &swap, true);
-        if (r < 0)
-                return r;
-
-        remove_entry(other, idx);
-        return 0;
-}
-
-HashmapBase *internal_hashmap_copy(HashmapBase *h) {
-        HashmapBase *copy;
-        int r;
-
-        assert(h);
-
-        copy = hashmap_base_new(h->hash_ops, h->type  HASHMAP_DEBUG_SRC_ARGS);
-        if (!copy)
-                return NULL;
-
-        switch (h->type) {
-        case HASHMAP_TYPE_PLAIN:
-        case HASHMAP_TYPE_ORDERED:
-                r = hashmap_merge((Hashmap*)copy, (Hashmap*)h);
-                break;
-        case HASHMAP_TYPE_SET:
-                r = set_merge((Set*)copy, (Set*)h);
-                break;
-        default:
-                assert_not_reached("Unknown hashmap type");
-        }
-
-        if (r < 0) {
-                internal_hashmap_free(copy);
-                return NULL;
-        }
-
-        return copy;
-}
-
-char **internal_hashmap_get_strv(HashmapBase *h) {
-        char **sv;
-        Iterator i;
-        unsigned idx, n;
-
-        sv = new(char*, n_entries(h)+1);
-        if (!sv)
-                return NULL;
-
-        n = 0;
-        HASHMAP_FOREACH_IDX(idx, h, i)
-                sv[n++] = entry_value(h, bucket_at(h, idx));
-        sv[n] = NULL;
-
-        return sv;
-}
-
-void *ordered_hashmap_next(OrderedHashmap *h, const void *key) {
-        struct ordered_hashmap_entry *e;
-        unsigned hash, idx;
-
-        if (!h)
-                return NULL;
-
-        hash = bucket_hash(h, key);
-        idx = bucket_scan(h, hash, key);
-        if (idx == IDX_NIL)
-                return NULL;
-
-        e = ordered_bucket_at(h, idx);
-        if (e->iterate_next == IDX_NIL)
-                return NULL;
-        return ordered_bucket_at(h, e->iterate_next)->p.value;
-}
-
-int set_consume(Set *s, void *value) {
-        int r;
-
-        assert(s);
-        assert(value);
-
-        r = set_put(s, value);
-        if (r <= 0)
-                free(value);
-
-        return r;
-}
-
-int set_put_strdup(Set *s, const char *p) {
-        char *c;
-
-        assert(s);
-        assert(p);
-
-        if (set_contains(s, (char*) p))
-                return 0;
-
-        c = strdup(p);
-        if (!c)
-                return -ENOMEM;
-
-        return set_consume(s, c);
-}
-
-int set_put_strdupv(Set *s, char **l) {
-        int n = 0, r;
-        char **i;
-
-        assert(s);
-
-        STRV_FOREACH(i, l) {
-                r = set_put_strdup(s, *i);
-                if (r < 0)
-                        return r;
-
-                n += r;
-        }
-
-        return n;
-}
-
-int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags flags) {
-        const char *p = v;
-        int r;
-
-        assert(s);
-        assert(v);
-
-        for (;;) {
-                char *word;
-
-                r = extract_first_word(&p, &word, separators, flags);
-                if (r <= 0)
-                        return r;
-
-                r = set_consume(s, word);
-                if (r < 0)
-                        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
deleted file mode 100644
index 274afb39..00000000
--- a/src/systemd/src/basic/hashmap.h
+++ /dev/null
@@ -1,393 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <limits.h>
-#include <stdbool.h>
-#include <stddef.h>
-
-#include "hash-funcs.h"
-#include "macro.h"
-#include "util.h"
-
-/*
- * A hash table implementation. As a minor optimization a NULL hashmap object
- * will be treated as empty hashmap for all read operations. That way it is not
- * necessary to instantiate an object for each Hashmap use.
- *
- * If ENABLE_DEBUG_HASHMAP is defined (by configuring with --enable-debug=hashmap),
- * the implemention will:
- * - store extra data for debugging and statistics (see tools/gdb-sd_dump_hashmaps.py)
- * - perform extra checks for invalid use of iterators
- */
-
-#define HASH_KEY_SIZE 16
-
-/* The base type for all hashmap and set types. Many functions in the
- * implementation take (HashmapBase*) parameters and are run-time polymorphic,
- * though the API is not meant to be polymorphic (do not call functions
- * internal_*() directly). */
-typedef struct HashmapBase HashmapBase;
-
-/* Specific hashmap/set types */
-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. */
-typedef struct {
-        unsigned idx;         /* index of an entry to be iterated next */
-        const void *next_key; /* expected value of that entry's key pointer */
-#if ENABLE_DEBUG_HASHMAP
-        unsigned put_count;   /* hashmap's put_count recorded at start of iteration */
-        unsigned rem_count;   /* hashmap's rem_count in previous iteration */
-        unsigned prev_idx;    /* idx in previous iteration */
-#endif
-} Iterator;
-
-#define _IDX_ITERATOR_FIRST (UINT_MAX - 1)
-#define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL })
-
-/* Macros for type checking */
-#define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \
-        (__builtin_types_compatible_p(typeof(h), HashmapBase*) || \
-         __builtin_types_compatible_p(typeof(h), Hashmap*) || \
-         __builtin_types_compatible_p(typeof(h), OrderedHashmap*) || \
-         __builtin_types_compatible_p(typeof(h), Set*))
-
-#define PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h) \
-        (__builtin_types_compatible_p(typeof(h), Hashmap*) || \
-         __builtin_types_compatible_p(typeof(h), OrderedHashmap*)) \
-
-#define HASHMAP_BASE(h) \
-        __builtin_choose_expr(PTR_COMPATIBLE_WITH_HASHMAP_BASE(h), \
-                (HashmapBase*)(h), \
-                (void)0)
-
-#define PLAIN_HASHMAP(h) \
-        __builtin_choose_expr(PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h), \
-                (Hashmap*)(h), \
-                (void)0)
-
-#if ENABLE_DEBUG_HASHMAP
-# define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line
-# define HASHMAP_DEBUG_SRC_ARGS   , __func__, __FILE__, __LINE__
-# define HASHMAP_DEBUG_PASS_ARGS   , func, file, line
-#else
-# define HASHMAP_DEBUG_PARAMS
-# define HASHMAP_DEBUG_SRC_ARGS
-# define HASHMAP_DEBUG_PASS_ARGS
-#endif
-
-Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
-OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
-#define hashmap_new(ops) internal_hashmap_new(ops  HASHMAP_DEBUG_SRC_ARGS)
-#define ordered_hashmap_new(ops) internal_ordered_hashmap_new(ops  HASHMAP_DEBUG_SRC_ARGS)
-
-HashmapBase *internal_hashmap_free(HashmapBase *h);
-static inline Hashmap *hashmap_free(Hashmap *h) {
-        return (void*)internal_hashmap_free(HASHMAP_BASE(h));
-}
-static inline OrderedHashmap *ordered_hashmap_free(OrderedHashmap *h) {
-        return (void*)internal_hashmap_free(HASHMAP_BASE(h));
-}
-
-HashmapBase *internal_hashmap_free_free(HashmapBase *h);
-static inline Hashmap *hashmap_free_free(Hashmap *h) {
-        return (void*)internal_hashmap_free_free(HASHMAP_BASE(h));
-}
-static inline OrderedHashmap *ordered_hashmap_free_free(OrderedHashmap *h) {
-        return (void*)internal_hashmap_free_free(HASHMAP_BASE(h));
-}
-
-Hashmap *hashmap_free_free_free(Hashmap *h);
-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));
-}
-static inline OrderedHashmap *ordered_hashmap_copy(OrderedHashmap *h) {
-        return (OrderedHashmap*) internal_hashmap_copy(HASHMAP_BASE(h));
-}
-
-int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
-int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops  HASHMAP_DEBUG_PARAMS);
-#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);
-}
-
-int hashmap_update(Hashmap *h, const void *key, void *value);
-static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) {
-        return hashmap_update(PLAIN_HASHMAP(h), key, value);
-}
-
-int hashmap_replace(Hashmap *h, const void *key, void *value);
-static inline int ordered_hashmap_replace(OrderedHashmap *h, const void *key, void *value) {
-        return hashmap_replace(PLAIN_HASHMAP(h), key, value);
-}
-
-void *internal_hashmap_get(HashmapBase *h, const void *key);
-static inline void *hashmap_get(Hashmap *h, const void *key) {
-        return internal_hashmap_get(HASHMAP_BASE(h), key);
-}
-static inline void *ordered_hashmap_get(OrderedHashmap *h, const void *key) {
-        return internal_hashmap_get(HASHMAP_BASE(h), key);
-}
-
-void *hashmap_get2(Hashmap *h, const void *key, void **rkey);
-static inline void *ordered_hashmap_get2(OrderedHashmap *h, const void *key, void **rkey) {
-        return hashmap_get2(PLAIN_HASHMAP(h), key, rkey);
-}
-
-bool internal_hashmap_contains(HashmapBase *h, const void *key);
-static inline bool hashmap_contains(Hashmap *h, const void *key) {
-        return internal_hashmap_contains(HASHMAP_BASE(h), key);
-}
-static inline bool ordered_hashmap_contains(OrderedHashmap *h, const void *key) {
-        return internal_hashmap_contains(HASHMAP_BASE(h), key);
-}
-
-void *internal_hashmap_remove(HashmapBase *h, const void *key);
-static inline void *hashmap_remove(Hashmap *h, const void *key) {
-        return internal_hashmap_remove(HASHMAP_BASE(h), key);
-}
-static inline void *ordered_hashmap_remove(OrderedHashmap *h, const void *key) {
-        return internal_hashmap_remove(HASHMAP_BASE(h), key);
-}
-
-void *hashmap_remove2(Hashmap *h, const void *key, void **rkey);
-static inline void *ordered_hashmap_remove2(OrderedHashmap *h, const void *key, void **rkey) {
-        return hashmap_remove2(PLAIN_HASHMAP(h), key, rkey);
-}
-
-void *hashmap_remove_value(Hashmap *h, const void *key, void *value);
-static inline void *ordered_hashmap_remove_value(OrderedHashmap *h, const void *key, void *value) {
-        return hashmap_remove_value(PLAIN_HASHMAP(h), key, value);
-}
-
-int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value);
-static inline int ordered_hashmap_remove_and_put(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) {
-        return hashmap_remove_and_put(PLAIN_HASHMAP(h), old_key, new_key, value);
-}
-
-int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value);
-static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) {
-        return hashmap_remove_and_replace(PLAIN_HASHMAP(h), old_key, new_key, value);
-}
-
-/* Since merging data from a OrderedHashmap into a Hashmap or vice-versa
- * should just work, allow this by having looser type-checking here. */
-int internal_hashmap_merge(Hashmap *h, Hashmap *other);
-#define hashmap_merge(h, other) internal_hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other))
-#define ordered_hashmap_merge(h, other) hashmap_merge(h, other)
-
-int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add);
-static inline int hashmap_reserve(Hashmap *h, unsigned entries_add) {
-        return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
-}
-static inline int ordered_hashmap_reserve(OrderedHashmap *h, unsigned entries_add) {
-        return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
-}
-
-int internal_hashmap_move(HashmapBase *h, HashmapBase *other);
-/* Unlike hashmap_merge, hashmap_move does not allow mixing the types. */
-static inline int hashmap_move(Hashmap *h, Hashmap *other) {
-        return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other));
-}
-static inline int ordered_hashmap_move(OrderedHashmap *h, OrderedHashmap *other) {
-        return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other));
-}
-
-int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key);
-static inline int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) {
-        return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key);
-}
-static inline int ordered_hashmap_move_one(OrderedHashmap *h, OrderedHashmap *other, const void *key) {
-        return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key);
-}
-
-unsigned internal_hashmap_size(HashmapBase *h) _pure_;
-static inline unsigned hashmap_size(Hashmap *h) {
-        return internal_hashmap_size(HASHMAP_BASE(h));
-}
-static inline unsigned ordered_hashmap_size(OrderedHashmap *h) {
-        return internal_hashmap_size(HASHMAP_BASE(h));
-}
-
-static inline bool hashmap_isempty(Hashmap *h) {
-        return hashmap_size(h) == 0;
-}
-static inline bool ordered_hashmap_isempty(OrderedHashmap *h) {
-        return ordered_hashmap_size(h) == 0;
-}
-
-unsigned internal_hashmap_buckets(HashmapBase *h) _pure_;
-static inline unsigned hashmap_buckets(Hashmap *h) {
-        return internal_hashmap_buckets(HASHMAP_BASE(h));
-}
-static inline unsigned ordered_hashmap_buckets(OrderedHashmap *h) {
-        return internal_hashmap_buckets(HASHMAP_BASE(h));
-}
-
-bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key);
-static inline bool hashmap_iterate(Hashmap *h, Iterator *i, void **value, const void **key) {
-        return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key);
-}
-static inline bool ordered_hashmap_iterate(OrderedHashmap *h, Iterator *i, void **value, const void **key) {
-        return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key);
-}
-
-void internal_hashmap_clear(HashmapBase *h);
-static inline void hashmap_clear(Hashmap *h) {
-        internal_hashmap_clear(HASHMAP_BASE(h));
-}
-static inline void ordered_hashmap_clear(OrderedHashmap *h) {
-        internal_hashmap_clear(HASHMAP_BASE(h));
-}
-
-void internal_hashmap_clear_free(HashmapBase *h);
-static inline void hashmap_clear_free(Hashmap *h) {
-        internal_hashmap_clear_free(HASHMAP_BASE(h));
-}
-static inline void ordered_hashmap_clear_free(OrderedHashmap *h) {
-        internal_hashmap_clear_free(HASHMAP_BASE(h));
-}
-
-void hashmap_clear_free_free(Hashmap *h);
-static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) {
-        hashmap_clear_free_free(PLAIN_HASHMAP(h));
-}
-
-/*
- * Note about all *_first*() functions
- *
- * For plain Hashmaps and Sets the order of entries is undefined.
- * The functions find whatever entry is first in the implementation
- * internal order.
- *
- * Only for OrderedHashmaps the order is well defined and finding
- * the first entry is O(1).
- */
-
-void *internal_hashmap_steal_first(HashmapBase *h);
-static inline void *hashmap_steal_first(Hashmap *h) {
-        return internal_hashmap_steal_first(HASHMAP_BASE(h));
-}
-static inline void *ordered_hashmap_steal_first(OrderedHashmap *h) {
-        return internal_hashmap_steal_first(HASHMAP_BASE(h));
-}
-
-void *internal_hashmap_steal_first_key(HashmapBase *h);
-static inline void *hashmap_steal_first_key(Hashmap *h) {
-        return internal_hashmap_steal_first_key(HASHMAP_BASE(h));
-}
-static inline void *ordered_hashmap_steal_first_key(OrderedHashmap *h) {
-        return internal_hashmap_steal_first_key(HASHMAP_BASE(h));
-}
-
-void *internal_hashmap_first_key(HashmapBase *h) _pure_;
-static inline void *hashmap_first_key(Hashmap *h) {
-        return internal_hashmap_first_key(HASHMAP_BASE(h));
-}
-static inline void *ordered_hashmap_first_key(OrderedHashmap *h) {
-        return internal_hashmap_first_key(HASHMAP_BASE(h));
-}
-
-void *internal_hashmap_first(HashmapBase *h) _pure_;
-static inline void *hashmap_first(Hashmap *h) {
-        return internal_hashmap_first(HASHMAP_BASE(h));
-}
-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);
-
-char **internal_hashmap_get_strv(HashmapBase *h);
-static inline char **hashmap_get_strv(Hashmap *h) {
-        return internal_hashmap_get_strv(HASHMAP_BASE(h));
-}
-static inline char **ordered_hashmap_get_strv(OrderedHashmap *h) {
-        return internal_hashmap_get_strv(HASHMAP_BASE(h));
-}
-
-/*
- * Hashmaps are iterated in unpredictable order.
- * OrderedHashmaps are an exception to this. They are iterated in the order
- * the entries were inserted.
- * It is safe to remove the current entry.
- */
-#define HASHMAP_FOREACH(e, h, i) \
-        for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), NULL); )
-
-#define ORDERED_HASHMAP_FOREACH(e, h, i) \
-        for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), NULL); )
-
-#define HASHMAP_FOREACH_KEY(e, k, h, i) \
-        for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); )
-
-#define ORDERED_HASHMAP_FOREACH_KEY(e, k, h, i) \
-        for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); )
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free);
-DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free);
-DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free);
-DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free);
-DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free);
-DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free);
-
-#define _cleanup_hashmap_free_ _cleanup_(hashmap_freep)
-#define _cleanup_hashmap_free_free_ _cleanup_(hashmap_free_freep)
-#define _cleanup_hashmap_free_free_free_ _cleanup_(hashmap_free_free_freep)
-#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
deleted file mode 100644
index 09f2a9e7..00000000
--- a/src/systemd/src/basic/hexdecoct.c
+++ /dev/null
@@ -1,821 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <ctype.h>
-#include <errno.h>
-#include <stdint.h>
-#include <stdlib.h>
-
-#include "alloc-util.h"
-#include "hexdecoct.h"
-#include "macro.h"
-#include "string-util.h"
-#include "util.h"
-
-char octchar(int x) {
-        return '0' + (x & 7);
-}
-
-int unoctchar(char c) {
-
-        if (c >= '0' && c <= '7')
-                return c - '0';
-
-        return -EINVAL;
-}
-
-char decchar(int x) {
-        return '0' + (x % 10);
-}
-
-int undecchar(char c) {
-
-        if (c >= '0' && c <= '9')
-                return c - '0';
-
-        return -EINVAL;
-}
-
-char hexchar(int x) {
-        static const char table[16] = "0123456789abcdef";
-
-        return table[x & 15];
-}
-
-int unhexchar(char c) {
-
-        if (c >= '0' && c <= '9')
-                return c - '0';
-
-        if (c >= 'a' && c <= 'f')
-                return c - 'a' + 10;
-
-        if (c >= 'A' && c <= 'F')
-                return c - 'A' + 10;
-
-        return -EINVAL;
-}
-
-char *hexmem(const void *p, size_t l) {
-        const uint8_t *x;
-        char *r, *z;
-
-        z = r = new(char, l * 2 + 1);
-        if (!r)
-                return NULL;
-
-        for (x = p; x < (const uint8_t*) p + l; x++) {
-                *(z++) = hexchar(*x >> 4);
-                *(z++) = hexchar(*x & 15);
-        }
-
-        *z = 0;
-        return r;
-}
-
-static int unhex_next(const char **p, size_t *l) {
-        int r;
-
-        assert(p);
-        assert(l);
-
-        /* Find the next non-whitespace character, and decode it. We
-         * greedily skip all preceeding and all following whitespace. */
-
-        for (;;) {
-                if (*l == 0)
-                        return -EPIPE;
-
-                if (!strchr(WHITESPACE, **p))
-                        break;
-
-                /* Skip leading whitespace */
-                (*p)++, (*l)--;
-        }
-
-        r = unhexchar(**p);
-        if (r < 0)
-                return r;
-
-        for (;;) {
-                (*p)++, (*l)--;
-
-                if (*l == 0 || !strchr(WHITESPACE, **p))
-                        break;
-
-                /* Skip following whitespace */
-        }
-
-        return r;
-}
-
-int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) {
-        _cleanup_free_ uint8_t *buf = NULL;
-        const char *x;
-        uint8_t *z;
-
-        assert(ret);
-        assert(ret_len);
-        assert(p || l == 0);
-
-        if (l == (size_t) -1)
-                l = strlen(p);
-
-        /* Note that the calculation of memory size is an upper boundary, as we ignore whitespace while decoding */
-        buf = malloc((l + 1) / 2 + 1);
-        if (!buf)
-                return -ENOMEM;
-
-        for (x = p, z = buf;;) {
-                int a, b;
-
-                a = unhex_next(&x, &l);
-                if (a == -EPIPE) /* End of string */
-                        break;
-                if (a < 0)
-                        return a;
-
-                b = unhex_next(&x, &l);
-                if (b < 0)
-                        return b;
-
-                *(z++) = (uint8_t) a << 4 | (uint8_t) b;
-        }
-
-        *z = 0;
-
-        *ret_len = (size_t) (z - buf);
-        *ret = TAKE_PTR(buf);
-
-        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
- * order of the underlying data when compared as bytestrings, this is
- * useful when representing NSEC3 hashes, as one can then verify the
- * order of hashes directly from their representation. */
-char base32hexchar(int x) {
-        static const char table[32] = "0123456789"
-                                      "ABCDEFGHIJKLMNOPQRSTUV";
-
-        return table[x & 31];
-}
-
-int unbase32hexchar(char c) {
-        unsigned offset;
-
-        if (c >= '0' && c <= '9')
-                return c - '0';
-
-        offset = '9' - '0' + 1;
-
-        if (c >= 'A' && c <= 'V')
-                return c - 'A' + offset;
-
-        return -EINVAL;
-}
-
-char *base32hexmem(const void *p, size_t l, bool padding) {
-        char *r, *z;
-        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;
-        else {
-                /* same, but round down as there is no padding */
-                len = 8 * l / 5;
-
-                switch (l % 5) {
-                case 4:
-                        len += 7;
-                        break;
-                case 3:
-                        len += 5;
-                        break;
-                case 2:
-                        len += 4;
-                        break;
-                case 1:
-                        len += 2;
-                        break;
-                }
-        }
-
-        z = r = malloc(len + 1);
-        if (!r)
-                return NULL;
-
-        for (x = p; x < (const uint8_t*) p + (l / 5) * 5; x += 5) {
-                /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ
-                 * x[3] == QQQQQQQQ; x[4] == WWWWWWWW */
-                *(z++) = base32hexchar(x[0] >> 3);                    /* 000XXXXX */
-                *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6);  /* 000XXXYY */
-                *(z++) = base32hexchar((x[1] & 63) >> 1);             /* 000YYYYY */
-                *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4);  /* 000YZZZZ */
-                *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */
-                *(z++) = base32hexchar((x[3] & 127) >> 2);            /* 000QQQQQ */
-                *(z++) = base32hexchar((x[3] & 3) << 3 | x[4] >> 5);  /* 000QQWWW */
-                *(z++) = base32hexchar((x[4] & 31));                  /* 000WWWWW */
-        }
-
-        switch (l % 5) {
-        case 4:
-                *(z++) = base32hexchar(x[0] >> 3);                    /* 000XXXXX */
-                *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6);  /* 000XXXYY */
-                *(z++) = base32hexchar((x[1] & 63) >> 1);             /* 000YYYYY */
-                *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4);   /* 000YZZZZ */
-                *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */
-                *(z++) = base32hexchar((x[3] & 127) >> 2);            /* 000QQQQQ */
-                *(z++) = base32hexchar((x[3] & 3) << 3);              /* 000QQ000 */
-                if (padding)
-                        *(z++) = '=';
-
-                break;
-
-        case 3:
-                *(z++) = base32hexchar(x[0] >> 3);                   /* 000XXXXX */
-                *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */
-                *(z++) = base32hexchar((x[1] & 63) >> 1);            /* 000YYYYY */
-                *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */
-                *(z++) = base32hexchar((x[2] & 15) << 1);            /* 000ZZZZ0 */
-                if (padding) {
-                        *(z++) = '=';
-                        *(z++) = '=';
-                        *(z++) = '=';
-                }
-
-                break;
-
-        case 2:
-                *(z++) = base32hexchar(x[0] >> 3);                   /* 000XXXXX */
-                *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */
-                *(z++) = base32hexchar((x[1] & 63) >> 1);            /* 000YYYYY */
-                *(z++) = base32hexchar((x[1] & 1) << 4);             /* 000Y0000 */
-                if (padding) {
-                        *(z++) = '=';
-                        *(z++) = '=';
-                        *(z++) = '=';
-                        *(z++) = '=';
-                }
-
-                break;
-
-        case 1:
-                *(z++) = base32hexchar(x[0] >> 3);       /* 000XXXXX */
-                *(z++) = base32hexchar((x[0] & 7) << 2); /* 000XXX00 */
-                if (padding) {
-                        *(z++) = '=';
-                        *(z++) = '=';
-                        *(z++) = '=';
-                        *(z++) = '=';
-                        *(z++) = '=';
-                        *(z++) = '=';
-                }
-
-                break;
-        }
-
-        *z = 0;
-        return r;
-}
-
-int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_len) {
-        _cleanup_free_ uint8_t *r = NULL;
-        int a, b, c, d, e, f, g, h;
-        uint8_t *z;
-        const char *x;
-        size_t len;
-        unsigned pad = 0;
-
-        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)
-                return -EINVAL;
-
-        if (padding) {
-                /* strip the padding */
-                while (l > 0 && p[l - 1] == '=' && pad < 7) {
-                        pad++;
-                        l--;
-                }
-        }
-
-        /* a group of eight input bytes needs five output bytes, in case of
-         * padding we need to add some extra bytes */
-        len = (l / 8) * 5;
-
-        switch (l % 8) {
-        case 7:
-                len += 4;
-                break;
-        case 5:
-                len += 3;
-                break;
-        case 4:
-                len += 2;
-                break;
-        case 2:
-                len += 1;
-                break;
-        case 0:
-                break;
-        default:
-                return -EINVAL;
-        }
-
-        z = r = malloc(len + 1);
-        if (!r)
-                return -ENOMEM;
-
-        for (x = p; x < p + (l / 8) * 8; x += 8) {
-                /* a == 000XXXXX; b == 000YYYYY; c == 000ZZZZZ; d == 000WWWWW
-                 * e == 000SSSSS; f == 000QQQQQ; g == 000VVVVV; h == 000RRRRR */
-                a = unbase32hexchar(x[0]);
-                if (a < 0)
-                        return -EINVAL;
-
-                b = unbase32hexchar(x[1]);
-                if (b < 0)
-                        return -EINVAL;
-
-                c = unbase32hexchar(x[2]);
-                if (c < 0)
-                        return -EINVAL;
-
-                d = unbase32hexchar(x[3]);
-                if (d < 0)
-                        return -EINVAL;
-
-                e = unbase32hexchar(x[4]);
-                if (e < 0)
-                        return -EINVAL;
-
-                f = unbase32hexchar(x[5]);
-                if (f < 0)
-                        return -EINVAL;
-
-                g = unbase32hexchar(x[6]);
-                if (g < 0)
-                        return -EINVAL;
-
-                h = unbase32hexchar(x[7]);
-                if (h < 0)
-                        return -EINVAL;
-
-                *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2;                    /* XXXXXYYY */
-                *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
-                *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1;                    /* WWWWSSSS */
-                *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */
-                *(z++) = (uint8_t) g << 5 | (uint8_t) h;                         /* VVVRRRRR */
-        }
-
-        switch (l % 8) {
-        case 7:
-                a = unbase32hexchar(x[0]);
-                if (a < 0)
-                        return -EINVAL;
-
-                b = unbase32hexchar(x[1]);
-                if (b < 0)
-                        return -EINVAL;
-
-                c = unbase32hexchar(x[2]);
-                if (c < 0)
-                        return -EINVAL;
-
-                d = unbase32hexchar(x[3]);
-                if (d < 0)
-                        return -EINVAL;
-
-                e = unbase32hexchar(x[4]);
-                if (e < 0)
-                        return -EINVAL;
-
-                f = unbase32hexchar(x[5]);
-                if (f < 0)
-                        return -EINVAL;
-
-                g = unbase32hexchar(x[6]);
-                if (g < 0)
-                        return -EINVAL;
-
-                /* g == 000VV000 */
-                if (g & 7)
-                        return -EINVAL;
-
-                *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2;                    /* XXXXXYYY */
-                *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
-                *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1;                    /* WWWWSSSS */
-                *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */
-
-                break;
-        case 5:
-                a = unbase32hexchar(x[0]);
-                if (a < 0)
-                        return -EINVAL;
-
-                b = unbase32hexchar(x[1]);
-                if (b < 0)
-                        return -EINVAL;
-
-                c = unbase32hexchar(x[2]);
-                if (c < 0)
-                        return -EINVAL;
-
-                d = unbase32hexchar(x[3]);
-                if (d < 0)
-                        return -EINVAL;
-
-                e = unbase32hexchar(x[4]);
-                if (e < 0)
-                        return -EINVAL;
-
-                /* e == 000SSSS0 */
-                if (e & 1)
-                        return -EINVAL;
-
-                *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2;                    /* XXXXXYYY */
-                *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
-                *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1;                    /* WWWWSSSS */
-
-                break;
-        case 4:
-                a = unbase32hexchar(x[0]);
-                if (a < 0)
-                        return -EINVAL;
-
-                b = unbase32hexchar(x[1]);
-                if (b < 0)
-                        return -EINVAL;
-
-                c = unbase32hexchar(x[2]);
-                if (c < 0)
-                        return -EINVAL;
-
-                d = unbase32hexchar(x[3]);
-                if (d < 0)
-                        return -EINVAL;
-
-                /* d == 000W0000 */
-                if (d & 15)
-                        return -EINVAL;
-
-                *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2;                    /* XXXXXYYY */
-                *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */
-
-                break;
-        case 2:
-                a = unbase32hexchar(x[0]);
-                if (a < 0)
-                        return -EINVAL;
-
-                b = unbase32hexchar(x[1]);
-                if (b < 0)
-                        return -EINVAL;
-
-                /* b == 000YYY00 */
-                if (b & 3)
-                        return -EINVAL;
-
-                *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */
-
-                break;
-        case 0:
-                break;
-        default:
-                return -EINVAL;
-        }
-
-        *z = 0;
-
-        *mem = TAKE_PTR(r);
-        *_len = len;
-
-        return 0;
-}
-
-/* https://tools.ietf.org/html/rfc4648#section-4 */
-char base64char(int x) {
-        static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                                      "abcdefghijklmnopqrstuvwxyz"
-                                      "0123456789+/";
-        return table[x & 63];
-}
-
-int unbase64char(char c) {
-        unsigned offset;
-
-        if (c >= 'A' && c <= 'Z')
-                return c - 'A';
-
-        offset = 'Z' - 'A' + 1;
-
-        if (c >= 'a' && c <= 'z')
-                return c - 'a' + offset;
-
-        offset += 'z' - 'a' + 1;
-
-        if (c >= '0' && c <= '9')
-                return c - '0' + offset;
-
-        offset += '9' - '0' + 1;
-
-        if (c == '+')
-                return offset;
-
-        offset++;
-
-        if (c == '/')
-                return offset;
-
-        return -EINVAL;
-}
-
-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)
-                return -ENOMEM;
-
-        for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) {
-                /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */
-                *(z++) = base64char(x[0] >> 2);                    /* 00XXXXXX */
-                *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4);  /* 00XXYYYY */
-                *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */
-                *(z++) = base64char(x[2] & 63);                    /* 00ZZZZZZ */
-        }
-
-        switch (l % 3) {
-        case 2:
-                *(z++) = base64char(x[0] >> 2);                   /* 00XXXXXX */
-                *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
-                *(z++) = base64char((x[1] & 15) << 2);            /* 00YYYY00 */
-                *(z++) = '=';
-
-                break;
-        case 1:
-                *(z++) = base64char(x[0] >> 2);        /* 00XXXXXX */
-                *(z++) = base64char((x[0] & 3) << 4);  /* 00XX0000 */
-                *(z++) = '=';
-                *(z++) = '=';
-
-                break;
-        }
-
-        *z = 0;
-        *out = r;
-        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) {
-
-        _cleanup_free_ char *x = NULL;
-        char *t, *s;
-        ssize_t slen, len, avail;
-        int line, lines;
-
-        len = base64mem(p, l, &x);
-        if (len <= 0)
-                return len;
-
-        lines = DIV_ROUND_UP(len, width);
-
-        slen = strlen_ptr(sep);
-        t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines);
-        if (!t)
-                return -ENOMEM;
-
-        memcpy_safe(t + plen, sep, slen);
-
-        for (line = 0, s = t + plen + slen, avail = len; line < lines; line++) {
-                int act = MIN(width, avail);
-
-                if (line > 0 || sep) {
-                        memset(s, ' ', indent);
-                        s += indent;
-                }
-
-                memcpy(s, x + width * line, act);
-                s += act;
-                *(s++) = line < lines - 1 ? '\n' : '\0';
-                avail -= act;
-        }
-        assert(avail == 0);
-
-        *prefix = t;
-        return 0;
-}
-
-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);
-}
-
-static int unbase64_next(const char **p, size_t *l) {
-        int ret;
-
-        assert(p);
-        assert(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. */
-
-        for (;;) {
-                if (*l == 0)
-                        return -EPIPE;
-
-                if (!strchr(WHITESPACE, **p))
-                        break;
-
-                /* Skip leading whitespace */
-                (*p)++, (*l)--;
-        }
-
-        if (**p == '=')
-                ret = INT_MAX; /* return padding as INT_MAX */
-        else {
-                ret = unbase64char(**p);
-                if (ret < 0)
-                        return ret;
-        }
-
-        for (;;) {
-                (*p)++, (*l)--;
-
-                if (*l == 0)
-                        break;
-                if (!strchr(WHITESPACE, **p))
-                        break;
-
-                /* Skip following whitespace */
-        }
-
-        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 = 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 = unbase64_next(&x, &l);
-                if (c < 0)
-                        return c;
-
-                d = unbase64_next(&x, &l);
-                if (d < 0)
-                        return d;
-
-                if (c == INT_MAX) { /* Padding at the third character */
-
-                        if (d != INT_MAX) /* If the third character is padding, the fourth must be too */
-                                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 */
-                        break;
-                }
-
-                if (d == INT_MAX) {
-                        /* c == 00ZZZZ00 */
-                        if (c & 3)
-                                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;
-
-        *ret_size = (size_t) (z - buf);
-        *ret = TAKE_PTR(buf);
-
-        return 0;
-}
-
-void hexdump(FILE *f, const void *p, size_t s) {
-        const uint8_t *b = p;
-        unsigned n = 0;
-
-        assert(b || s == 0);
-
-        if (!f)
-                f = stdout;
-
-        while (s > 0) {
-                size_t i;
-
-                fprintf(f, "%04x  ", n);
-
-                for (i = 0; i < 16; i++) {
-
-                        if (i >= s)
-                                fputs("   ", f);
-                        else
-                                fprintf(f, "%02x ", b[i]);
-
-                        if (i == 7)
-                                fputc(' ', f);
-                }
-
-                fputc(' ', f);
-
-                for (i = 0; i < 16; i++) {
-
-                        if (i >= s)
-                                fputc(' ', f);
-                        else
-                                fputc(isprint(b[i]) ? (char) b[i] : '.', f);
-                }
-
-                fputc('\n', f);
-
-                if (s < 16)
-                        break;
-
-                n += 16;
-                b += 16;
-                s -= 16;
-        }
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/hexdecoct.h b/src/systemd/src/basic/hexdecoct.h
deleted file mode 100644
index 9477d16e..00000000
--- a/src/systemd/src/basic/hexdecoct.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <sys/types.h>
-
-#include "macro.h"
-
-char octchar(int x) _const_;
-int unoctchar(char c) _const_;
-
-char decchar(int x) _const_;
-int undecchar(char c) _const_;
-
-char hexchar(int x) _const_;
-int unhexchar(char c) _const_;
-
-char *hexmem(const void *p, size_t l);
-int unhexmem(const char *p, size_t l, void **mem, size_t *len);
-
-char base32hexchar(int x) _const_;
-int unbase32hexchar(char c) _const_;
-
-char base64char(int x) _const_;
-int unbase64char(char c) _const_;
-
-char *base32hexmem(const void *p, size_t l, bool padding);
-int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len);
-
-ssize_t base64mem(const void *p, size_t l, char **out);
-int base64_append(char **prefix, int plen,
-                  const void *p, size_t l,
-                  int margin, int width);
-int unbase64mem(const char *p, size_t l, void **mem, size_t *len);
-
-void hexdump(FILE *f, const void *p, size_t s);
diff --git a/src/systemd/src/basic/hostname-util.c b/src/systemd/src/basic/hostname-util.c
deleted file mode 100644
index 85708394..00000000
--- a/src/systemd/src/basic/hostname-util.c
+++ /dev/null
@@ -1,295 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <limits.h>
-#include <stdio.h>
-#include <string.h>
-#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;
-
-        assert_se(uname(&u) >= 0);
-
-        if (isempty(u.nodename))
-                return false;
-
-        /* This is the built-in kernel default host name */
-        if (streq(u.nodename, "(none)"))
-                return false;
-
-        return true;
-}
-
-char* gethostname_malloc(void) {
-        struct utsname u;
-
-        /* This call tries to return something useful, either the actual hostname
-         * or it makes something up. The only reason it might fail is OOM.
-         * It might even return "localhost" if that's set. */
-
-        assert_se(uname(&u) >= 0);
-
-        if (isempty(u.nodename) || streq(u.nodename, "(none)"))
-                return strdup(FALLBACK_HOSTNAME);
-
-        return strdup(u.nodename);
-}
-#endif /* NM_IGNORED */
-
-int gethostname_strict(char **ret) {
-        struct utsname u;
-        char *k;
-
-        /* This call will rather fail than make up a name. It will not return "localhost" either. */
-
-        assert_se(uname(&u) >= 0);
-
-        if (isempty(u.nodename))
-                return -ENXIO;
-
-        if (streq(u.nodename, "(none)"))
-                return -ENXIO;
-
-        if (is_localhost(u.nodename))
-                return -ENXIO;
-
-        k = strdup(u.nodename);
-        if (!k)
-                return -ENOMEM;
-
-        *ret = k;
-        return 0;
-}
-
-static bool hostname_valid_char(char c) {
-        return
-                (c >= 'a' && c <= 'z') ||
-                (c >= 'A' && c <= 'Z') ||
-                (c >= '0' && c <= '9') ||
-                IN_SET(c, '-', '_', '.');
-}
-
-/**
- * Check if s looks like a valid host name or FQDN. This does not do
- * full DNS validation, but only checks if the name is composed of
- * allowed characters and the length is not above the maximum allowed
- * by Linux (c.f. dns_name_is_valid()). Trailing dot is allowed if
- * allow_trailing_dot is true and at least two components are present
- * in the name. Note that due to the restricted charset and length
- * this call is substantially more conservative than
- * dns_name_is_valid().
- */
-bool hostname_is_valid(const char *s, bool allow_trailing_dot) {
-        unsigned n_dots = 0;
-        const char *p;
-        bool dot;
-
-        if (isempty(s))
-                return false;
-
-        /* Doesn't accept empty hostnames, hostnames with
-         * leading dots, and hostnames with multiple dots in a
-         * sequence. Also ensures that the length stays below
-         * HOST_NAME_MAX. */
-
-        for (p = s, dot = true; *p; p++) {
-                if (*p == '.') {
-                        if (dot)
-                                return false;
-
-                        dot = true;
-                        n_dots++;
-                } else {
-                        if (!hostname_valid_char(*p))
-                                return false;
-
-                        dot = false;
-                }
-        }
-
-        if (dot && (n_dots < 2 || !allow_trailing_dot))
-                return false;
-
-        if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on
-                                  * Linux, but DNS allows domain names
-                                  * up to 255 characters */
-                return false;
-
-        return true;
-}
-
-char* hostname_cleanup(char *s) {
-        char *p, *d;
-        bool dot;
-
-        assert(s);
-
-        strshorten(s, HOST_NAME_MAX);
-
-        for (p = s, d = s, dot = true; *p; p++) {
-                if (*p == '.') {
-                        if (dot)
-                                continue;
-
-                        *(d++) = '.';
-                        dot = true;
-                } else if (hostname_valid_char(*p)) {
-                        *(d++) = *p;
-                        dot = false;
-                }
-        }
-
-        if (dot && d > s)
-                d[-1] = 0;
-        else
-                *d = 0;
-
-        return s;
-}
-
-bool is_localhost(const char *hostname) {
-        assert(hostname);
-
-        /* This tries to identify local host and domain names
-         * described in RFC6761 plus the redhatism of localdomain */
-
-        return strcaseeq(hostname, "localhost") ||
-               strcaseeq(hostname, "localhost.") ||
-               strcaseeq(hostname, "localhost.localdomain") ||
-               strcaseeq(hostname, "localhost.localdomain.") ||
-               endswith_no_case(hostname, ".localhost") ||
-               endswith_no_case(hostname, ".localhost.") ||
-               endswith_no_case(hostname, ".localhost.localdomain") ||
-               endswith_no_case(hostname, ".localhost.localdomain.");
-}
-
-#if 0 /* NM_IGNORED */
-bool is_gateway_hostname(const char *hostname) {
-        assert(hostname);
-
-        /* This tries to identify the valid syntaxes for the our
-         * synthetic "gateway" host. */
-
-        return
-                strcaseeq(hostname, "_gateway") || strcaseeq(hostname, "_gateway.")
-#if ENABLE_COMPAT_GATEWAY_HOSTNAME
-                || strcaseeq(hostname, "gateway") || strcaseeq(hostname, "gateway.")
-#endif
-                ;
-}
-
-int sethostname_idempotent(const char *s) {
-        char buf[HOST_NAME_MAX + 1] = {};
-
-        assert(s);
-
-        if (gethostname(buf, sizeof(buf)) < 0)
-                return -errno;
-
-        if (streq(buf, s))
-                return 0;
-
-        if (sethostname(s, strlen(s)) < 0)
-                return -errno;
-
-        return 1;
-}
-
-int shorten_overlong(const char *s, char **ret) {
-        char *h, *p;
-
-        /* Shorten an overlong name to HOST_NAME_MAX or to the first dot,
-         * whatever comes earlier. */
-
-        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;
-}
-
-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;
-
-                        *ret = copy;
-                        return 0;
-                }
-        }
-}
-
-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);
-
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/hostname-util.h b/src/systemd/src/basic/hostname-util.h
deleted file mode 100644
index 74948172..00000000
--- a/src/systemd/src/basic/hostname-util.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-#include <stdio.h>
-
-#include "macro.h"
-
-bool hostname_is_set(void);
-
-char* gethostname_malloc(void);
-int gethostname_strict(char **ret);
-
-bool hostname_is_valid(const char *s, bool allow_trailing_dot) _pure_;
-char* hostname_cleanup(char *s);
-
-#define machine_name_is_valid(s) hostname_is_valid(s, false)
-
-bool is_localhost(const char *hostname);
-bool is_gateway_hostname(const char *hostname);
-
-int sethostname_idempotent(const char *s);
-
-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
deleted file mode 100644
index 19d0db25..00000000
--- a/src/systemd/src/basic/in-addr-util.c
+++ /dev/null
@@ -1,602 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <arpa/inet.h>
-#include <endian.h>
-#include <errno.h>
-#include <net/if.h>
-#include <stdint.h>
-#include <stdlib.h>
-
-#include "alloc-util.h"
-#include "in-addr-util.h"
-#include "macro.h"
-#include "parse-util.h"
-#include "util.h"
-
-bool in4_addr_is_null(const struct in_addr *a) {
-        assert(a);
-
-        return a->s_addr == 0;
-}
-
-int in_addr_is_null(int family, const union in_addr_union *u) {
-        assert(u);
-
-        if (family == AF_INET)
-                return in4_addr_is_null(&u->in);
-
-        if (family == AF_INET6)
-                return IN6_IS_ADDR_UNSPECIFIED(&u->in6);
-
-        return -EAFNOSUPPORT;
-}
-
-bool in4_addr_is_link_local(const struct in_addr *a) {
-        assert(a);
-
-        return (be32toh(a->s_addr) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16);
-}
-
-int in_addr_is_link_local(int family, const union in_addr_union *u) {
-        assert(u);
-
-        if (family == AF_INET)
-                return in4_addr_is_link_local(&u->in);
-
-        if (family == AF_INET6)
-                return IN6_IS_ADDR_LINKLOCAL(&u->in6);
-
-        return -EAFNOSUPPORT;
-}
-
-int in_addr_is_multicast(int family, const union in_addr_union *u) {
-        assert(u);
-
-        if (family == AF_INET)
-                return IN_MULTICAST(be32toh(u->in.s_addr));
-
-        if (family == AF_INET6)
-                return IN6_IS_ADDR_MULTICAST(&u->in6);
-
-        return -EAFNOSUPPORT;
-}
-
-bool in4_addr_is_localhost(const struct in_addr *a) {
-        assert(a);
-
-        /* All of 127.x.x.x is localhost. */
-        return (be32toh(a->s_addr) & UINT32_C(0xFF000000)) == UINT32_C(127) << 24;
-}
-
-int in_addr_is_localhost(int family, const union in_addr_union *u) {
-        assert(u);
-
-        if (family == AF_INET)
-                return in4_addr_is_localhost(&u->in);
-
-        if (family == AF_INET6)
-                return IN6_IS_ADDR_LOOPBACK(&u->in6);
-
-        return -EAFNOSUPPORT;
-}
-
-int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) {
-        assert(a);
-        assert(b);
-
-        if (family == AF_INET)
-                return a->in.s_addr == b->in.s_addr;
-
-        if (family == AF_INET6)
-                return
-                        a->in6.s6_addr32[0] == b->in6.s6_addr32[0] &&
-                        a->in6.s6_addr32[1] == b->in6.s6_addr32[1] &&
-                        a->in6.s6_addr32[2] == b->in6.s6_addr32[2] &&
-                        a->in6.s6_addr32[3] == b->in6.s6_addr32[3];
-
-        return -EAFNOSUPPORT;
-}
-
-int in_addr_prefix_intersect(
-                int family,
-                const union in_addr_union *a,
-                unsigned aprefixlen,
-                const union in_addr_union *b,
-                unsigned bprefixlen) {
-
-        unsigned m;
-
-        assert(a);
-        assert(b);
-
-        /* Checks whether there are any addresses that are in both
-         * networks */
-
-        m = MIN(aprefixlen, bprefixlen);
-
-        if (family == AF_INET) {
-                uint32_t x, nm;
-
-                x = be32toh(a->in.s_addr ^ b->in.s_addr);
-                nm = (m == 0) ? 0 : 0xFFFFFFFFUL << (32 - m);
-
-                return (x & nm) == 0;
-        }
-
-        if (family == AF_INET6) {
-                unsigned i;
-
-                if (m > 128)
-                        m = 128;
-
-                for (i = 0; i < 16; i++) {
-                        uint8_t x, nm;
-
-                        x = a->in6.s6_addr[i] ^ b->in6.s6_addr[i];
-
-                        if (m < 8)
-                                nm = 0xFF << (8 - m);
-                        else
-                                nm = 0xFF;
-
-                        if ((x & nm) != 0)
-                                return 0;
-
-                        if (m > 8)
-                                m -= 8;
-                        else
-                                m = 0;
-                }
-
-                return 1;
-        }
-
-        return -EAFNOSUPPORT;
-}
-
-int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen) {
-        assert(u);
-
-        /* Increases the network part of an address by one. Returns
-         * positive it that succeeds, or 0 if this overflows. */
-
-        if (prefixlen <= 0)
-                return 0;
-
-        if (family == AF_INET) {
-                uint32_t c, n;
-
-                if (prefixlen > 32)
-                        prefixlen = 32;
-
-                c = be32toh(u->in.s_addr);
-                n = c + (1UL << (32 - prefixlen));
-                if (n < c)
-                        return 0;
-                n &= 0xFFFFFFFFUL << (32 - prefixlen);
-
-                u->in.s_addr = htobe32(n);
-                return 1;
-        }
-
-        if (family == AF_INET6) {
-                struct in6_addr add = {}, result;
-                uint8_t overflow = 0;
-                unsigned i;
-
-                if (prefixlen > 128)
-                        prefixlen = 128;
-
-                /* First calculate what we have to add */
-                add.s6_addr[(prefixlen-1) / 8] = 1 << (7 - (prefixlen-1) % 8);
-
-                for (i = 16; i > 0; i--) {
-                        unsigned j = i - 1;
-
-                        result.s6_addr[j] = u->in6.s6_addr[j] + add.s6_addr[j] + overflow;
-                        overflow = (result.s6_addr[j] < u->in6.s6_addr[j]);
-                }
-
-                if (overflow)
-                        return 0;
-
-                u->in6 = result;
-                return 1;
-        }
-
-        return -EAFNOSUPPORT;
-}
-
-int in_addr_to_string(int family, const union in_addr_union *u, char **ret) {
-        char *x;
-        size_t l;
-
-        assert(u);
-        assert(ret);
-
-        if (family == AF_INET)
-                l = INET_ADDRSTRLEN;
-        else if (family == AF_INET6)
-                l = INET6_ADDRSTRLEN;
-        else
-                return -EAFNOSUPPORT;
-
-        x = new(char, l);
-        if (!x)
-                return -ENOMEM;
-
-        errno = 0;
-        if (!inet_ntop(family, u, x, l)) {
-                free(x);
-                return errno > 0 ? -errno : -EINVAL;
-        }
-
-        *ret = x;
-        return 0;
-}
-
-int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret) {
-        size_t l;
-        char *x;
-        int r;
-
-        assert(u);
-        assert(ret);
-
-        /* Much like in_addr_to_string(), but optionally appends the zone interface index to the address, to properly
-         * handle IPv6 link-local addresses. */
-
-        if (family != AF_INET6)
-                goto fallback;
-        if (ifindex <= 0)
-                goto fallback;
-
-        r = in_addr_is_link_local(family, u);
-        if (r < 0)
-                return r;
-        if (r == 0)
-                goto fallback;
-
-        l = INET6_ADDRSTRLEN + 1 + DECIMAL_STR_MAX(ifindex) + 1;
-        x = new(char, l);
-        if (!x)
-                return -ENOMEM;
-
-        errno = 0;
-        if (!inet_ntop(family, u, x, l)) {
-                free(x);
-                return errno > 0 ? -errno : -EINVAL;
-        }
-
-        sprintf(strchr(x, 0), "%%%i", ifindex);
-        *ret = x;
-
-        return 0;
-
-fallback:
-        return in_addr_to_string(family, u, ret);
-}
-
-int in_addr_from_string(int family, const char *s, union in_addr_union *ret) {
-        union in_addr_union buffer;
-        assert(s);
-
-        if (!IN_SET(family, AF_INET, AF_INET6))
-                return -EAFNOSUPPORT;
-
-        errno = 0;
-        if (inet_pton(family, s, ret ?: &buffer) <= 0)
-                return errno > 0 ? -errno : -EINVAL;
-
-        return 0;
-}
-
-int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union *ret) {
-        int r;
-
-        assert(s);
-
-        r = in_addr_from_string(AF_INET, s, ret);
-        if (r >= 0) {
-                if (ret_family)
-                        *ret_family = AF_INET;
-                return 0;
-        }
-
-        r = in_addr_from_string(AF_INET6, s, ret);
-        if (r >= 0) {
-                if (ret_family)
-                        *ret_family = AF_INET6;
-                return 0;
-        }
-
-        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;
-
-        assert(s);
-        assert(family);
-        assert(ret);
-
-        /* Similar to in_addr_from_string_auto() but also parses an optionally appended IPv6 zone suffix ("scope id")
-         * if one is found. */
-
-        suffix = strchr(s, '%');
-        if (suffix) {
-
-                if (ifindex) {
-                        /* If we shall return the interface index, try to parse it */
-                        r = parse_ifindex(suffix + 1, &ifi);
-                        if (r < 0) {
-                                unsigned u;
-
-                                u = if_nametoindex(suffix + 1);
-                                if (u <= 0)
-                                        return -errno;
-
-                                ifi = (int) u;
-                        }
-                }
-
-                s = strndupa(s, suffix - s);
-        }
-
-        r = in_addr_from_string_auto(s, family, ret);
-        if (r < 0)
-                return r;
-
-        if (ifindex)
-                *ifindex = ifi;
-
-        return r;
-}
-#endif /* NM_IGNORED */
-
-unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr) {
-        assert(addr);
-
-        return 32 - u32ctz(be32toh(addr->s_addr));
-}
-
-struct in_addr* in4_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen) {
-        assert(addr);
-        assert(prefixlen <= 32);
-
-        /* Shifting beyond 32 is not defined, handle this specially. */
-        if (prefixlen == 0)
-                addr->s_addr = 0;
-        else
-                addr->s_addr = htobe32((0xffffffff << (32 - prefixlen)) & 0xffffffff);
-
-        return addr;
-}
-
-int in4_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen) {
-        uint8_t msb_octet = *(uint8_t*) addr;
-
-        /* addr may not be aligned, so make sure we only access it byte-wise */
-
-        assert(addr);
-        assert(prefixlen);
-
-        if (msb_octet < 128)
-                /* class A, leading bits: 0 */
-                *prefixlen = 8;
-        else if (msb_octet < 192)
-                /* class B, leading bits 10 */
-                *prefixlen = 16;
-        else if (msb_octet < 224)
-                /* class C, leading bits 110 */
-                *prefixlen = 24;
-        else
-                /* class D or E, no default prefixlen */
-                return -ERANGE;
-
-        return 0;
-}
-
-int in4_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask) {
-        unsigned char prefixlen;
-        int r;
-
-        assert(addr);
-        assert(mask);
-
-        r = in4_addr_default_prefixlen(addr, &prefixlen);
-        if (r < 0)
-                return r;
-
-        in4_addr_prefixlen_to_netmask(mask, prefixlen);
-        return 0;
-}
-
-#if 0 /* NM_IGNORED */
-int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen) {
-        assert(addr);
-
-        if (family == AF_INET) {
-                struct in_addr mask;
-
-                if (!in4_addr_prefixlen_to_netmask(&mask, prefixlen))
-                        return -EINVAL;
-
-                addr->in.s_addr &= mask.s_addr;
-                return 0;
-        }
-
-        if (family == AF_INET6) {
-                unsigned i;
-
-                for (i = 0; i < 16; i++) {
-                        uint8_t mask;
-
-                        if (prefixlen >= 8) {
-                                mask = 0xFF;
-                                prefixlen -= 8;
-                        } else {
-                                mask = 0xFF << (8 - prefixlen);
-                                prefixlen = 0;
-                        }
-
-                        addr->in6.s6_addr[i] &= mask;
-                }
-
-                return 0;
-        }
-
-        return -EAFNOSUPPORT;
-}
-
-int in_addr_prefix_covers(int family,
-                          const union in_addr_union *prefix,
-                          unsigned char prefixlen,
-                          const union in_addr_union *address) {
-
-        union in_addr_union masked_prefix, masked_address;
-        int r;
-
-        assert(prefix);
-        assert(address);
-
-        masked_prefix = *prefix;
-        r = in_addr_mask(family, &masked_prefix, prefixlen);
-        if (r < 0)
-                return r;
-
-        masked_address = *address;
-        r = in_addr_mask(family, &masked_address, prefixlen);
-        if (r < 0)
-                return r;
-
-        return in_addr_equal(family, &masked_prefix, &masked_address);
-}
-
-int in_addr_parse_prefixlen(int family, const char *p, unsigned char *ret) {
-        uint8_t u;
-        int r;
-
-        if (!IN_SET(family, AF_INET, AF_INET6))
-                return -EAFNOSUPPORT;
-
-        r = safe_atou8(p, &u);
-        if (r < 0)
-                return r;
-
-        if (u > FAMILY_ADDRESS_SIZE(family) * 8)
-                return -ERANGE;
-
-        *ret = u;
-        return 0;
-}
-
-int in_addr_prefix_from_string(
-                const char *p,
-                int family,
-                union in_addr_union *ret_prefix,
-                unsigned char *ret_prefixlen) {
-
-        union in_addr_union buffer;
-        const char *e, *l;
-        unsigned char k;
-        int r;
-
-        assert(p);
-
-        if (!IN_SET(family, AF_INET, AF_INET6))
-                return -EAFNOSUPPORT;
-
-        e = strchr(p, '/');
-        if (e)
-                l = strndupa(p, e - p);
-        else
-                l = p;
-
-        r = in_addr_from_string(family, l, &buffer);
-        if (r < 0)
-                return r;
-
-        if (e) {
-                r = in_addr_parse_prefixlen(family, e+1, &k);
-                if (r < 0)
-                        return r;
-        } else
-                k = FAMILY_ADDRESS_SIZE(family) * 8;
-
-        if (ret_prefix)
-                *ret_prefix = buffer;
-        if (ret_prefixlen)
-                *ret_prefixlen = k;
-
-        return 0;
-}
-
-int in_addr_prefix_from_string_auto(
-                const char *p,
-                int *ret_family,
-                union in_addr_union *ret_prefix,
-                unsigned char *ret_prefixlen) {
-
-        union in_addr_union buffer;
-        const char *e, *l;
-        unsigned char k;
-        int family, r;
-
-        assert(p);
-
-        e = strchr(p, '/');
-        if (e)
-                l = strndupa(p, e - p);
-        else
-                l = p;
-
-        r = in_addr_from_string_auto(l, &family, &buffer);
-        if (r < 0)
-                return r;
-
-        if (e) {
-                r = in_addr_parse_prefixlen(family, e+1, &k);
-                if (r < 0)
-                        return r;
-        } else
-                k = FAMILY_ADDRESS_SIZE(family) * 8;
-
-        if (ret_family)
-                *ret_family = family;
-        if (ret_prefix)
-                *ret_prefix = buffer;
-        if (ret_prefixlen)
-                *ret_prefixlen = k;
-
-        return 0;
-
-}
-
-void in_addr_data_hash_func(const void *p, struct siphash *state) {
-        const struct in_addr_data *a = p;
-
-        siphash24_compress(&a->family, sizeof(a->family), state);
-        siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state);
-}
-
-int in_addr_data_compare_func(const void *a, const void *b) {
-        const struct in_addr_data *x = a, *y = b;
-        int r;
-
-        r = CMP(x->family, y->family);
-        if (r != 0)
-                return r;
-
-        return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family));
-}
-
-const struct hash_ops in_addr_data_hash_ops = {
-        .hash = in_addr_data_hash_func,
-        .compare = in_addr_data_compare_func,
-};
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/in-addr-util.h b/src/systemd/src/basic/in-addr-util.h
deleted file mode 100644
index e4be30dc..00000000
--- a/src/systemd/src/basic/in-addr-util.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <netinet/in.h>
-#include <stddef.h>
-#include <sys/socket.h>
-
-#include "hash-funcs.h"
-#include "macro.h"
-#include "util.h"
-
-union in_addr_union {
-        struct in_addr in;
-        struct in6_addr in6;
-};
-
-struct in_addr_data {
-        int family;
-        union in_addr_union address;
-};
-
-bool in4_addr_is_null(const struct in_addr *a);
-int in_addr_is_null(int family, const union in_addr_union *u);
-
-int in_addr_is_multicast(int family, const union in_addr_union *u);
-
-bool in4_addr_is_link_local(const struct in_addr *a);
-int in_addr_is_link_local(int family, const union in_addr_union *u);
-
-bool in4_addr_is_localhost(const struct in_addr *a);
-int in_addr_is_localhost(int family, const union in_addr_union *u);
-
-int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b);
-int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen);
-int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
-int in_addr_to_string(int family, const union in_addr_union *u, char **ret);
-int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret);
-int in_addr_from_string(int family, const char *s, union in_addr_union *ret);
-int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union *ret);
-int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex);
-unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr);
-struct in_addr* in4_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen);
-int in4_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen);
-int in4_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask);
-int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen);
-int in_addr_prefix_covers(int family, const union in_addr_union *prefix, unsigned char prefixlen, const union in_addr_union *address);
-int in_addr_parse_prefixlen(int family, const char *p, unsigned char *ret);
-int in_addr_prefix_from_string(const char *p, int family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen);
-int in_addr_prefix_from_string_auto(const char *p, int *ret_family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen);
-
-static inline size_t FAMILY_ADDRESS_SIZE(int family) {
-        assert(IN_SET(family, AF_INET, AF_INET6));
-        return family == AF_INET6 ? 16 : 4;
-}
-
-#define IN_ADDR_NULL ((union in_addr_union) {})
-
-void in_addr_data_hash_func(const void *p, struct siphash *state);
-int in_addr_data_compare_func(const void *a, const void *b);
-extern const struct hash_ops in_addr_data_hash_ops;
diff --git a/src/systemd/src/basic/io-util.c b/src/systemd/src/basic/io-util.c
deleted file mode 100644
index 6f46c944..00000000
--- a/src/systemd/src/basic/io-util.c
+++ /dev/null
@@ -1,256 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <limits.h>
-#include <poll.h>
-#include <stdio.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "io-util.h"
-#include "time-util.h"
-
-int flush_fd(int fd) {
-        struct pollfd pollfd = {
-                .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
-         * (due to IP packet checksum mismatches), hence this function is only safe to be non-blocking if the fd used
-         * was set to non-blocking too. */
-
-        for (;;) {
-                char buf[LINE_MAX];
-                ssize_t l;
-                int r;
-
-                r = poll(&pollfd, 1, 0);
-                if (r < 0) {
-                        if (errno == EINTR)
-                                continue;
-
-                        return -errno;
-
-                } else if (r == 0)
-                        return count;
-
-                l = read(fd, buf, sizeof(buf));
-                if (l < 0) {
-
-                        if (errno == EINTR)
-                                continue;
-
-                        if (errno == EAGAIN)
-                                return count;
-
-                        return -errno;
-                } else if (l == 0)
-                        return count;
-
-                count += (int) l;
-        }
-}
-
-ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) {
-        uint8_t *p = buf;
-        ssize_t n = 0;
-
-        assert(fd >= 0);
-        assert(buf);
-
-        /* If called with nbytes == 0, let's call read() at least
-         * once, to validate the operation */
-
-        if (nbytes > (size_t) SSIZE_MAX)
-                return -EINVAL;
-
-        do {
-                ssize_t k;
-
-                k = read(fd, p, nbytes);
-                if (k < 0) {
-                        if (errno == EINTR)
-                                continue;
-
-                        if (errno == EAGAIN && do_poll) {
-
-                                /* We knowingly ignore any return value here,
-                                 * and expect that any error/EOF is reported
-                                 * via read() */
-
-                                (void) fd_wait_for_event(fd, POLLIN, USEC_INFINITY);
-                                continue;
-                        }
-
-                        return n > 0 ? n : -errno;
-                }
-
-                if (k == 0)
-                        return n;
-
-                assert((size_t) k <= nbytes);
-
-                p += k;
-                nbytes -= k;
-                n += k;
-        } while (nbytes > 0);
-
-        return n;
-}
-
-int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) {
-        ssize_t n;
-
-        n = loop_read(fd, buf, nbytes, do_poll);
-        if (n < 0)
-                return (int) n;
-        if ((size_t) n != nbytes)
-                return -EIO;
-
-        return 0;
-}
-
-int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
-        const uint8_t *p = buf;
-
-        assert(fd >= 0);
-        assert(buf);
-
-        if (_unlikely_(nbytes > (size_t) SSIZE_MAX))
-                return -EINVAL;
-
-        do {
-                ssize_t k;
-
-                k = write(fd, p, nbytes);
-                if (k < 0) {
-                        if (errno == EINTR)
-                                continue;
-
-                        if (errno == EAGAIN && do_poll) {
-                                /* We knowingly ignore any return value here,
-                                 * and expect that any error/EOF is reported
-                                 * via write() */
-
-                                (void) fd_wait_for_event(fd, POLLOUT, USEC_INFINITY);
-                                continue;
-                        }
-
-                        return -errno;
-                }
-
-                if (_unlikely_(nbytes > 0 && k == 0)) /* Can't really happen */
-                        return -EIO;
-
-                assert((size_t) k <= nbytes);
-
-                p += k;
-                nbytes -= k;
-        } while (nbytes > 0);
-
-        return 0;
-}
-
-int pipe_eof(int fd) {
-        struct pollfd pollfd = {
-                .fd = fd,
-                .events = POLLIN|POLLHUP,
-        };
-
-        int r;
-
-        r = poll(&pollfd, 1, 0);
-        if (r < 0)
-                return -errno;
-
-        if (r == 0)
-                return 0;
-
-        return pollfd.revents & POLLHUP;
-}
-
-int fd_wait_for_event(int fd, int event, usec_t t) {
-
-        struct pollfd pollfd = {
-                .fd = fd,
-                .events = event,
-        };
-
-        struct timespec ts;
-        int r;
-
-        r = ppoll(&pollfd, 1, t == USEC_INFINITY ? NULL : timespec_store(&ts, t), NULL);
-        if (r < 0)
-                return -errno;
-        if (r == 0)
-                return 0;
-
-        return pollfd.revents;
-}
-
-static size_t nul_length(const uint8_t *p, size_t sz) {
-        size_t n = 0;
-
-        while (sz > 0) {
-                if (*p != 0)
-                        break;
-
-                n++;
-                p++;
-                sz--;
-        }
-
-        return n;
-}
-
-ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) {
-        const uint8_t *q, *w, *e;
-        ssize_t l;
-
-        q = w = p;
-        e = q + sz;
-        while (q < e) {
-                size_t n;
-
-                n = nul_length(q, e - q);
-
-                /* If there are more than the specified run length of
-                 * NUL bytes, or if this is the beginning or the end
-                 * of the buffer, then seek instead of write */
-                if ((n > run_length) ||
-                    (n > 0 && q == p) ||
-                    (n > 0 && q + n >= e)) {
-                        if (q > w) {
-                                l = write(fd, w, q - w);
-                                if (l < 0)
-                                        return -errno;
-                                if (l != q -w)
-                                        return -EIO;
-                        }
-
-                        if (lseek(fd, n, SEEK_CUR) == (off_t) -1)
-                                return -errno;
-
-                        q += n;
-                        w = q;
-                } else if (n > 0)
-                        q += n;
-                else
-                        q++;
-        }
-
-        if (q > w) {
-                l = write(fd, w, q - w);
-                if (l < 0)
-                        return -errno;
-                if (l != q - w)
-                        return -EIO;
-        }
-
-        return q - (const uint8_t*) p;
-}
diff --git a/src/systemd/src/basic/io-util.h b/src/systemd/src/basic/io-util.h
deleted file mode 100644
index ed189b58..00000000
--- a/src/systemd/src/basic/io-util.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-
-#include "macro.h"
-#include "time-util.h"
-
-int flush_fd(int fd);
-
-ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll);
-int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll);
-int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll);
-
-int pipe_eof(int fd);
-
-int fd_wait_for_event(int fd, int event, usec_t timeout);
-
-ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length);
-
-static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, size_t n) {
-        size_t j, r = 0;
-
-        for (j = 0; j < n; j++)
-                r += i[j].iov_len;
-
-        return r;
-}
-
-static inline size_t IOVEC_INCREMENT(struct iovec *i, size_t n, size_t k) {
-        size_t j;
-
-        for (j = 0; j < n; j++) {
-                size_t sub;
-
-                if (_unlikely_(k <= 0))
-                        break;
-
-                sub = MIN(i[j].iov_len, k);
-                i[j].iov_len -= sub;
-                i[j].iov_base = (uint8_t*) i[j].iov_base + sub;
-                k -= sub;
-        }
-
-        return k;
-}
-
-static inline bool FILE_SIZE_VALID(uint64_t l) {
-        /* ftruncate() and friends take an unsigned file size, but actually cannot deal with file sizes larger than
-         * 2^63 since the kernel internally handles it as signed value. This call allows checking for this early. */
-
-        return (l >> 63) == 0;
-}
-
-static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) {
-
-        /* Same as above, but allows one extra value: -1 as indication for infinity. */
-
-        if (l == (uint64_t) -1)
-                return true;
-
-        return FILE_SIZE_VALID(l);
-
-}
-
-#define IOVEC_INIT(base, len) { .iov_base = (base), .iov_len = (len) }
-#define IOVEC_MAKE(base, len) (struct iovec) IOVEC_INIT(base, len)
-#define IOVEC_INIT_STRING(string) IOVEC_INIT((char*) string, strlen(string))
-#define IOVEC_MAKE_STRING(string) (struct iovec) IOVEC_INIT_STRING(string)
diff --git a/src/systemd/src/basic/list.h b/src/systemd/src/basic/list.h
deleted file mode 100644
index 643e0bea..00000000
--- a/src/systemd/src/basic/list.h
+++ /dev/null
@@ -1,169 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-/* The head of the linked list. Use this in the structure that shall
- * contain the head of the linked list */
-#define LIST_HEAD(t,name)                                               \
-        t *name
-
-/* The pointers in the linked list's items. Use this in the item structure */
-#define LIST_FIELDS(t,name)                                             \
-        t *name##_next, *name##_prev
-
-/* Initialize the list's head */
-#define LIST_HEAD_INIT(head)                                            \
-        do {                                                            \
-                (head) = NULL; }                                        \
-        while (false)
-
-/* Initialize a list item */
-#define LIST_INIT(name,item)                                            \
-        do {                                                            \
-                typeof(*(item)) *_item = (item);                        \
-                assert(_item);                                          \
-                _item->name##_prev = _item->name##_next = NULL;         \
-        } while (false)
-
-/* Prepend an item to the list */
-#define LIST_PREPEND(name,head,item)                                    \
-        do {                                                            \
-                typeof(*(head)) **_head = &(head), *_item = (item);     \
-                assert(_item);                                          \
-                if ((_item->name##_next = *_head))                      \
-                        _item->name##_next->name##_prev = _item;        \
-                _item->name##_prev = NULL;                              \
-                *_head = _item;                                         \
-        } while (false)
-
-/* Append an item to the list */
-#define LIST_APPEND(name,head,item)                                     \
-        do {                                                            \
-                typeof(*(head)) *_tail;                                 \
-                LIST_FIND_TAIL(name,head,_tail);                        \
-                LIST_INSERT_AFTER(name,head,_tail,item);                \
-        } while (false)
-
-/* Remove an item from the list */
-#define LIST_REMOVE(name,head,item)                                     \
-        do {                                                            \
-                typeof(*(head)) **_head = &(head), *_item = (item);     \
-                assert(_item);                                          \
-                if (_item->name##_next)                                 \
-                        _item->name##_next->name##_prev = _item->name##_prev; \
-                if (_item->name##_prev)                                 \
-                        _item->name##_prev->name##_next = _item->name##_next; \
-                else {                                                  \
-                        assert(*_head == _item);                        \
-                        *_head = _item->name##_next;                    \
-                }                                                       \
-                _item->name##_next = _item->name##_prev = NULL;         \
-        } while (false)
-
-/* Find the head of the list */
-#define LIST_FIND_HEAD(name,item,head)                                  \
-        do {                                                            \
-                typeof(*(item)) *_item = (item);                        \
-                if (!_item)                                             \
-                        (head) = NULL;                                  \
-                else {                                                  \
-                        while (_item->name##_prev)                      \
-                                _item = _item->name##_prev;             \
-                        (head) = _item;                                 \
-                }                                                       \
-        } while (false)
-
-/* Find the tail of the list */
-#define LIST_FIND_TAIL(name,item,tail)                                  \
-        do {                                                            \
-                typeof(*(item)) *_item = (item);                        \
-                if (!_item)                                             \
-                        (tail) = NULL;                                  \
-                else {                                                  \
-                        while (_item->name##_next)                      \
-                                _item = _item->name##_next;             \
-                        (tail) = _item;                                 \
-                }                                                       \
-        } while (false)
-
-/* Insert an item after another one (a = where, b = what) */
-#define LIST_INSERT_AFTER(name,head,a,b)                                \
-        do {                                                            \
-                typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \
-                assert(_b);                                             \
-                if (!_a) {                                              \
-                        if ((_b->name##_next = *_head))                 \
-                                _b->name##_next->name##_prev = _b;      \
-                        _b->name##_prev = NULL;                         \
-                        *_head = _b;                                    \
-                } else {                                                \
-                        if ((_b->name##_next = _a->name##_next))        \
-                                _b->name##_next->name##_prev = _b;      \
-                        _b->name##_prev = _a;                           \
-                        _a->name##_next = _b;                           \
-                }                                                       \
-        } while (false)
-
-/* Insert an item before another one (a = where, b = what) */
-#define LIST_INSERT_BEFORE(name,head,a,b)                               \
-        do {                                                            \
-                typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \
-                assert(_b);                                             \
-                if (!_a) {                                              \
-                        if (!*_head) {                                  \
-                                _b->name##_next = NULL;                 \
-                                _b->name##_prev = NULL;                 \
-                                *_head = _b;                            \
-                        } else {                                        \
-                                typeof(*(head)) *_tail = (head);        \
-                                while (_tail->name##_next)              \
-                                        _tail = _tail->name##_next;     \
-                                _b->name##_next = NULL;                 \
-                                _b->name##_prev = _tail;                \
-                                _tail->name##_next = _b;                \
-                        }                                               \
-                } else {                                                \
-                        if ((_b->name##_prev = _a->name##_prev))        \
-                                _b->name##_prev->name##_next = _b;      \
-                        else                                            \
-                                *_head = _b;                            \
-                        _b->name##_next = _a;                           \
-                        _a->name##_prev = _b;                           \
-                }                                                       \
-        } while (false)
-
-#define LIST_JUST_US(name,item)                                         \
-        (!(item)->name##_prev && !(item)->name##_next)                  \
-
-#define LIST_FOREACH(name,i,head)                                       \
-        for ((i) = (head); (i); (i) = (i)->name##_next)
-
-#define LIST_FOREACH_SAFE(name,i,n,head)                                \
-        for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n))
-
-#define LIST_FOREACH_BEFORE(name,i,p)                                   \
-        for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev)
-
-#define LIST_FOREACH_AFTER(name,i,p)                                    \
-        for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next)
-
-/* Iterate through all the members of the list p is included in, but skip over p */
-#define LIST_FOREACH_OTHERS(name,i,p)                                   \
-        for (({                                                         \
-                (i) = (p);                                              \
-                while ((i) && (i)->name##_prev)                         \
-                        (i) = (i)->name##_prev;                         \
-                if ((i) == (p))                                         \
-                        (i) = (p)->name##_next;                         \
-             });                                                        \
-             (i);                                                       \
-             (i) = (i)->name##_next == (p) ? (p)->name##_next : (i)->name##_next)
-
-/* Loop starting from p->next until p->prev.
-   p can be adjusted meanwhile. */
-#define LIST_LOOP_BUT_ONE(name,i,head,p)                                \
-        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
deleted file mode 100644
index 28edd572..00000000
--- a/src/systemd/src/basic/log.h
+++ /dev/null
@@ -1,326 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <syslog.h>
-
-#include "macro.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,
-        LOG_REALM_UDEV,
-        _LOG_REALM_MAX,
-} LogRealm;
-
-#ifndef LOG_REALM
-#  define LOG_REALM LOG_REALM_SYSTEMD
-#endif
-
-typedef enum LogTarget{
-        LOG_TARGET_CONSOLE,
-        LOG_TARGET_CONSOLE_PREFIXED,
-        LOG_TARGET_KMSG,
-        LOG_TARGET_JOURNAL,
-        LOG_TARGET_JOURNAL_OR_KMSG,
-        LOG_TARGET_SYSLOG,
-        LOG_TARGET_SYSLOG_OR_KMSG,
-        LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */
-        LOG_TARGET_NULL,
-        _LOG_TARGET_MAX,
-        _LOG_TARGET_INVALID = -1
-} LogTarget;
-
-#define LOG_REALM_PLUS_LEVEL(realm, level)      \
-        ((realm) << 10 | (level))
-#define LOG_REALM_REMOVE_LEVEL(realm_level)     \
-        ((realm_level >> 10))
-
-void log_set_target(LogTarget target);
-void log_set_max_level_realm(LogRealm realm, int level);
-#define log_set_max_level(level)                \
-        log_set_max_level_realm(LOG_REALM, (level))
-
-void log_set_facility(int facility);
-
-int log_set_target_from_string(const char *e);
-int log_set_max_level_from_string_realm(LogRealm realm, const char *e);
-#define log_set_max_level_from_string(e)        \
-        log_set_max_level_from_string_realm(LOG_REALM, (e))
-
-void log_show_color(bool b);
-bool log_get_show_color(void) _pure_;
-void log_show_location(bool b);
-bool log_get_show_location(void) _pure_;
-
-int log_show_color_from_string(const char *e);
-int log_show_location_from_string(const char *e);
-
-LogTarget log_get_target(void) _pure_;
-#if 0 /* NM_IGNORED */
-int log_get_max_level_realm(LogRealm realm) _pure_;
-#endif /* NM_IGNORED */
-#define log_get_max_level()                     \
-        log_get_max_level_realm(LOG_REALM)
-
-/* Functions below that open and close logs or configure logging based on the
- * environment should not be called from library code — this is always a job
- * for the application itself.
- */
-
-int log_open(void);
-void log_close(void);
-void log_forget_fds(void);
-
-void log_parse_environment_realm(LogRealm realm);
-#define log_parse_environment() \
-        log_parse_environment_realm(LOG_REALM)
-
-#if 0 /* NM_IGNORED */
-int log_dispatch_internal(
-                int level,
-                int error,
-                const char *file,
-                int line,
-                const char *func,
-                const char *object_field,
-                const char *object,
-                const char *extra,
-                const char *extra_field,
-                char *buffer);
-
-int log_internal_realm(
-                int level,
-                int error,
-                const char *file,
-                int line,
-                const char *func,
-                const char *format, ...) _printf_(6,7);
-#endif /* NM_IGNORED */
-#define log_internal(level, ...) \
-        log_internal_realm(LOG_REALM_PLUS_LEVEL(LOG_REALM, (level)), __VA_ARGS__)
-
-#if 0 /* NM_IGNORED */
-int log_internalv_realm(
-                int level,
-                int error,
-                const char *file,
-                int line,
-                const char *func,
-                const char *format,
-                va_list ap) _printf_(6,0);
-#define log_internalv(level, ...) \
-        log_internalv_realm(LOG_REALM_PLUS_LEVEL(LOG_REALM, (level)), __VA_ARGS__)
-
-/* Realm is fixed to LOG_REALM_SYSTEMD for those */
-int log_object_internal(
-                int level,
-                int error,
-                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, ...) _printf_(10,11);
-
-int log_struct_internal(
-                int level,
-                int error,
-                const char *file,
-                int line,
-                const char *func,
-                const char *format, ...) _printf_(6,0) _sentinel_;
-
-int log_oom_internal(
-                LogRealm realm,
-                const char *file,
-                int line,
-                const char *func);
-
-int log_format_iovec(
-                struct iovec *iovec,
-                size_t iovec_len,
-                size_t *n,
-                bool newline_separator,
-                int error,
-                const char *format,
-                va_list ap) _printf_(6, 0);
-
-int log_struct_iovec_internal(
-                int level,
-                int error,
-                const char *file,
-                int line,
-                const char *func,
-                const struct iovec *input_iovec,
-                size_t n_input_iovec);
-
-/* This modifies the buffer passed! */
-int log_dump_internal(
-                int level,
-                int error,
-                const char *file,
-                int line,
-                const char *func,
-                char *buffer);
-
-/* Logging for various assertions */
-_noreturn_ void log_assert_failed_realm(
-                LogRealm realm,
-                const char *text,
-                const char *file,
-                int line,
-                const char *func);
-#define log_assert_failed(text, ...) \
-        log_assert_failed_realm(LOG_REALM, (text), __VA_ARGS__)
-
-_noreturn_ void log_assert_failed_unreachable_realm(
-                LogRealm realm,
-                const char *text,
-                const char *file,
-                int line,
-                const char *func);
-#define log_assert_failed_unreachable(text, ...) \
-        log_assert_failed_unreachable_realm(LOG_REALM, (text), __VA_ARGS__)
-
-void log_assert_failed_return_realm(
-                LogRealm realm,
-                const char *text,
-                const char *file,
-                int line,
-                const char *func);
-#define log_assert_failed_return(text, ...) \
-        log_assert_failed_return_realm(LOG_REALM, (text), __VA_ARGS__)
-
-#define log_dispatch(level, error, buffer)                              \
-        log_dispatch_internal(level, error, __FILE__, __LINE__, __func__, NULL, NULL, NULL, NULL, buffer)
-#endif /* NM_IGNORED */
-
-/* Logging with level */
-#define log_full_errno_realm(realm, level, error, ...)                  \
-        ({                                                              \
-                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);                                     \
-        })
-
-#define log_full_errno(level, error, ...)                               \
-        log_full_errno_realm(LOG_REALM, (level), (error), __VA_ARGS__)
-
-#define log_full(level, ...) log_full_errno((level), 0, __VA_ARGS__)
-
-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(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__)
-#define log_info_errno(error, ...)      log_full_errno(LOG_INFO,    error, __VA_ARGS__)
-#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(log_emergency_level(), error, __VA_ARGS__)
-
-#ifdef LOG_TRACE
-#  define log_trace(...) log_debug(__VA_ARGS__)
-#else
-#  define log_trace(...) do {} while (0)
-#endif
-
-/* Structured logging */
-#define log_struct_errno(level, error, ...) \
-        log_struct_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
-                            error, __FILE__, __LINE__, __func__, __VA_ARGS__, NULL)
-#define log_struct(level, ...) log_struct_errno(level, 0, __VA_ARGS__)
-
-#define log_struct_iovec_errno(level, error, iovec, n_iovec)            \
-        log_struct_iovec_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
-                                  error, __FILE__, __LINE__, __func__, iovec, n_iovec)
-#define log_struct_iovec(level, iovec, n_iovec) log_struct_iovec_errno(level, 0, iovec, n_iovec)
-
-/* This modifies the buffer passed! */
-#define log_dump(level, buffer) \
-        log_dump_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
-                          0, __FILE__, __LINE__, __func__, buffer)
-
-#define log_oom() log_oom_internal(LOG_REALM, __FILE__, __LINE__, __func__)
-
-bool log_on_console(void) _pure_;
-
-const char *log_target_to_string(LogTarget target) _const_;
-LogTarget log_target_from_string(const char *s) _pure_;
-
-/* Helper to prepare various field for structured logging */
-#define LOG_MESSAGE(fmt, ...) "MESSAGE=" fmt, ##__VA_ARGS__
-
-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_dup_console(void);
-
-int log_syntax_internal(
-                const char *unit,
-                int level,
-                const char *config_file,
-                unsigned config_line,
-                int error,
-                const char *file,
-                int line,
-                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);                     \
-                (log_get_max_level() >= LOG_PRI(_level))                \
-                        ? log_syntax_internal(unit, _level, config_file, config_line, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \
-                        : -abs(_e);                                     \
-        })
-
-#define log_syntax_invalid_utf8(unit, level, config_file, config_line, rvalue) \
-        ({                                                              \
-                int _level = (level);                                   \
-                (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
deleted file mode 100644
index 22193fe3..00000000
--- a/src/systemd/src/basic/macro.h
+++ /dev/null
@@ -1,476 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <inttypes.h>
-#include <stdbool.h>
-#include <sys/param.h>
-#include <sys/sysmacros.h>
-#include <sys/types.h>
-
-#define _printf_(a, b) __attribute__ ((format (printf, a, b)))
-#ifdef __clang__
-#  define _alloc_(...)
-#else
-#  define _alloc_(...) __attribute__ ((alloc_size(__VA_ARGS__)))
-#endif
-#define _sentinel_ __attribute__ ((sentinel))
-#define _unused_ __attribute__ ((unused))
-#define _destructor_ __attribute__ ((destructor))
-#define _pure_ __attribute__ ((pure))
-#define _const_ __attribute__ ((const))
-#define _deprecated_ __attribute__ ((deprecated))
-#define _packed_ __attribute__ ((packed))
-#define _malloc_ __attribute__ ((malloc))
-#define _weak_ __attribute__ ((weak))
-#define _likely_(x) (__builtin_expect(!!(x), 1))
-#define _unlikely_(x) (__builtin_expect(!!(x), 0))
-#define _public_ __attribute__ ((visibility("default")))
-#define _hidden_ __attribute__ ((visibility("hidden")))
-#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 */
-#define DISABLE_WARNING_DECLARATION_AFTER_STATEMENT                     \
-        _Pragma("GCC diagnostic push");                                 \
-        _Pragma("GCC diagnostic ignored \"-Wdeclaration-after-statement\"")
-
-#define DISABLE_WARNING_FORMAT_NONLITERAL                               \
-        _Pragma("GCC diagnostic push");                                 \
-        _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"")
-
-#define DISABLE_WARNING_MISSING_PROTOTYPES                              \
-        _Pragma("GCC diagnostic push");                                 \
-        _Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"")
-
-#define DISABLE_WARNING_NONNULL                                         \
-        _Pragma("GCC diagnostic push");                                 \
-        _Pragma("GCC diagnostic ignored \"-Wnonnull\"")
-
-#define DISABLE_WARNING_SHADOW                                          \
-        _Pragma("GCC diagnostic push");                                 \
-        _Pragma("GCC diagnostic ignored \"-Wshadow\"")
-
-#define DISABLE_WARNING_INCOMPATIBLE_POINTER_TYPES                      \
-        _Pragma("GCC diagnostic push");                                 \
-        _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"")
-
-#define REENABLE_WARNING                                                \
-        _Pragma("GCC diagnostic pop")
-#else
-#define DISABLE_WARNING_DECLARATION_AFTER_STATEMENT
-#define DISABLE_WARNING_FORMAT_NONLITERAL
-#define DISABLE_WARNING_MISSING_PROTOTYPES
-#define DISABLE_WARNING_NONNULL
-#define DISABLE_WARNING_SHADOW
-#define REENABLE_WARNING
-#endif
-
-/* automake test harness */
-#define EXIT_TEST_SKIP 77
-
-#define XSTRINGIFY(x) #x
-#define STRINGIFY(x) XSTRINGIFY(x)
-
-#define XCONCATENATE(x, y) x ## y
-#define CONCATENATE(x, y) XCONCATENATE(x, y)
-
-#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq))
-#define UNIQ __COUNTER__
-
-/* builtins */
-#if __SIZEOF_INT__ == 4
-#define BUILTIN_FFS_U32(x) __builtin_ffs(x);
-#elif __SIZEOF_LONG__ == 4
-#define BUILTIN_FFS_U32(x) __builtin_ffsl(x);
-#else
-#error "neither int nor long are four bytes long?!?"
-#endif
-
-/* Rounds up */
-
-#define ALIGN4(l) (((l) + 3) & ~3)
-#define ALIGN8(l) (((l) + 7) & ~7)
-
-#if __SIZEOF_POINTER__ == 8
-#define ALIGN(l) ALIGN8(l)
-#elif __SIZEOF_POINTER__ == 4
-#define ALIGN(l) ALIGN4(l)
-#else
-#error "Wut? Pointers are neither 4 nor 8 bytes long?"
-#endif
-
-#define ALIGN_PTR(p) ((void*) ALIGN((unsigned long) (p)))
-#define ALIGN4_PTR(p) ((void*) ALIGN4((unsigned long) (p)))
-#define ALIGN8_PTR(p) ((void*) ALIGN8((unsigned long) (p)))
-
-static inline size_t ALIGN_TO(size_t l, size_t ali) {
-        return ((l + ali - 1) & ~(ali - 1));
-}
-
-#define ALIGN_TO_PTR(p, ali) ((void*) ALIGN_TO((unsigned long) (p), (ali)))
-
-/* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */
-static inline unsigned long ALIGN_POWER2(unsigned long u) {
-        /* clz(0) is undefined */
-        if (u == 1)
-                return 1;
-
-        /* left-shift overflow is undefined */
-        if (__builtin_clzl(u - 1UL) < 1)
-                return 0;
-
-        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)                                                   \
-        (__builtin_choose_expr(                                         \
-                !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \
-                sizeof(x)/sizeof((x)[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.
- * @type: the type of the container struct this is embedded in.
- * @member: the name of the member within the struct.
- */
-#define container_of(ptr, type, member) __container_of(UNIQ, (ptr), type, member)
-#define __container_of(uniq, ptr, type, member)                         \
-        ({                                                              \
-                const typeof( ((type*)0)->member ) *UNIQ_T(A, uniq) = (ptr); \
-                (type*)( (char *)UNIQ_T(A, uniq) - offsetof(type, member) ); \
-        })
-
-#undef MAX
-#define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b))
-#define __MAX(aq, a, bq, b)                             \
-        ({                                              \
-                const typeof(a) UNIQ_T(A, aq) = (a);    \
-                const typeof(b) UNIQ_T(B, bq) = (b);    \
-                UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \
-        })
-
-/* evaluates to (void) if _A or _B are not constant or of different types */
-#define CONST_MAX(_A, _B) \
-        (__builtin_choose_expr(                                         \
-                __builtin_constant_p(_A) &&                             \
-                __builtin_constant_p(_B) &&                             \
-                __builtin_types_compatible_p(typeof(_A), typeof(_B)),   \
-                ((_A) > (_B)) ? (_A) : (_B),                            \
-                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; }))
-
-#define MAX3(x, y, z)                                   \
-        ({                                              \
-                const typeof(x) _c = MAX(x, y);         \
-                MAX(_c, z);                             \
-        })
-
-#undef MIN
-#define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b))
-#define __MIN(aq, a, bq, b)                             \
-        ({                                              \
-                const typeof(a) UNIQ_T(A, aq) = (a);    \
-                const typeof(b) UNIQ_T(B, bq) = (b);    \
-                UNIQ_T(A, aq) < UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \
-        })
-
-#define MIN3(x, y, z)                                   \
-        ({                                              \
-                const typeof(x) _c = MIN(x, y);         \
-                MIN(_c, z);                             \
-        })
-
-#define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b))
-#define __LESS_BY(aq, a, bq, b)                         \
-        ({                                              \
-                const typeof(a) UNIQ_T(A, aq) = (a);    \
-                const typeof(b) UNIQ_T(B, bq) = (b);    \
-                UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) - UNIQ_T(B, bq) : 0; \
-        })
-
-#define CMP(a, b) __CMP(UNIQ, (a), UNIQ, (b))
-#define __CMP(aq, a, bq, b)                             \
-        ({                                              \
-                const typeof(a) UNIQ_T(A, aq) = (a);    \
-                const typeof(b) UNIQ_T(B, bq) = (b);    \
-                UNIQ_T(A, aq) < UNIQ_T(B, bq) ? -1 :    \
-                UNIQ_T(A, aq) > UNIQ_T(B, bq) ? 1 : 0;  \
-        })
-
-#undef CLAMP
-#define CLAMP(x, low, high) __CLAMP(UNIQ, (x), UNIQ, (low), UNIQ, (high))
-#define __CLAMP(xq, x, lowq, low, highq, high)                          \
-        ({                                                              \
-                const typeof(x) UNIQ_T(X, xq) = (x);                    \
-                const typeof(low) UNIQ_T(LOW, lowq) = (low);            \
-                const typeof(high) UNIQ_T(HIGH, highq) = (high);        \
-                        UNIQ_T(X, xq) > UNIQ_T(HIGH, highq) ?           \
-                                UNIQ_T(HIGH, highq) :                   \
-                                UNIQ_T(X, xq) < UNIQ_T(LOW, lowq) ?     \
-                                        UNIQ_T(LOW, lowq) :             \
-                                        UNIQ_T(X, xq);                  \
-        })
-
-/* [(x + y - 1) / y] suffers from an integer overflow, even though the
- * computation should be possible in the given type. Therefore, we use
- * [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the
- * quotient and the remainder, so both should be equally fast. */
-#define DIV_ROUND_UP(_x, _y)                                            \
-        ({                                                              \
-                const typeof(_x) __x = (_x);                            \
-                const typeof(_y) __y = (_y);                            \
-                (__x / __y + !!(__x % __y));                            \
-        })
-
-#ifdef __COVERITY__
-
-/* Use special definitions of assertion macros in order to prevent
- * false positives of ASSERT_SIDE_EFFECT on Coverity static analyzer
- * for uses of assert_se() and assert_return().
- *
- * These definitions make expression go through a (trivial) function
- * call to ensure they are not discarded. Also use ! or !! to ensure
- * the boolean expressions are seen as such.
- *
- * This technique has been described and recommended in:
- * https://community.synopsys.com/s/question/0D534000046Yuzb/suppressing-assertsideeffect-for-functions-that-allow-for-sideeffects
- */
-
-extern void __coverity_panic__(void);
-
-static inline int __coverity_check__(int condition) {
-        return condition;
-}
-
-#define assert_message_se(expr, message)                                \
-        do {                                                            \
-                if (__coverity_check__(!(expr)))                        \
-                        __coverity_panic__();                           \
-        } while (false)
-
-#define assert_log(expr, message) __coverity_check__(!!(expr))
-
-#else  /* ! __COVERITY__ */
-
-#define assert_message_se(expr, message)                                \
-        do {                                                            \
-                if (_unlikely_(!(expr)))                                \
-                        log_assert_failed(message, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
-        } while (false)
-
-#define assert_log(expr, message) ((_likely_(expr))                     \
-        ? (true)                                                        \
-        : (log_assert_failed_return(message, __FILE__, __LINE__, __PRETTY_FUNCTION__), false))
-
-#endif  /* __COVERITY__ */
-
-#define assert_se(expr) assert_message_se(expr, #expr)
-
-/* We override the glibc assert() here. */
-#undef assert
-#ifdef NDEBUG
-#define assert(expr) do {} while (false)
-#else
-#define assert(expr) assert_message_se(expr, #expr)
-#endif
-
-#define assert_not_reached(t)                                           \
-        do {                                                            \
-                log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
-        } while (false)
-
-#if defined(static_assert)
-/* static_assert() is sometimes defined in a way that trips up
- * -Wdeclaration-after-statement, hence let's temporarily turn off
- * this warning around it. */
-#define assert_cc(expr)                                                 \
-        DISABLE_WARNING_DECLARATION_AFTER_STATEMENT;                    \
-        static_assert(expr, #expr);                                     \
-        REENABLE_WARNING
-#else
-#define assert_cc(expr)                                                 \
-        DISABLE_WARNING_DECLARATION_AFTER_STATEMENT;                    \
-        struct CONCATENATE(_assert_struct_, __COUNTER__) {              \
-                char x[(expr) ? 0 : -1];                                \
-        };                                                              \
-        REENABLE_WARNING
-#endif
-
-#define assert_return(expr, r)                                          \
-        do {                                                            \
-                if (!assert_log(expr, #expr))                           \
-                        return (r);                                     \
-        } while (false)
-
-#define assert_return_errno(expr, r, err)                               \
-        do {                                                            \
-                if (!assert_log(expr, #expr)) {                         \
-                        errno = err;                                    \
-                        return (r);                                     \
-                }                                                       \
-        } while (false)
-
-#define PTR_TO_INT(p) ((int) ((intptr_t) (p)))
-#define INT_TO_PTR(u) ((void *) ((intptr_t) (u)))
-#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p)))
-#define UINT_TO_PTR(u) ((void *) ((uintptr_t) (u)))
-
-#define PTR_TO_LONG(p) ((long) ((intptr_t) (p)))
-#define LONG_TO_PTR(u) ((void *) ((intptr_t) (u)))
-#define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p)))
-#define ULONG_TO_PTR(u) ((void *) ((uintptr_t) (u)))
-
-#define PTR_TO_INT32(p) ((int32_t) ((intptr_t) (p)))
-#define INT32_TO_PTR(u) ((void *) ((intptr_t) (u)))
-#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p)))
-#define UINT32_TO_PTR(u) ((void *) ((uintptr_t) (u)))
-
-#define PTR_TO_INT64(p) ((int64_t) ((intptr_t) (p)))
-#define INT64_TO_PTR(u) ((void *) ((intptr_t) (u)))
-#define PTR_TO_UINT64(p) ((uint64_t) ((uintptr_t) (p)))
-#define UINT64_TO_PTR(u) ((void *) ((uintptr_t) (u)))
-
-#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p)))
-#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u)))
-
-#define CHAR_TO_STR(x) ((char[2]) { x, 0 })
-
-#define char_array_0(x) x[sizeof(x)-1] = 0;
-
-/* Returns the number of chars needed to format variables of the
- * specified type as a decimal string. Adds in extra space for a
- * negative '-' prefix (hence works correctly on signed
- * types). Includes space for the trailing NUL. */
-#define DECIMAL_STR_MAX(type)                                           \
-        (2+(sizeof(type) <= 1 ? 3 :                                     \
-            sizeof(type) <= 2 ? 5 :                                     \
-            sizeof(type) <= 4 ? 10 :                                    \
-            sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)])))
-
-#define DECIMAL_STR_WIDTH(x)                            \
-        ({                                              \
-                typeof(x) _x_ = (x);                    \
-                unsigned ans = 1;                       \
-                while ((_x_ /= 10) != 0)                \
-                        ans++;                          \
-                ans;                                    \
-        })
-
-#define SET_FLAG(v, flag, b) \
-        (v) = (b) ? ((v) | (flag)) : ((v) & ~(flag))
-#define FLAGS_SET(v, flags) \
-        (((v) & (flags)) == (flags))
-
-#define CASE_F(X) case X:
-#define CASE_F_1(CASE, X) CASE_F(X)
-#define CASE_F_2(CASE, X, ...)  CASE(X) CASE_F_1(CASE, __VA_ARGS__)
-#define CASE_F_3(CASE, X, ...)  CASE(X) CASE_F_2(CASE, __VA_ARGS__)
-#define CASE_F_4(CASE, X, ...)  CASE(X) CASE_F_3(CASE, __VA_ARGS__)
-#define CASE_F_5(CASE, X, ...)  CASE(X) CASE_F_4(CASE, __VA_ARGS__)
-#define CASE_F_6(CASE, X, ...)  CASE(X) CASE_F_5(CASE, __VA_ARGS__)
-#define CASE_F_7(CASE, X, ...)  CASE(X) CASE_F_6(CASE, __VA_ARGS__)
-#define CASE_F_8(CASE, X, ...)  CASE(X) CASE_F_7(CASE, __VA_ARGS__)
-#define CASE_F_9(CASE, X, ...)  CASE(X) CASE_F_8(CASE, __VA_ARGS__)
-#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__)
-#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__)
-#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__)
-#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__)
-#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__)
-#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__)
-#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__)
-#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__)
-#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__)
-#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__)
-#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__)
-
-#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME
-#define FOR_EACH_MAKE_CASE(...) \
-        GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \
-                               CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \
-                   (CASE_F,__VA_ARGS__)
-
-#define IN_SET(x, ...)                          \
-        ({                                      \
-                bool _found = false;            \
-                /* If the build breaks in the line below, you need to extend the case macros */ \
-                static _unused_ char _static_assert__macros_need_to_be_extended[20 - sizeof((int[]){__VA_ARGS__})/sizeof(int)]; \
-                switch(x) {                     \
-                FOR_EACH_MAKE_CASE(__VA_ARGS__) \
-                        _found = true;          \
-                        break;                  \
-                default:                        \
-                        break;                  \
-                }                               \
-                _found;                         \
-        })
-
-#define SWAP_TWO(x, y) do {                        \
-                typeof(x) _t = (x);                \
-                (x) = (y);                         \
-                (y) = (_t);                        \
-        } while (false)
-
-/* Define C11 thread_local attribute even on older gcc compiler
- * version */
-#ifndef thread_local
-/*
- * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__
- * see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769
- */
-#if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16))
-#define thread_local _Thread_local
-#else
-#define thread_local __thread
-#endif
-#endif
-
-#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func)                 \
-        static inline void func##p(type *p) {                   \
-                if (*p)                                         \
-                        func(*p);                               \
-        }
-
-#include "log.h"
diff --git a/src/systemd/src/basic/mempool.c b/src/systemd/src/basic/mempool.c
deleted file mode 100644
index dd1b1e5f..00000000
--- a/src/systemd/src/basic/mempool.c
+++ /dev/null
@@ -1,87 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <stdint.h>
-#include <stdlib.h>
-
-#include "macro.h"
-#include "mempool.h"
-#include "util.h"
-
-struct pool {
-        struct pool *next;
-        size_t n_tiles;
-        size_t n_used;
-};
-
-void* mempool_alloc_tile(struct mempool *mp) {
-        size_t i;
-
-        /* When a tile is released we add it to the list and simply
-         * place the next pointer at its offset 0. */
-
-        assert(mp->tile_size >= sizeof(void*));
-        assert(mp->at_least > 0);
-
-        if (mp->freelist) {
-                void *r;
-
-                r = mp->freelist;
-                mp->freelist = * (void**) mp->freelist;
-                return r;
-        }
-
-        if (_unlikely_(!mp->first_pool) ||
-            _unlikely_(mp->first_pool->n_used >= mp->first_pool->n_tiles)) {
-                size_t size, n;
-                struct pool *p;
-
-                n = mp->first_pool ? mp->first_pool->n_tiles : 0;
-                n = MAX(mp->at_least, n * 2);
-                size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*mp->tile_size);
-                n = (size - ALIGN(sizeof(struct pool))) / mp->tile_size;
-
-                p = malloc(size);
-                if (!p)
-                        return NULL;
-
-                p->next = mp->first_pool;
-                p->n_tiles = n;
-                p->n_used = 0;
-
-                mp->first_pool = p;
-        }
-
-        i = mp->first_pool->n_used++;
-
-        return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size;
-}
-
-void* mempool_alloc0_tile(struct mempool *mp) {
-        void *p;
-
-        p = mempool_alloc_tile(mp);
-        if (p)
-                memzero(p, mp->tile_size);
-        return p;
-}
-
-void mempool_free_tile(struct mempool *mp, void *p) {
-        * (void**) p = mp->freelist;
-        mp->freelist = p;
-}
-
-#if VALGRIND
-
-void mempool_drop(struct mempool *mp) {
-        struct pool *p = mp->first_pool;
-        while (p) {
-                struct pool *n;
-                n = p->next;
-                free(p);
-                p = n;
-        }
-}
-
-#endif
diff --git a/src/systemd/src/basic/mempool.h b/src/systemd/src/basic/mempool.h
deleted file mode 100644
index 4098535c..00000000
--- a/src/systemd/src/basic/mempool.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stddef.h>
-
-struct pool;
-
-struct mempool {
-        struct pool *first_pool;
-        void *freelist;
-        size_t tile_size;
-        unsigned at_least;
-};
-
-void* mempool_alloc_tile(struct mempool *mp);
-void* mempool_alloc0_tile(struct mempool *mp);
-void mempool_free_tile(struct mempool *mp, void *p);
-
-#define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \
-static struct mempool pool_name = { \
-        .tile_size = sizeof(tile_type), \
-        .at_least = alloc_at_least, \
-}
-
-#if VALGRIND
-void mempool_drop(struct mempool *mp);
-#endif
diff --git a/src/systemd/src/basic/parse-util.c b/src/systemd/src/basic/parse-util.c
deleted file mode 100644
index b0ccce28..00000000
--- a/src/systemd/src/basic/parse-util.c
+++ /dev/null
@@ -1,749 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <inttypes.h>
-#include <locale.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-
-#include "alloc-util.h"
-#include "errno-list.h"
-#include "extract-word.h"
-#include "locale-util.h"
-#include "macro.h"
-#include "missing.h"
-#include "parse-util.h"
-#include "process-util.h"
-#include "string-util.h"
-
-int parse_boolean(const char *v) {
-        assert(v);
-
-        if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on"))
-                return 1;
-        else if (streq(v, "0") || strcaseeq(v, "no") || strcaseeq(v, "n") || strcaseeq(v, "false") || strcaseeq(v, "f") || strcaseeq(v, "off"))
-                return 0;
-
-        return -EINVAL;
-}
-
-#if 0 /* NM_IGNORED */
-int parse_pid(const char *s, pid_t* ret_pid) {
-        unsigned long ul = 0;
-        pid_t pid;
-        int r;
-
-        assert(s);
-        assert(ret_pid);
-
-        r = safe_atolu(s, &ul);
-        if (r < 0)
-                return r;
-
-        pid = (pid_t) ul;
-
-        if ((unsigned long) pid != ul)
-                return -ERANGE;
-
-        if (!pid_is_valid(pid))
-                return -ERANGE;
-
-        *ret_pid = pid;
-        return 0;
-}
-
-int parse_mode(const char *s, mode_t *ret) {
-        char *x;
-        long l;
-
-        assert(s);
-        assert(ret);
-
-        s += strspn(s, WHITESPACE);
-        if (s[0] == '-')
-                return -ERANGE;
-
-        errno = 0;
-        l = strtol(s, &x, 8);
-        if (errno > 0)
-                return -errno;
-        if (!x || x == s || *x != 0)
-                return -EINVAL;
-        if (l < 0 || l  > 07777)
-                return -ERANGE;
-
-        *ret = (mode_t) l;
-        return 0;
-}
-
-int parse_ifindex(const char *s, int *ret) {
-        int ifi, r;
-
-        r = safe_atoi(s, &ifi);
-        if (r < 0)
-                return r;
-        if (ifi <= 0)
-                return -EINVAL;
-
-        *ret = ifi;
-        return 0;
-}
-
-int parse_mtu(int family, const char *s, uint32_t *ret) {
-        uint64_t u;
-        size_t m;
-        int r;
-
-        r = parse_size(s, 1024, &u);
-        if (r < 0)
-                return r;
-
-        if (u > UINT32_MAX)
-                return -ERANGE;
-
-        if (family == AF_INET6)
-                m = IPV6_MIN_MTU; /* This is 1280 */
-        else
-                m = IPV4_MIN_MTU; /* For all other protocols, including 'unspecified' we assume the IPv4 minimal MTU */
-
-        if (u < m)
-                return -ERANGE;
-
-        *ret = (uint32_t) u;
-        return 0;
-}
-
-int parse_size(const char *t, uint64_t base, uint64_t *size) {
-
-        /* Soo, sometimes we want to parse IEC binary suffixes, and
-         * sometimes SI decimal suffixes. This function can parse
-         * both. Which one is the right way depends on the
-         * context. Wikipedia suggests that SI is customary for
-         * hardware metrics and network speeds, while IEC is
-         * customary for most data sizes used by software and volatile
-         * (RAM) memory. Hence be careful which one you pick!
-         *
-         * In either case we use just K, M, G as suffix, and not Ki,
-         * Mi, Gi or so (as IEC would suggest). That's because that's
-         * frickin' ugly. But this means you really need to make sure
-         * to document which base you are parsing when you use this
-         * call. */
-
-        struct table {
-                const char *suffix;
-                unsigned long long factor;
-        };
-
-        static const struct table iec[] = {
-                { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
-                { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL },
-                { "T", 1024ULL*1024ULL*1024ULL*1024ULL },
-                { "G", 1024ULL*1024ULL*1024ULL },
-                { "M", 1024ULL*1024ULL },
-                { "K", 1024ULL },
-                { "B", 1ULL },
-                { "",  1ULL },
-        };
-
-        static const struct table si[] = {
-                { "E", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL*1000ULL },
-                { "P", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL },
-                { "T", 1000ULL*1000ULL*1000ULL*1000ULL },
-                { "G", 1000ULL*1000ULL*1000ULL },
-                { "M", 1000ULL*1000ULL },
-                { "K", 1000ULL },
-                { "B", 1ULL },
-                { "",  1ULL },
-        };
-
-        const struct table *table;
-        const char *p;
-        unsigned long long r = 0;
-        unsigned n_entries, start_pos = 0;
-
-        assert(t);
-        assert(IN_SET(base, 1000, 1024));
-        assert(size);
-
-        if (base == 1000) {
-                table = si;
-                n_entries = ELEMENTSOF(si);
-        } else {
-                table = iec;
-                n_entries = ELEMENTSOF(iec);
-        }
-
-        p = t;
-        do {
-                unsigned long long l, tmp;
-                double frac = 0;
-                char *e;
-                unsigned i;
-
-                p += strspn(p, WHITESPACE);
-
-                errno = 0;
-                l = strtoull(p, &e, 10);
-                if (errno > 0)
-                        return -errno;
-                if (e == p)
-                        return -EINVAL;
-                if (*p == '-')
-                        return -ERANGE;
-
-                if (*e == '.') {
-                        e++;
-
-                        /* strtoull() itself would accept space/+/- */
-                        if (*e >= '0' && *e <= '9') {
-                                unsigned long long l2;
-                                char *e2;
-
-                                l2 = strtoull(e, &e2, 10);
-                                if (errno > 0)
-                                        return -errno;
-
-                                /* Ignore failure. E.g. 10.M is valid */
-                                frac = l2;
-                                for (; e < e2; e++)
-                                        frac /= 10;
-                        }
-                }
-
-                e += strspn(e, WHITESPACE);
-
-                for (i = start_pos; i < n_entries; i++)
-                        if (startswith(e, table[i].suffix))
-                                break;
-
-                if (i >= n_entries)
-                        return -EINVAL;
-
-                if (l + (frac > 0) > ULLONG_MAX / table[i].factor)
-                        return -ERANGE;
-
-                tmp = l * table[i].factor + (unsigned long long) (frac * table[i].factor);
-                if (tmp > ULLONG_MAX - r)
-                        return -ERANGE;
-
-                r += tmp;
-                if ((unsigned long long) (uint64_t) r != r)
-                        return -ERANGE;
-
-                p = e + strlen(table[i].suffix);
-
-                start_pos = i + 1;
-
-        } while (*p);
-
-        *size = r;
-
-        return 0;
-}
-
-int parse_range(const char *t, unsigned *lower, unsigned *upper) {
-        _cleanup_free_ char *word = NULL;
-        unsigned l, u;
-        int r;
-
-        assert(lower);
-        assert(upper);
-
-        /* Extract the lower bound. */
-        r = extract_first_word(&t, &word, "-", EXTRACT_DONT_COALESCE_SEPARATORS);
-        if (r < 0)
-                return r;
-        if (r == 0)
-                return -EINVAL;
-
-        r = safe_atou(word, &l);
-        if (r < 0)
-                return r;
-
-        /* Check for the upper bound and extract it if needed */
-        if (!t)
-                /* Single number with no dashes. */
-                u = l;
-        else if (!*t)
-                /* Trailing dash is an error. */
-                return -EINVAL;
-        else {
-                r = safe_atou(t, &u);
-                if (r < 0)
-                        return r;
-        }
-
-        *lower = l;
-        *upper = u;
-        return 0;
-}
-
-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;
-
-        /* This only does IEC units so far */
-
-        static const struct {
-                const char *suffix;
-                uint64_t factor;
-        } table[] = {
-                { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
-                { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
-                { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
-                { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
-                { "M", UINT64_C(1024)*UINT64_C(1024) },
-                { "K", UINT64_C(1024) },
-        };
-
-        if (t == (uint64_t) -1)
-                return NULL;
-
-        for (i = 0; i < ELEMENTSOF(table); i++) {
-
-                if (t >= table[i].factor) {
-                        snprintf(buf, l,
-                                 "%" PRIu64 ".%" PRIu64 "%s",
-                                 t / table[i].factor,
-                                 ((t*UINT64_C(10)) / table[i].factor) % UINT64_C(10),
-                                 table[i].suffix);
-
-                        goto finish;
-                }
-        }
-
-        snprintf(buf, l, "%" PRIu64 "B", t);
-
-finish:
-        buf[l-1] = 0;
-        return buf;
-
-}
-#endif /* NM_IGNORED */
-
-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
-         * error. We want a clean error, hence let's look for the "-"
-         * prefix on our own, and generate an error. But let's do so
-         * only after strtoul() validated that the string is clean
-         * otherwise, so that we return EINVAL preferably over
-         * ERANGE. */
-
-        s += strspn(s, WHITESPACE);
-
-        errno = 0;
-        l = strtoul(s, &x, base);
-        if (errno > 0)
-                return -errno;
-        if (!x || x == s || *x != 0)
-                return -EINVAL;
-        if (s[0] == '-')
-                return -ERANGE;
-        if ((unsigned long) (unsigned) l != l)
-                return -ERANGE;
-
-        *ret_u = (unsigned) l;
-        return 0;
-}
-
-int safe_atoi(const char *s, int *ret_i) {
-        char *x = NULL;
-        long l;
-
-        assert(s);
-        assert(ret_i);
-
-        errno = 0;
-        l = strtol(s, &x, 0);
-        if (errno > 0)
-                return -errno;
-        if (!x || x == s || *x != 0)
-                return -EINVAL;
-        if ((long) (int) l != l)
-                return -ERANGE;
-
-        *ret_i = (int) l;
-        return 0;
-}
-
-int safe_atollu(const char *s, long long unsigned *ret_llu) {
-        char *x = NULL;
-        unsigned long long l;
-
-        assert(s);
-        assert(ret_llu);
-
-        s += strspn(s, WHITESPACE);
-
-        errno = 0;
-        l = strtoull(s, &x, 0);
-        if (errno > 0)
-                return -errno;
-        if (!x || x == s || *x != 0)
-                return -EINVAL;
-        if (*s == '-')
-                return -ERANGE;
-
-        *ret_llu = l;
-        return 0;
-}
-
-int safe_atolli(const char *s, long long int *ret_lli) {
-        char *x = NULL;
-        long long l;
-
-        assert(s);
-        assert(ret_lli);
-
-        errno = 0;
-        l = strtoll(s, &x, 0);
-        if (errno > 0)
-                return -errno;
-        if (!x || x == s || *x != 0)
-                return -EINVAL;
-
-        *ret_lli = l;
-        return 0;
-}
-
-int safe_atou8(const char *s, uint8_t *ret) {
-        char *x = NULL;
-        unsigned long l;
-
-        assert(s);
-        assert(ret);
-
-        s += strspn(s, WHITESPACE);
-
-        errno = 0;
-        l = strtoul(s, &x, 0);
-        if (errno > 0)
-                return -errno;
-        if (!x || x == s || *x != 0)
-                return -EINVAL;
-        if (s[0] == '-')
-                return -ERANGE;
-        if ((unsigned long) (uint8_t) l != l)
-                return -ERANGE;
-
-        *ret = (uint8_t) l;
-        return 0;
-}
-
-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, base);
-        if (errno > 0)
-                return -errno;
-        if (!x || x == s || *x != 0)
-                return -EINVAL;
-        if (s[0] == '-')
-                return -ERANGE;
-        if ((unsigned long) (uint16_t) l != l)
-                return -ERANGE;
-
-        *ret = (uint16_t) l;
-        return 0;
-}
-
-int safe_atoi16(const char *s, int16_t *ret) {
-        char *x = NULL;
-        long l;
-
-        assert(s);
-        assert(ret);
-
-        errno = 0;
-        l = strtol(s, &x, 0);
-        if (errno > 0)
-                return -errno;
-        if (!x || x == s || *x != 0)
-                return -EINVAL;
-        if ((long) (int16_t) l != l)
-                return -ERANGE;
-
-        *ret = (int16_t) l;
-        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;
-
-        assert(s);
-        assert(ret_d);
-
-        loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
-        if (loc == (locale_t) 0)
-                return -errno;
-
-        errno = 0;
-        d = strtod_l(s, &x, loc);
-        if (errno > 0)
-                return -errno;
-        if (!x || x == s || *x != 0)
-                return -EINVAL;
-
-        *ret_d = (double) d;
-        return 0;
-}
-
-int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) {
-        size_t i;
-        unsigned val = 0;
-        const char *s;
-
-        s = *p;
-
-        /* accept any number of digits, strtoull is limted to 19 */
-        for (i=0; i < digits; i++,s++) {
-                if (*s < '0' || *s > '9') {
-                        if (i == 0)
-                                return -EINVAL;
-
-                        /* too few digits, pad with 0 */
-                        for (; i < digits; i++)
-                                val *= 10;
-
-                        break;
-                }
-
-                val *= 10;
-                val += *s - '0';
-        }
-
-        /* maybe round up */
-        if (*s >= '5' && *s <= '9')
-                val++;
-
-        s += strspn(s, DIGITS);
-
-        *p = s;
-        *res = val;
-
-        return 0;
-}
-
-int parse_percent_unbounded(const char *p) {
-        const char *pc, *n;
-        int r, v;
-
-        pc = endswith(p, "%");
-        if (!pc)
-                return -EINVAL;
-
-        n = strndupa(p, pc - p);
-        r = safe_atoi(n, &v);
-        if (r < 0)
-                return r;
-        if (v < 0)
-                return -ERANGE;
-
-        return v;
-}
-
-int parse_percent(const char *p) {
-        int v;
-
-        v = parse_percent_unbounded(p);
-        if (v > 100)
-                return -ERANGE;
-
-        return v;
-}
-
-int parse_permille_unbounded(const char *p) {
-        const char *pc, *pm, *dot, *n;
-        int r, q, v;
-
-        pm = endswith(p, "‰");
-        if (pm) {
-                n = strndupa(p, pm - p);
-                r = safe_atoi(n, &v);
-                if (r < 0)
-                        return r;
-                if (v < 0)
-                        return -ERANGE;
-        } else {
-                pc = endswith(p, "%");
-                if (!pc)
-                        return -EINVAL;
-
-                dot = memchr(p, '.', pc - p);
-                if (dot) {
-                        if (dot + 2 != pc)
-                                return -EINVAL;
-                        if (dot[1] < '0' || dot[1] > '9')
-                                return -EINVAL;
-                        q = dot[1] - '0';
-                        n = strndupa(p, dot - p);
-                } else {
-                        q = 0;
-                        n = strndupa(p, pc - p);
-                }
-                r = safe_atoi(n, &v);
-                if (r < 0)
-                        return r;
-                if (v < 0)
-                        return -ERANGE;
-                if (v > (INT_MAX - q) / 10)
-                        return -ERANGE;
-
-                v = v * 10 + q;
-        }
-
-        return v;
-}
-
-int parse_permille(const char *p) {
-        int v;
-
-        v = parse_permille_unbounded(p);
-        if (v > 1000)
-                return -ERANGE;
-
-        return v;
-}
-
-int parse_nice(const char *p, int *ret) {
-        int n, r;
-
-        r = safe_atoi(p, &n);
-        if (r < 0)
-                return r;
-
-        if (!nice_is_valid(n))
-                return -ERANGE;
-
-        *ret = n;
-        return 0;
-}
-
-int parse_ip_port(const char *s, uint16_t *ret) {
-        uint16_t l;
-        int r;
-
-        r = safe_atou16(s, &l);
-        if (r < 0)
-                return r;
-
-        if (l == 0)
-                return -EINVAL;
-
-        *ret = (uint16_t) l;
-
-        return 0;
-}
-
-int parse_dev(const char *s, dev_t *ret) {
-        unsigned x, y;
-        dev_t d;
-
-        if (sscanf(s, "%u:%u", &x, &y) != 2)
-                return -EINVAL;
-
-        d = makedev(x, y);
-        if ((unsigned) major(d) != x || (unsigned) minor(d) != y)
-                return -EINVAL;
-
-        *ret = d;
-        return 0;
-}
-
-int parse_oom_score_adjust(const char *s, int *ret) {
-        int r, v;
-
-        assert(s);
-        assert(ret);
-
-        r = safe_atoi(s, &v);
-        if (r < 0)
-                return r;
-
-        if (v < OOM_SCORE_ADJ_MIN || v > OOM_SCORE_ADJ_MAX)
-                return -ERANGE;
-
-        *ret = v;
-        return 0;
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/parse-util.h b/src/systemd/src/basic/parse-util.h
deleted file mode 100644
index f3267f4c..00000000
--- a/src/systemd/src/basic/parse-util.h
+++ /dev/null
@@ -1,119 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <inttypes.h>
-#include <limits.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/types.h>
-
-#include "macro.h"
-
-#define MODE_INVALID ((mode_t) -1)
-
-int parse_boolean(const char *v) _pure_;
-int parse_dev(const char *s, dev_t *ret);
-int parse_pid(const char *s, pid_t* ret_pid);
-int parse_mode(const char *s, mode_t *ret);
-int parse_ifindex(const char *s, int *ret);
-int parse_mtu(int family, const char *s, uint32_t *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_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_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) {
-        assert_cc(sizeof(uint32_t) == sizeof(unsigned));
-        return safe_atou(s, (unsigned*) ret_u);
-}
-
-static inline int safe_atoi32(const char *s, int32_t *ret_i) {
-        assert_cc(sizeof(int32_t) == sizeof(int));
-        return safe_atoi(s, (int*) ret_i);
-}
-
-static inline int safe_atou64(const char *s, uint64_t *ret_u) {
-        assert_cc(sizeof(uint64_t) == sizeof(unsigned long long));
-        return safe_atollu(s, (unsigned long long*) ret_u);
-}
-
-static inline int safe_atoi64(const char *s, int64_t *ret_i) {
-        assert_cc(sizeof(int64_t) == sizeof(long long int));
-        return safe_atolli(s, (long long int*) ret_i);
-}
-
-#if LONG_MAX == INT_MAX
-static inline int safe_atolu(const char *s, unsigned long *ret_u) {
-        assert_cc(sizeof(unsigned long) == sizeof(unsigned));
-        return safe_atou(s, (unsigned*) ret_u);
-}
-static inline int safe_atoli(const char *s, long int *ret_u) {
-        assert_cc(sizeof(long int) == sizeof(int));
-        return safe_atoi(s, (int*) ret_u);
-}
-#else
-static inline int safe_atolu(const char *s, unsigned long *ret_u) {
-        assert_cc(sizeof(unsigned long) == sizeof(unsigned long long));
-        return safe_atollu(s, (unsigned long long*) ret_u);
-}
-static inline int safe_atoli(const char *s, long int *ret_u) {
-        assert_cc(sizeof(long int) == sizeof(long long int));
-        return safe_atolli(s, (long long int*) ret_u);
-}
-#endif
-
-#if SIZE_MAX == UINT_MAX
-static inline int safe_atozu(const char *s, size_t *ret_u) {
-        assert_cc(sizeof(size_t) == sizeof(unsigned));
-        return safe_atou(s, (unsigned *) ret_u);
-}
-#else
-static inline int safe_atozu(const char *s, size_t *ret_u) {
-        assert_cc(sizeof(size_t) == sizeof(long unsigned));
-        return safe_atolu(s, ret_u);
-}
-#endif
-
-int safe_atod(const char *s, double *ret_d);
-
-int parse_fractional_part_u(const char **s, size_t digits, unsigned *res);
-
-int parse_percent_unbounded(const char *p);
-int parse_percent(const char *p);
-
-int parse_permille_unbounded(const char *p);
-int parse_permille(const char *p);
-
-int parse_nice(const char *p, int *ret);
-
-int parse_ip_port(const char *s, uint16_t *ret);
-
-int parse_oom_score_adjust(const char *s, int *ret);
diff --git a/src/systemd/src/basic/path-util.c b/src/systemd/src/basic/path-util.c
deleted file mode 100644
index 3656a011..00000000
--- a/src/systemd/src/basic/path-util.c
+++ /dev/null
@@ -1,1060 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-/* When we include libgen.h because we need dirname() we immediately
- * undefine basename() since libgen.h defines it as a macro to the
- * POSIX version which is really broken. We prefer GNU basename(). */
-#include <libgen.h>
-#undef basename
-
-#include "alloc-util.h"
-#include "extract-word.h"
-#include "fs-util.h"
-#include "glob-util.h"
-#include "log.h"
-#include "macro.h"
-#include "missing.h"
-#include "parse-util.h"
-#include "path-util.h"
-#include "stat-util.h"
-#include "string-util.h"
-#include "strv.h"
-#include "time-util.h"
-#include "utf8.h"
-
-bool path_is_absolute(const char *p) {
-        return p[0] == '/';
-}
-
-#if 0 /* NM_IGNORED */
-bool is_path(const char *p) {
-        return !!strchr(p, '/');
-}
-
-int path_split_and_make_absolute(const char *p, char ***ret) {
-        char **l;
-        int r;
-
-        assert(p);
-        assert(ret);
-
-        l = strv_split(p, ":");
-        if (!l)
-                return -ENOMEM;
-
-        r = path_strv_make_absolute_cwd(l);
-        if (r < 0) {
-                strv_free(l);
-                return r;
-        }
-
-        *ret = l;
-        return r;
-}
-
-char *path_make_absolute(const char *p, const char *prefix) {
-        assert(p);
-
-        /* Makes every item in the list an absolute path by prepending
-         * the prefix, if specified and necessary */
-
-        if (path_is_absolute(p) || isempty(prefix))
-                return strdup(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);
-
-        /* Similar to path_make_absolute(), but prefixes with the
-         * current working directory. */
-
-        if (path_is_absolute(p))
-                c = strdup(p);
-        else {
-                _cleanup_free_ char *cwd = NULL;
-
-                r = safe_getcwd(&cwd);
-                if (r < 0)
-                        return r;
-
-                c = path_join(NULL, cwd, p);
-        }
-        if (!c)
-                return -ENOMEM;
-
-        *ret = c;
-        return 0;
-}
-
-int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
-        char *f, *t, *r, *p;
-        unsigned n_parents = 0;
-
-        assert(from_dir);
-        assert(to_path);
-        assert(_r);
-
-        /* Strips the common part, and adds ".." elements as necessary. */
-
-        if (!path_is_absolute(from_dir) || !path_is_absolute(to_path))
-                return -EINVAL;
-
-        f = strdupa(from_dir);
-        t = strdupa(to_path);
-
-        path_simplify(f, true);
-        path_simplify(t, true);
-
-        /* Skip the common part. */
-        for (;;) {
-                size_t a, b;
-
-                f += *f == '/';
-                t += *t == '/';
-
-                if (!*f) {
-                        if (!*t)
-                                /* from_dir equals to_path. */
-                                r = strdup(".");
-                        else
-                                /* from_dir is a parent directory of to_path. */
-                                r = strdup(t);
-                        if (!r)
-                                return -ENOMEM;
-
-                        *_r = r;
-                        return 0;
-                }
-
-                if (!*t)
-                        break;
-
-                a = strcspn(f, "/");
-                b = strcspn(t, "/");
-
-                if (a != b || memcmp(f, t, a) != 0)
-                        break;
-
-                f += a;
-                t += b;
-        }
-
-        /* If we're here, then "from_dir" has one or more elements that need to
-         * be replaced with "..". */
-
-        /* Count the number of necessary ".." elements. */
-        for (; *f;) {
-                size_t w;
-
-                w = strcspn(f, "/");
-
-                /* If this includes ".." we can't do a simple series of "..", refuse */
-                if (w == 2 && f[0] == '.' && f[1] == '.')
-                        return -EINVAL;
-
-                /* Count number of elements */
-                n_parents++;
-
-                f += w;
-                f += *f == '/';
-        }
-
-        r = new(char, n_parents * 3 + strlen(t) + 1);
-        if (!r)
-                return -ENOMEM;
-
-        for (p = r; n_parents > 0; n_parents--)
-                p = mempcpy(p, "../", 3);
-
-        if (*t)
-                strcpy(p, t);
-        else
-                /* Remove trailing slash */
-                *(--p) = 0;
-
-        *_r = r;
-        return 0;
-}
-
-int path_strv_make_absolute_cwd(char **l) {
-        char **s;
-        int r;
-
-        /* Goes through every item in the string list and makes it
-         * absolute. This works in place and won't rollback any
-         * changes on failure. */
-
-        STRV_FOREACH(s, l) {
-                char *t;
-
-                r = path_make_absolute_cwd(*s, &t);
-                if (r < 0)
-                        return r;
-
-                path_simplify(t, false);
-                free_and_replace(*s, t);
-        }
-
-        return 0;
-}
-
-char **path_strv_resolve(char **l, const char *root) {
-        char **s;
-        unsigned k = 0;
-        bool enomem = false;
-        int r;
-
-        if (strv_isempty(l))
-                return l;
-
-        /* Goes through every item in the string list and canonicalize
-         * the path. This works in place and won't rollback any
-         * changes on failure. */
-
-        STRV_FOREACH(s, l) {
-                _cleanup_free_ char *orig = NULL;
-                char *t, *u;
-
-                if (!path_is_absolute(*s)) {
-                        free(*s);
-                        continue;
-                }
-
-                if (root) {
-                        orig = *s;
-                        t = prefix_root(root, orig);
-                        if (!t) {
-                                enomem = true;
-                                continue;
-                        }
-                } else
-                        t = *s;
-
-                r = chase_symlinks(t, root, 0, &u);
-                if (r == -ENOENT) {
-                        if (root) {
-                                u = TAKE_PTR(orig);
-                                free(t);
-                        } else
-                                u = t;
-                } else if (r < 0) {
-                        free(t);
-
-                        if (r == -ENOMEM)
-                                enomem = true;
-
-                        continue;
-                } else if (root) {
-                        char *x;
-
-                        free(t);
-                        x = path_startswith(u, root);
-                        if (x) {
-                                /* restore the slash if it was lost */
-                                if (!startswith(x, "/"))
-                                        *(--x) = '/';
-
-                                t = strdup(x);
-                                free(u);
-                                if (!t) {
-                                        enomem = true;
-                                        continue;
-                                }
-                                u = t;
-                        } else {
-                                /* canonicalized path goes outside of
-                                 * prefix, keep the original path instead */
-                                free_and_replace(u, orig);
-                        }
-                } else
-                        free(t);
-
-                l[k++] = u;
-        }
-
-        l[k] = NULL;
-
-        if (enomem)
-                return NULL;
-
-        return l;
-}
-
-char **path_strv_resolve_uniq(char **l, const char *root) {
-
-        if (strv_isempty(l))
-                return l;
-
-        if (!path_strv_resolve(l, root))
-                return NULL;
-
-        return strv_uniq(l);
-}
-#endif /* NM_IGNORED */
-
-char *path_simplify(char *path, bool kill_dots) {
-        char *f, *t;
-        bool slash = false, ignore_slash = false, absolute;
-
-        assert(path);
-
-        /* Removes redundant inner and trailing slashes. Also removes unnecessary dots
-         * if kill_dots is true. Modifies the passed string in-place.
-         *
-         * ///foo//./bar/.   becomes /foo/./bar/.  (if kill_dots is false)
-         * ///foo//./bar/.   becomes /foo/bar      (if kill_dots is true)
-         * .//./foo//./bar/. becomes ./foo/bar     (if kill_dots is false)
-         * .//./foo//./bar/. becomes foo/bar       (if kill_dots is true)
-         */
-
-        absolute = path_is_absolute(path);
-
-        f = path;
-        if (kill_dots && *f == '.' && IN_SET(f[1], 0, '/')) {
-                ignore_slash = true;
-                f++;
-        }
-
-        for (t = path; *f; f++) {
-
-                if (*f == '/') {
-                        slash = true;
-                        continue;
-                }
-
-                if (slash) {
-                        if (kill_dots && *f == '.' && IN_SET(f[1], 0, '/'))
-                                continue;
-
-                        slash = false;
-                        if (ignore_slash)
-                                ignore_slash = false;
-                        else
-                                *(t++) = '/';
-                }
-
-                *(t++) = *f;
-        }
-
-        /* Special rule, if we are talking of the root directory, a trailing slash is good */
-        if (absolute && t == path)
-                *(t++) = '/';
-
-        *t = 0;
-        return path;
-}
-
-char* path_startswith(const char *path, const char *prefix) {
-        assert(path);
-        assert(prefix);
-
-        /* Returns a pointer to the start of the first component after the parts matched by
-         * the prefix, iff
-         * - both paths are absolute or both paths are relative,
-         * and
-         * - each component in prefix in turn matches a component in path at the same position.
-         * An empty string will be returned when the prefix and path are equivalent.
-         *
-         * Returns NULL otherwise.
-         */
-
-        if ((path[0] == '/') != (prefix[0] == '/'))
-                return NULL;
-
-        for (;;) {
-                size_t a, b;
-
-                path += strspn(path, "/");
-                prefix += strspn(prefix, "/");
-
-                if (*prefix == 0)
-                        return (char*) path;
-
-                if (*path == 0)
-                        return NULL;
-
-                a = strcspn(path, "/");
-                b = strcspn(prefix, "/");
-
-                if (a != b)
-                        return NULL;
-
-                if (memcmp(path, prefix, a) != 0)
-                        return NULL;
-
-                path += a;
-                prefix += b;
-        }
-}
-
-int path_compare(const char *a, const char *b) {
-        int d;
-
-        assert(a);
-        assert(b);
-
-        /* A relative path and an abolute path must not compare as equal.
-         * Which one is sorted before the other does not really matter.
-         * Here a relative path is ordered before an absolute path. */
-        d = (a[0] == '/') - (b[0] == '/');
-        if (d != 0)
-                return d;
-
-        for (;;) {
-                size_t j, k;
-
-                a += strspn(a, "/");
-                b += strspn(b, "/");
-
-                if (*a == 0 && *b == 0)
-                        return 0;
-
-                /* Order prefixes first: "/foo" before "/foo/bar" */
-                if (*a == 0)
-                        return -1;
-                if (*b == 0)
-                        return 1;
-
-                j = strcspn(a, "/");
-                k = strcspn(b, "/");
-
-                /* Alphabetical sort: "/foo/aaa" before "/foo/b" */
-                d = memcmp(a, b, MIN(j, k));
-                if (d != 0)
-                        return (d > 0) - (d < 0); /* sign of d */
-
-                /* Sort "/foo/a" before "/foo/aaa" */
-                d = (j > k) - (j < k);  /* sign of (j - k) */
-                if (d != 0)
-                        return d;
-
-                a += j;
-                b += k;
-        }
-}
-
-bool path_equal(const char *a, const char *b) {
-        return path_compare(a, b) == 0;
-}
-
-#if 0 /* NM_IGNORED */
-bool path_equal_or_files_same(const char *a, const char *b, int flags) {
-        return path_equal(a, b) || files_same(a, b, flags) > 0;
-}
-
-char* path_join(const char *root, const char *path, const char *rest) {
-        assert(path);
-
-        if (!isempty(root))
-                return strjoin(root, endswith(root, "/") ? "" : "/",
-                               path[0] == '/' ? path+1 : path,
-                               rest ? (endswith(path, "/") ? "" : "/") : NULL,
-                               rest && rest[0] == '/' ? rest+1 : rest);
-        else
-                return strjoin(path,
-                               rest ? (endswith(path, "/") ? "" : "/") : NULL,
-                               rest && rest[0] == '/' ? rest+1 : rest);
-}
-
-int find_binary(const char *name, char **ret) {
-        int last_error, r;
-        const char *p;
-
-        assert(name);
-
-        if (is_path(name)) {
-                if (access(name, X_OK) < 0)
-                        return -errno;
-
-                if (ret) {
-                        r = path_make_absolute_cwd(name, ret);
-                        if (r < 0)
-                                return r;
-                }
-
-                return 0;
-        }
-
-        /**
-         * Plain getenv, not secure_getenv, because we want
-         * to actually allow the user to pick the binary.
-         */
-        p = getenv("PATH");
-        if (!p)
-                p = DEFAULT_PATH;
-
-        last_error = -ENOENT;
-
-        for (;;) {
-                _cleanup_free_ char *j = NULL, *element = NULL;
-
-                r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
-
-                if (!path_is_absolute(element))
-                        continue;
-
-                j = strjoin(element, "/", name);
-                if (!j)
-                        return -ENOMEM;
-
-                if (access(j, X_OK) >= 0) {
-                        /* Found it! */
-
-                        if (ret) {
-                                *ret = path_simplify(j, false);
-                                j = NULL;
-                        }
-
-                        return 0;
-                }
-
-                last_error = -errno;
-        }
-
-        return last_error;
-}
-
-bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) {
-        bool changed = false;
-        const char* const* i;
-
-        assert(timestamp);
-
-        if (!paths)
-                return false;
-
-        STRV_FOREACH(i, paths) {
-                struct stat stats;
-                usec_t u;
-
-                if (stat(*i, &stats) < 0)
-                        continue;
-
-                u = timespec_load(&stats.st_mtim);
-
-                /* first check */
-                if (*timestamp >= u)
-                        continue;
-
-                log_debug("timestamp of '%s' changed", *i);
-
-                /* update timestamp */
-                if (update) {
-                        *timestamp = u;
-                        changed = true;
-                } else
-                        return true;
-        }
-
-        return changed;
-}
-
-static int binary_is_good(const char *binary) {
-        _cleanup_free_ char *p = NULL, *d = NULL;
-        int r;
-
-        r = find_binary(binary, &p);
-        if (r == -ENOENT)
-                return 0;
-        if (r < 0)
-                return r;
-
-        /* An fsck that is linked to /bin/true is a non-existent
-         * fsck */
-
-        r = readlink_malloc(p, &d);
-        if (r == -EINVAL) /* not a symlink */
-                return 1;
-        if (r < 0)
-                return r;
-
-        return !PATH_IN_SET(d, "true"
-                               "/bin/true",
-                               "/usr/bin/true",
-                               "/dev/null");
-}
-
-int fsck_exists(const char *fstype) {
-        const char *checker;
-
-        assert(fstype);
-
-        if (streq(fstype, "auto"))
-                return -EINVAL;
-
-        checker = strjoina("fsck.", fstype);
-        return binary_is_good(checker);
-}
-
-int mkfs_exists(const char *fstype) {
-        const char *mkfs;
-
-        assert(fstype);
-
-        if (streq(fstype, "auto"))
-                return -EINVAL;
-
-        mkfs = strjoina("mkfs.", fstype);
-        return binary_is_good(mkfs);
-}
-
-char *prefix_root(const char *root, const char *path) {
-        char *n, *p;
-        size_t l;
-
-        /* If root is passed, prefixes path with it. Otherwise returns
-         * it as is. */
-
-        assert(path);
-
-        /* First, drop duplicate prefixing slashes from the path */
-        while (path[0] == '/' && path[1] == '/')
-                path++;
-
-        if (empty_or_root(root))
-                return strdup(path);
-
-        l = strlen(root) + 1 + strlen(path) + 1;
-
-        n = new(char, l);
-        if (!n)
-                return NULL;
-
-        p = stpcpy(n, root);
-
-        while (p > n && p[-1] == '/')
-                p--;
-
-        if (path[0] != '/')
-                *(p++) = '/';
-
-        strcpy(p, path);
-        return n;
-}
-
-int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) {
-        char *p;
-        int r;
-
-        /*
-         * This function is intended to be used in command line
-         * parsers, to handle paths that are passed in. It makes the
-         * path absolute, and reduces it to NULL if omitted or
-         * root (the latter optionally).
-         *
-         * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON
-         * SUCCESS! Hence, do not pass in uninitialized pointers.
-         */
-
-        if (isempty(path)) {
-                *arg = mfree(*arg);
-                return 0;
-        }
-
-        r = path_make_absolute_cwd(path, &p);
-        if (r < 0)
-                return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path);
-
-        path_simplify(p, false);
-        if (suppress_root && empty_or_root(p))
-                p = mfree(p);
-
-        free_and_replace(*arg, p);
-
-        return 0;
-}
-
-char* dirname_malloc(const char *path) {
-        char *d, *dir, *dir2;
-
-        assert(path);
-
-        d = strdup(path);
-        if (!d)
-                return NULL;
-
-        dir = dirname(d);
-        assert(dir);
-
-        if (dir == d)
-                return d;
-
-        dir2 = strdup(dir);
-        free(d);
-
-        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/
-         *    x → x
-         *    x/ → x/
-         *    /y → y
-         *    /y/ → y/
-         *    / → /
-         *    // → /
-         *    /foo/a → a
-         *    /foo/a/ → a/
-         *
-         *    Also, the empty string is mapped to itself.
-         *
-         * 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) {
-        const char *e;
-
-        if (isempty(p))
-                return false;
-
-        if (dot_or_dot_dot(p))
-                return false;
-
-        e = strchrnul(p, '/');
-        if (*e != 0)
-                return false;
-
-        if (e - p > FILENAME_MAX)
-                return false;
-
-        return true;
-}
-
-bool path_is_normalized(const char *p) {
-
-        if (isempty(p))
-                return false;
-
-        if (dot_or_dot_dot(p))
-                return false;
-
-        if (startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../"))
-                return false;
-
-        if (strlen(p)+1 > PATH_MAX)
-                return false;
-
-        if (startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./"))
-                return false;
-
-        if (strstr(p, "//"))
-                return false;
-
-        return true;
-}
-
-#if 0 /* NM_IGNORED */
-char *file_in_same_dir(const char *path, const char *filename) {
-        char *e, *ret;
-        size_t k;
-
-        assert(path);
-        assert(filename);
-
-        /* This removes the last component of path and appends
-         * filename, unless the latter is absolute anyway or the
-         * former isn't */
-
-        if (path_is_absolute(filename))
-                return strdup(filename);
-
-        e = strrchr(path, '/');
-        if (!e)
-                return strdup(filename);
-
-        k = strlen(filename);
-        ret = new(char, (e + 1 - path) + k + 1);
-        if (!ret)
-                return NULL;
-
-        memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1);
-        return ret;
-}
-
-bool hidden_or_backup_file(const char *filename) {
-        const char *p;
-
-        assert(filename);
-
-        if (filename[0] == '.' ||
-            streq(filename, "lost+found") ||
-            streq(filename, "aquota.user") ||
-            streq(filename, "aquota.group") ||
-            endswith(filename, "~"))
-                return true;
-
-        p = strrchr(filename, '.');
-        if (!p)
-                return false;
-
-        /* Please, let's not add more entries to the list below. If external projects think it's a good idea to come up
-         * with always new suffixes and that everybody else should just adjust to that, then it really should be on
-         * them. Hence, in future, let's not add any more entries. Instead, let's ask those packages to instead adopt
-         * one of the generic suffixes/prefixes for hidden files or backups, possibly augmented with an additional
-         * string. Specifically: there's now:
-         *
-         *    The generic suffixes "~" and ".bak" for backup files
-         *    The generic prefix "." for hidden files
-         *
-         * Thus, if a new package manager "foopkg" wants its own set of ".foopkg-new", ".foopkg-old", ".foopkg-dist"
-         * or so registered, let's refuse that and ask them to use ".foopkg.new", ".foopkg.old" or ".foopkg~" instead.
-         */
-
-        return STR_IN_SET(p + 1,
-                          "rpmnew",
-                          "rpmsave",
-                          "rpmorig",
-                          "dpkg-old",
-                          "dpkg-new",
-                          "dpkg-tmp",
-                          "dpkg-dist",
-                          "dpkg-bak",
-                          "dpkg-backup",
-                          "dpkg-remove",
-                          "ucf-new",
-                          "ucf-old",
-                          "ucf-dist",
-                          "swp",
-                          "bak",
-                          "old",
-                          "new");
-}
-
-bool is_device_path(const char *path) {
-
-        /* Returns true on paths that likely refer to a device, either by path in sysfs or to something in /dev */
-
-        return PATH_STARTSWITH_SET(path, "/dev/", "/sys/");
-}
-
-bool valid_device_node_path(const char *path) {
-
-        /* Some superficial checks whether the specified path is a valid device node path, all without looking at the
-         * actual device node. */
-
-        if (!PATH_STARTSWITH_SET(path, "/dev/", "/run/systemd/inaccessible/"))
-                return false;
-
-        if (endswith(path, "/")) /* can't be a device node if it ends in a slash */
-                return false;
-
-        return path_is_normalized(path);
-}
-
-bool valid_device_allow_pattern(const char *path) {
-        assert(path);
-
-        /* Like valid_device_node_path(), but also allows full-subsystem expressions, like DeviceAllow= and DeviceDeny=
-         * accept it */
-
-        if (startswith(path, "block-") ||
-            startswith(path, "char-"))
-                return true;
-
-        return valid_device_node_path(path);
-}
-
-int systemd_installation_has_version(const char *root, unsigned minimal_version) {
-        const char *pattern;
-        int r;
-
-        /* Try to guess if systemd installation is later than the specified version. This
-         * is hacky and likely to yield false negatives, particularly if the installation
-         * is non-standard. False positives should be relatively rare.
-         */
-
-        NULSTR_FOREACH(pattern,
-                       /* /lib works for systems without usr-merge, and for systems with a sane
-                        * usr-merge, where /lib is a symlink to /usr/lib. /usr/lib is necessary
-                        * for Gentoo which does a merge without making /lib a symlink.
-                        */
-                       "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;
-                char *c, **name;
-
-                path = prefix_root(root, pattern);
-                if (!path)
-                        return -ENOMEM;
-
-                r = glob_extend(&names, path);
-                if (r == -ENOENT)
-                        continue;
-                if (r < 0)
-                        return r;
-
-                assert_se(c = endswith(path, "*.so"));
-                *c = '\0'; /* truncate the glob part */
-
-                STRV_FOREACH(name, names) {
-                        /* This is most likely to run only once, hence let's not optimize anything. */
-                        char *t, *t2;
-                        unsigned version;
-
-                        t = startswith(*name, path);
-                        if (!t)
-                                continue;
-
-                        t2 = endswith(t, ".so");
-                        if (!t2)
-                                continue;
-
-                        t2[0] = '\0'; /* truncate the suffix */
-
-                        r = safe_atou(t, &version);
-                        if (r < 0) {
-                                log_debug_errno(r, "Found libsystemd shared at \"%s.so\", but failed to parse version: %m", *name);
-                                continue;
-                        }
-
-                        log_debug("Found libsystemd shared at \"%s.so\", version %u (%s).",
-                                  *name, version,
-                                  version >= minimal_version ? "OK" : "too old");
-                        if (version >= minimal_version)
-                                return true;
-                }
-        }
-
-        return false;
-}
-#endif /* NM_IGNORED */
-
-bool dot_or_dot_dot(const char *path) {
-        if (!path)
-                return false;
-        if (path[0] != '.')
-                return false;
-        if (path[1] == 0)
-                return true;
-        if (path[1] != '.')
-                return false;
-
-        return path[2] == 0;
-}
-
-#if 0 /* NM_IGNORED */
-bool empty_or_root(const char *root) {
-
-        /* For operations relative to some root directory, returns true if the specified root directory is redundant,
-         * i.e. either / or NULL or the empty string or any equivalent. */
-
-        if (!root)
-                return true;
-
-        return root[strspn(root, "/")] == 0;
-}
-
-int path_simplify_and_warn(
-                char *path,
-                unsigned flag,
-                const char *unit,
-                const char *filename,
-                unsigned line,
-                const char *lvalue) {
-
-        bool absolute, fatal = flag & PATH_CHECK_FATAL;
-
-        assert(!FLAGS_SET(flag, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE));
-
-        if (!utf8_is_valid(path)) {
-                log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, path);
-                return -EINVAL;
-        }
-
-        if (flag & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) {
-                absolute = path_is_absolute(path);
-
-                if (!absolute && (flag & PATH_CHECK_ABSOLUTE)) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0,
-                                   "%s= path is not absolute%s: %s",
-                                   lvalue, fatal ? "" : ", ignoring", path);
-                        return -EINVAL;
-                }
-
-                if (absolute && (flag & PATH_CHECK_RELATIVE)) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0,
-                                   "%s= path is absolute%s: %s",
-                                   lvalue, fatal ? "" : ", ignoring", path);
-                        return -EINVAL;
-                }
-        }
-
-        path_simplify(path, true);
-
-        if (!path_is_normalized(path)) {
-                log_syntax(unit, LOG_ERR, filename, line, 0,
-                           "%s= path is not normalized%s: %s",
-                           lvalue, fatal ? "" : ", ignoring", path);
-                return -EINVAL;
-        }
-
-        return 0;
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/path-util.h b/src/systemd/src/basic/path-util.h
deleted file mode 100644
index 72f3ce36..00000000
--- a/src/systemd/src/basic/path-util.h
+++ /dev/null
@@ -1,175 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <alloca.h>
-#include <stdbool.h>
-#include <stddef.h>
-
-#include "macro.h"
-#include "string-util.h"
-#include "time-util.h"
-
-#if 0 /* NM_IGNORED */
-#define PATH_SPLIT_SBIN_BIN(x) x "sbin:" x "bin"
-#define PATH_SPLIT_SBIN_BIN_NULSTR(x) x "sbin\0" x "bin\0"
-
-#define PATH_NORMAL_SBIN_BIN(x) x "bin"
-#define PATH_NORMAL_SBIN_BIN_NULSTR(x) x "bin\0"
-
-#if HAVE_SPLIT_BIN
-#  define PATH_SBIN_BIN(x) PATH_SPLIT_SBIN_BIN(x)
-#  define PATH_SBIN_BIN_NULSTR(x) PATH_SPLIT_SBIN_BIN_NULSTR(x)
-#else
-#  define PATH_SBIN_BIN(x) PATH_NORMAL_SBIN_BIN(x)
-#  define PATH_SBIN_BIN_NULSTR(x) PATH_NORMAL_SBIN_BIN_NULSTR(x)
-#endif
-
-#define DEFAULT_PATH_NORMAL PATH_SBIN_BIN("/usr/local/") ":" PATH_SBIN_BIN("/usr/")
-#define DEFAULT_PATH_NORMAL_NULSTR PATH_SBIN_BIN_NULSTR("/usr/local/") PATH_SBIN_BIN_NULSTR("/usr/")
-#define DEFAULT_PATH_SPLIT_USR DEFAULT_PATH_NORMAL ":" PATH_SBIN_BIN("/")
-#define DEFAULT_PATH_SPLIT_USR_NULSTR DEFAULT_PATH_NORMAL_NULSTR PATH_SBIN_BIN_NULSTR("/")
-#define DEFAULT_PATH_COMPAT PATH_SPLIT_SBIN_BIN("/usr/local/") ":" PATH_SPLIT_SBIN_BIN("/usr/") ":" PATH_SPLIT_SBIN_BIN("/")
-
-#if HAVE_SPLIT_USR
-#  define DEFAULT_PATH DEFAULT_PATH_SPLIT_USR
-#  define DEFAULT_PATH_NULSTR DEFAULT_PATH_SPLIT_USR_NULSTR
-#else
-#  define DEFAULT_PATH DEFAULT_PATH_NORMAL
-#  define DEFAULT_PATH_NULSTR DEFAULT_PATH_NORMAL_NULSTR
-#endif
-#endif /* NM_IGNORED */
-
-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_startswith(const char *path, const char *prefix) _pure_;
-int path_compare(const char *a, const char *b) _pure_;
-bool path_equal(const char *a, const char *b) _pure_;
-bool path_equal_or_files_same(const char *a, const char *b, int flags);
-char* path_join(const char *root, const char *path, const char *rest);
-char* path_simplify(char *path, bool kill_dots);
-
-static inline bool path_equal_ptr(const char *a, const char *b) {
-        return !!a == !!b && (!a || path_equal(a, b));
-}
-
-/* Note: the search terminates on the first NULL item. */
-#define PATH_IN_SET(p, ...)                                     \
-        ({                                                      \
-                char **_s;                                      \
-                bool _found = false;                            \
-                STRV_FOREACH(_s, STRV_MAKE(__VA_ARGS__))        \
-                        if (path_equal(p, *_s)) {               \
-                               _found = true;                   \
-                               break;                           \
-                        }                                       \
-                _found;                                         \
-        })
-
-#define PATH_STARTSWITH_SET(p, ...)                             \
-        ({                                                      \
-                char **s;                                       \
-                bool _found = false;                            \
-                STRV_FOREACH(s, STRV_MAKE(__VA_ARGS__))         \
-                        if (path_startswith(p, *s)) {           \
-                               _found = true;                   \
-                               break;                           \
-                        }                                       \
-                _found;                                         \
-        })
-
-int path_strv_make_absolute_cwd(char **l);
-char** path_strv_resolve(char **l, const char *root);
-char** path_strv_resolve_uniq(char **l, const char *root);
-
-int find_binary(const char *name, char **filename);
-
-bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update);
-
-int fsck_exists(const char *fstype);
-int mkfs_exists(const char *fstype);
-
-/* Iterates through the path prefixes of the specified path, going up
- * the tree, to root. Also returns "" (and not "/"!) for the root
- * directory. Excludes the specified directory itself */
-#define PATH_FOREACH_PREFIX(prefix, path) \
-        for (char *_slash = ({ path_simplify(strcpy(prefix, path), false); streq(prefix, "/") ? NULL : strrchr(prefix, '/'); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/'))
-
-/* Same as PATH_FOREACH_PREFIX but also includes the specified path itself */
-#define PATH_FOREACH_PREFIX_MORE(prefix, path) \
-        for (char *_slash = ({ path_simplify(strcpy(prefix, path), false); if (streq(prefix, "/")) prefix[0] = 0; strrchr(prefix, 0); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/'))
-
-char *prefix_root(const char *root, const char *path);
-
-/* Similar to prefix_root(), but returns an alloca() buffer, or
- * possibly a const pointer into the path parameter */
-#define prefix_roota(root, path)                                        \
-        ({                                                              \
-                const char* _path = (path), *_root = (root), *_ret;     \
-                char *_p, *_n;                                          \
-                size_t _l;                                              \
-                while (_path[0] == '/' && _path[1] == '/')              \
-                        _path ++;                                       \
-                if (empty_or_root(_root))                               \
-                        _ret = _path;                                   \
-                else {                                                  \
-                        _l = strlen(_root) + 1 + strlen(_path) + 1;     \
-                        _n = alloca(_l);                                \
-                        _p = stpcpy(_n, _root);                         \
-                        while (_p > _n && _p[-1] == '/')                \
-                                _p--;                                   \
-                        if (_path[0] != '/')                            \
-                                *(_p++) = '/';                          \
-                        strcpy(_p, _path);                              \
-                        _ret = _n;                                      \
-                }                                                       \
-                _ret;                                                   \
-        })
-
-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_normalized(const char *p) _pure_;
-
-char *file_in_same_dir(const char *path, const char *filename);
-
-bool hidden_or_backup_file(const char *filename) _pure_;
-
-bool is_device_path(const char *path);
-
-bool valid_device_node_path(const char *path);
-bool valid_device_allow_pattern(const char *path);
-
-int systemd_installation_has_version(const char *root, unsigned minimal_version);
-
-bool dot_or_dot_dot(const char *path);
-
-static inline const char *skip_dev_prefix(const char *p) {
-        const char *e;
-
-        /* Drop any /dev prefix if there is any */
-
-        e = path_startswith(p, "/dev/");
-
-        return e ?: p;
-}
-
-bool empty_or_root(const char *root);
-static inline const char *empty_to_root(const char *path) {
-        return isempty(path) ? "/" : path;
-}
-
-enum {
-        PATH_CHECK_FATAL    = 1 << 0,  /* If not set, then error message is appended with 'ignoring'. */
-        PATH_CHECK_ABSOLUTE = 1 << 1,
-        PATH_CHECK_RELATIVE = 1 << 2,
-};
-
-int path_simplify_and_warn(char *path, unsigned flag, const char *unit, const char *filename, unsigned line, const char *lvalue);
diff --git a/src/systemd/src/basic/prioq.c b/src/systemd/src/basic/prioq.c
deleted file mode 100644
index 79900df3..00000000
--- a/src/systemd/src/basic/prioq.c
+++ /dev/null
@@ -1,303 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-/*
- * Priority Queue
- * The prioq object implements a priority queue. That is, it orders objects by
- * their priority and allows O(1) access to the object with the highest
- * priority. Insertion and removal are Θ(log n). Optionally, the caller can
- * provide a pointer to an index which will be kept up-to-date by the prioq.
- *
- * The underlying algorithm used in this implementation is a Heap.
- */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <stdlib.h>
-
-#include "alloc-util.h"
-#include "hashmap.h"
-#include "prioq.h"
-
-struct prioq_item {
-        void *data;
-        unsigned *idx;
-};
-
-struct Prioq {
-        compare_func_t compare_func;
-        unsigned n_items, n_allocated;
-
-        struct prioq_item *items;
-};
-
-Prioq *prioq_new(compare_func_t compare_func) {
-        Prioq *q;
-
-        q = new0(Prioq, 1);
-        if (!q)
-                return q;
-
-        q->compare_func = compare_func;
-        return q;
-}
-
-Prioq* prioq_free(Prioq *q) {
-        if (!q)
-                return NULL;
-
-        free(q->items);
-        return mfree(q);
-}
-
-int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func) {
-        assert(q);
-
-        if (*q)
-                return 0;
-
-        *q = prioq_new(compare_func);
-        if (!*q)
-                return -ENOMEM;
-
-        return 0;
-}
-
-static void swap(Prioq *q, unsigned j, unsigned k) {
-        void *saved_data;
-        unsigned *saved_idx;
-
-        assert(q);
-        assert(j < q->n_items);
-        assert(k < q->n_items);
-
-        assert(!q->items[j].idx || *(q->items[j].idx) == j);
-        assert(!q->items[k].idx || *(q->items[k].idx) == k);
-
-        saved_data = q->items[j].data;
-        saved_idx = q->items[j].idx;
-        q->items[j].data = q->items[k].data;
-        q->items[j].idx = q->items[k].idx;
-        q->items[k].data = saved_data;
-        q->items[k].idx = saved_idx;
-
-        if (q->items[j].idx)
-                *q->items[j].idx = j;
-
-        if (q->items[k].idx)
-                *q->items[k].idx = k;
-}
-
-static unsigned shuffle_up(Prioq *q, unsigned idx) {
-        assert(q);
-
-        while (idx > 0) {
-                unsigned k;
-
-                k = (idx-1)/2;
-
-                if (q->compare_func(q->items[k].data, q->items[idx].data) <= 0)
-                        break;
-
-                swap(q, idx, k);
-                idx = k;
-        }
-
-        return idx;
-}
-
-static unsigned shuffle_down(Prioq *q, unsigned idx) {
-        assert(q);
-
-        for (;;) {
-                unsigned j, k, s;
-
-                k = (idx+1)*2; /* right child */
-                j = k-1;       /* left child */
-
-                if (j >= q->n_items)
-                        break;
-
-                if (q->compare_func(q->items[j].data, q->items[idx].data) < 0)
-
-                        /* So our left child is smaller than we are, let's
-                         * remember this fact */
-                        s = j;
-                else
-                        s = idx;
-
-                if (k < q->n_items &&
-                    q->compare_func(q->items[k].data, q->items[s].data) < 0)
-
-                        /* So our right child is smaller than we are, let's
-                         * remember this fact */
-                        s = k;
-
-                /* s now points to the smallest of the three items */
-
-                if (s == idx)
-                        /* No swap necessary, we're done */
-                        break;
-
-                swap(q, idx, s);
-                idx = s;
-        }
-
-        return idx;
-}
-
-int prioq_put(Prioq *q, void *data, unsigned *idx) {
-        struct prioq_item *i;
-        unsigned k;
-
-        assert(q);
-
-        if (q->n_items >= q->n_allocated) {
-                unsigned n;
-                struct prioq_item *j;
-
-                n = MAX((q->n_items+1) * 2, 16u);
-                j = reallocarray(q->items, n, sizeof(struct prioq_item));
-                if (!j)
-                        return -ENOMEM;
-
-                q->items = j;
-                q->n_allocated = n;
-        }
-
-        k = q->n_items++;
-        i = q->items + k;
-        i->data = data;
-        i->idx = idx;
-
-        if (idx)
-                *idx = k;
-
-        shuffle_up(q, k);
-
-        return 0;
-}
-
-static void remove_item(Prioq *q, struct prioq_item *i) {
-        struct prioq_item *l;
-
-        assert(q);
-        assert(i);
-
-        l = q->items + q->n_items - 1;
-
-        if (i == l)
-                /* Last entry, let's just remove it */
-                q->n_items--;
-        else {
-                unsigned k;
-
-                /* Not last entry, let's replace the last entry with
-                 * this one, and reshuffle */
-
-                k = i - q->items;
-
-                i->data = l->data;
-                i->idx = l->idx;
-                if (i->idx)
-                        *i->idx = k;
-                q->n_items--;
-
-                k = shuffle_down(q, k);
-                shuffle_up(q, k);
-        }
-}
-
-_pure_ static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) {
-        struct prioq_item *i;
-
-        assert(q);
-
-        if (idx) {
-                if (*idx == PRIOQ_IDX_NULL ||
-                    *idx > q->n_items)
-                        return NULL;
-
-                i = q->items + *idx;
-                if (i->data != data)
-                        return NULL;
-
-                return i;
-        } else {
-                for (i = q->items; i < q->items + q->n_items; i++)
-                        if (i->data == data)
-                                return i;
-                return NULL;
-        }
-}
-
-int prioq_remove(Prioq *q, void *data, unsigned *idx) {
-        struct prioq_item *i;
-
-        if (!q)
-                return 0;
-
-        i = find_item(q, data, idx);
-        if (!i)
-                return 0;
-
-        remove_item(q, i);
-        return 1;
-}
-
-int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) {
-        struct prioq_item *i;
-        unsigned k;
-
-        assert(q);
-
-        i = find_item(q, data, idx);
-        if (!i)
-                return 0;
-
-        k = i - q->items;
-        k = shuffle_down(q, k);
-        shuffle_up(q, k);
-        return 1;
-}
-
-void *prioq_peek(Prioq *q) {
-
-        if (!q)
-                return NULL;
-
-        if (q->n_items <= 0)
-                return NULL;
-
-        return q->items[0].data;
-}
-
-void *prioq_pop(Prioq *q) {
-        void *data;
-
-        if (!q)
-                return NULL;
-
-        if (q->n_items <= 0)
-                return NULL;
-
-        data = q->items[0].data;
-        remove_item(q, q->items);
-        return data;
-}
-
-unsigned prioq_size(Prioq *q) {
-
-        if (!q)
-                return 0;
-
-        return q->n_items;
-}
-
-bool prioq_isempty(Prioq *q) {
-
-        if (!q)
-                return true;
-
-        return q->n_items <= 0;
-}
diff --git a/src/systemd/src/basic/prioq.h b/src/systemd/src/basic/prioq.h
deleted file mode 100644
index e0361752..00000000
--- a/src/systemd/src/basic/prioq.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-
-#include "hashmap.h"
-#include "macro.h"
-
-typedef struct Prioq Prioq;
-
-#define PRIOQ_IDX_NULL ((unsigned) -1)
-
-Prioq *prioq_new(compare_func_t compare);
-Prioq *prioq_free(Prioq *q);
-int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func);
-
-int prioq_put(Prioq *q, void *data, unsigned *idx);
-int prioq_remove(Prioq *q, void *data, unsigned *idx);
-int prioq_reshuffle(Prioq *q, void *data, unsigned *idx);
-
-void *prioq_peek(Prioq *q) _pure_;
-void *prioq_pop(Prioq *q);
-
-unsigned prioq_size(Prioq *q) _pure_;
-bool prioq_isempty(Prioq *q) _pure_;
diff --git a/src/systemd/src/basic/process-util.c b/src/systemd/src/basic/process-util.c
deleted file mode 100644
index 1412f036..00000000
--- a/src/systemd/src/basic/process-util.c
+++ /dev/null
@@ -1,1505 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <ctype.h>
-#include <errno.h>
-#include <limits.h>
-#include <linux/oom.h>
-#include <sched.h>
-#include <signal.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdio_ext.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/mount.h>
-#include <sys/personality.h>
-#include <sys/prctl.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <syslog.h>
-#include <unistd.h>
-#if 0 /* NM_IGNORED */
-#if HAVE_VALGRIND_VALGRIND_H
-#include <valgrind/valgrind.h>
-#endif
-#endif /* NM_IGNORED */
-
-#include "alloc-util.h"
-#include "architecture.h"
-#include "escape.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "fs-util.h"
-#include "ioprio.h"
-#include "log.h"
-#include "macro.h"
-#include "missing.h"
-#include "process-util.h"
-#include "raw-clone.h"
-#include "signal-util.h"
-#include "stat-util.h"
-#include "string-table.h"
-#include "string-util.h"
-#include "terminal-util.h"
-#include "user-util.h"
-#include "util.h"
-
-#if 0 /* NM_IGNORED */
-int get_process_state(pid_t pid) {
-        const char *p;
-        char state;
-        int r;
-        _cleanup_free_ char *line = NULL;
-
-        assert(pid >= 0);
-
-        p = procfs_file_alloca(pid, "stat");
-
-        r = read_one_line_file(p, &line);
-        if (r == -ENOENT)
-                return -ESRCH;
-        if (r < 0)
-                return r;
-
-        p = strrchr(line, ')');
-        if (!p)
-                return -EIO;
-
-        p++;
-
-        if (sscanf(p, " %c", &state) != 1)
-                return -EIO;
-
-        return (unsigned char) state;
-}
-
-int get_process_comm(pid_t pid, char **ret) {
-        _cleanup_free_ char *escaped = NULL, *comm = NULL;
-        const char *p;
-        int r;
-
-        assert(ret);
-        assert(pid >= 0);
-
-        escaped = new(char, TASK_COMM_LEN);
-        if (!escaped)
-                return -ENOMEM;
-
-        p = procfs_file_alloca(pid, "comm");
-
-        r = read_one_line_file(p, &comm);
-        if (r == -ENOENT)
-                return -ESRCH;
-        if (r < 0)
-                return r;
-
-        /* Escape unprintable characters, just in case, but don't grow the string beyond the underlying size */
-        cellescape(escaped, TASK_COMM_LEN, comm);
-
-        *ret = TAKE_PTR(escaped);
-        return 0;
-}
-
-int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) {
-        _cleanup_fclose_ FILE *f = NULL;
-        bool space = false;
-        char *k, *ans = NULL;
-        const char *p;
-        int c;
-
-        assert(line);
-        assert(pid >= 0);
-
-        /* Retrieves a process' command line. Replaces unprintable characters while doing so by whitespace (coalescing
-         * multiple sequential ones into one). If max_length is != 0 will return a string of the specified size at most
-         * (the trailing NUL byte does count towards the length here!), abbreviated with a "..." ellipsis. If
-         * comm_fallback is true and the process has no command line set (the case for kernel threads), or has a
-         * command line that resolves to the empty string will return the "comm" name of the process instead.
-         *
-         * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and
-         * comm_fallback is false). Returns 0 and sets *line otherwise. */
-
-        p = procfs_file_alloca(pid, "cmdline");
-
-        f = fopen(p, "re");
-        if (!f) {
-                if (errno == ENOENT)
-                        return -ESRCH;
-                return -errno;
-        }
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-        if (max_length == 1) {
-
-                /* If there's only room for one byte, return the empty string */
-                ans = new0(char, 1);
-                if (!ans)
-                        return -ENOMEM;
-
-                *line = ans;
-                return 0;
-
-        } else if (max_length == 0) {
-                size_t len = 0, allocated = 0;
-
-                while ((c = getc(f)) != EOF) {
-
-                        if (!GREEDY_REALLOC(ans, allocated, len+3)) {
-                                free(ans);
-                                return -ENOMEM;
-                        }
-
-                        if (isprint(c)) {
-                                if (space) {
-                                        ans[len++] = ' ';
-                                        space = false;
-                                }
-
-                                ans[len++] = c;
-                        } else if (len > 0)
-                                space = true;
-               }
-
-                if (len > 0)
-                        ans[len] = '\0';
-                else
-                        ans = mfree(ans);
-
-        } else {
-                bool dotdotdot = false;
-                size_t left;
-
-                ans = new(char, max_length);
-                if (!ans)
-                        return -ENOMEM;
-
-                k = ans;
-                left = max_length;
-                while ((c = getc(f)) != EOF) {
-
-                        if (isprint(c)) {
-
-                                if (space) {
-                                        if (left <= 2) {
-                                                dotdotdot = true;
-                                                break;
-                                        }
-
-                                        *(k++) = ' ';
-                                        left--;
-                                        space = false;
-                                }
-
-                                if (left <= 1) {
-                                        dotdotdot = true;
-                                        break;
-                                }
-
-                                *(k++) = (char) c;
-                                left--;
-                        } else if (k > ans)
-                                space = true;
-                }
-
-                if (dotdotdot) {
-                        if (max_length <= 4) {
-                                k = ans;
-                                left = max_length;
-                        } else {
-                                k = ans + max_length - 4;
-                                left = 4;
-
-                                /* Eat up final spaces */
-                                while (k > ans && isspace(k[-1])) {
-                                        k--;
-                                        left++;
-                                }
-                        }
-
-                        strncpy(k, "...", left-1);
-                        k[left-1] = 0;
-                } else
-                        *k = 0;
-        }
-
-        /* Kernel threads have no argv[] */
-        if (isempty(ans)) {
-                _cleanup_free_ char *t = NULL;
-                int h;
-
-                free(ans);
-
-                if (!comm_fallback)
-                        return -ENOENT;
-
-                h = get_process_comm(pid, &t);
-                if (h < 0)
-                        return h;
-
-                if (max_length == 0)
-                        ans = strjoin("[", t, "]");
-                else {
-                        size_t l;
-
-                        l = strlen(t);
-
-                        if (l + 3 <= max_length)
-                                ans = strjoin("[", t, "]");
-                        else if (max_length <= 6) {
-
-                                ans = new(char, max_length);
-                                if (!ans)
-                                        return -ENOMEM;
-
-                                memcpy(ans, "[...]", max_length-1);
-                                ans[max_length-1] = 0;
-                        } else {
-                                t[max_length - 6] = 0;
-
-                                /* Chop off final spaces */
-                                delete_trailing_chars(t, WHITESPACE);
-
-                                ans = strjoin("[", t, "...]");
-                        }
-                }
-                if (!ans)
-                        return -ENOMEM;
-        }
-
-        *line = ans;
-        return 0;
-}
-
-int rename_process(const char name[]) {
-        static size_t mm_size = 0;
-        static char *mm = NULL;
-        bool truncated = false;
-        size_t l;
-
-        /* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's
-         * internally used name of the process. For the first one a limit of 16 chars applies; to the second one in
-         * many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded;
-         * to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be
-         * truncated.
-         *
-         * Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */
-
-        if (isempty(name))
-                return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
-
-        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. 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 >= TASK_COMM_LEN) /* Linux process names can be 15 chars at max */
-                truncated = true;
-
-        /* Second step, change glibc's ID of the process name. */
-        if (program_invocation_name) {
-                size_t k;
-
-                k = strlen(program_invocation_name);
-                strncpy(program_invocation_name, name, k);
-                if (l > k)
-                        truncated = true;
-        }
-
-        /* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
-         * has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at
-         * the end. This is the best option for changing /proc/self/cmdline. */
-
-        /* Let's not bother with this if we don't have euid == 0. Strictly speaking we should check for the
-         * CAP_SYS_RESOURCE capability which is independent of the euid. In our own code the capability generally is
-         * present only for euid == 0, hence let's use this as quick bypass check, to avoid calling mmap() if
-         * PR_SET_MM_ARG_{START,END} fails with EPERM later on anyway. After all geteuid() is dead cheap to call, but
-         * mmap() is not. */
-        if (geteuid() != 0)
-                log_debug("Skipping PR_SET_MM, as we don't have privileges.");
-        else if (mm_size < l+1) {
-                size_t nn_size;
-                char *nn;
-
-                nn_size = PAGE_ALIGN(l+1);
-                nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
-                if (nn == MAP_FAILED) {
-                        log_debug_errno(errno, "mmap() failed: %m");
-                        goto use_saved_argv;
-                }
-
-                strncpy(nn, name, nn_size);
-
-                /* Now, let's tell the kernel about this new memory */
-                if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
-                        log_debug_errno(errno, "PR_SET_MM_ARG_START failed, proceeding without: %m");
-                        (void) munmap(nn, nn_size);
-                        goto use_saved_argv;
-                }
-
-                /* And update the end pointer to the new end, too. If this fails, we don't really know what to do, it's
-                 * pretty unlikely that we can rollback, hence we'll just accept the failure, and continue. */
-                if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
-                        log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
-
-                if (mm)
-                        (void) munmap(mm, mm_size);
-
-                mm = nn;
-                mm_size = nn_size;
-        } else {
-                strncpy(mm, name, mm_size);
-
-                /* Update the end pointer, continuing regardless of any failure. */
-                if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) mm + l + 1, 0, 0) < 0)
-                        log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
-        }
-
-use_saved_argv:
-        /* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if
-         * it still looks here */
-
-        if (saved_argc > 0) {
-                int i;
-
-                if (saved_argv[0]) {
-                        size_t k;
-
-                        k = strlen(saved_argv[0]);
-                        strncpy(saved_argv[0], name, k);
-                        if (l > k)
-                                truncated = true;
-                }
-
-                for (i = 1; i < saved_argc; i++) {
-                        if (!saved_argv[i])
-                                break;
-
-                        memzero(saved_argv[i], strlen(saved_argv[i]));
-                }
-        }
-
-        return !truncated;
-}
-
-int is_kernel_thread(pid_t pid) {
-        _cleanup_free_ char *line = NULL;
-        unsigned long long flags;
-        size_t l, i;
-        const char *p;
-        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;
-
-        p = procfs_file_alloca(pid, "stat");
-        r = read_one_line_file(p, &line);
-        if (r == -ENOENT)
-                return -ESRCH;
-        if (r < 0)
-                return r;
-
-        /* 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;
-        }
-
-        /* Skip preceeding whitespace */
-        l = strspn(q, WHITESPACE);
-        if (l < 1)
-                return -EINVAL;
-        q += l;
-
-        /* Truncate the rest */
-        l = strcspn(q, WHITESPACE);
-        if (l < 1)
-                return -EINVAL;
-        q[l] = 0;
-
-        r = safe_atollu(q, &flags);
-        if (r < 0)
-                return r;
-
-        return !!(flags & PF_KTHREAD);
-}
-
-int get_process_capeff(pid_t pid, char **capeff) {
-        const char *p;
-        int r;
-
-        assert(capeff);
-        assert(pid >= 0);
-
-        p = procfs_file_alloca(pid, "status");
-
-        r = get_proc_field(p, "CapEff", WHITESPACE, capeff);
-        if (r == -ENOENT)
-                return -ESRCH;
-
-        return r;
-}
-
-static int get_process_link_contents(const char *proc_file, char **name) {
-        int r;
-
-        assert(proc_file);
-        assert(name);
-
-        r = readlink_malloc(proc_file, name);
-        if (r == -ENOENT)
-                return -ESRCH;
-        if (r < 0)
-                return r;
-
-        return 0;
-}
-
-int get_process_exe(pid_t pid, char **name) {
-        const char *p;
-        char *d;
-        int r;
-
-        assert(pid >= 0);
-
-        p = procfs_file_alloca(pid, "exe");
-        r = get_process_link_contents(p, name);
-        if (r < 0)
-                return r;
-
-        d = endswith(*name, " (deleted)");
-        if (d)
-                *d = '\0';
-
-        return 0;
-}
-
-static int get_process_id(pid_t pid, const char *field, uid_t *uid) {
-        _cleanup_fclose_ FILE *f = NULL;
-        char line[LINE_MAX];
-        const char *p;
-
-        assert(field);
-        assert(uid);
-
-        if (pid < 0)
-                return -EINVAL;
-
-        p = procfs_file_alloca(pid, "status");
-        f = fopen(p, "re");
-        if (!f) {
-                if (errno == ENOENT)
-                        return -ESRCH;
-                return -errno;
-        }
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-        FOREACH_LINE(line, f, return -errno) {
-                char *l;
-
-                l = strstrip(line);
-
-                if (startswith(l, field)) {
-                        l += strlen(field);
-                        l += strspn(l, WHITESPACE);
-
-                        l[strcspn(l, WHITESPACE)] = 0;
-
-                        return parse_uid(l, uid);
-                }
-        }
-
-        return -EIO;
-}
-
-int get_process_uid(pid_t pid, uid_t *uid) {
-
-        if (pid == 0 || pid == getpid_cached()) {
-                *uid = getuid();
-                return 0;
-        }
-
-        return get_process_id(pid, "Uid:", uid);
-}
-
-int get_process_gid(pid_t pid, gid_t *gid) {
-
-        if (pid == 0 || pid == getpid_cached()) {
-                *gid = getgid();
-                return 0;
-        }
-
-        assert_cc(sizeof(uid_t) == sizeof(gid_t));
-        return get_process_id(pid, "Gid:", gid);
-}
-
-int get_process_cwd(pid_t pid, char **cwd) {
-        const char *p;
-
-        assert(pid >= 0);
-
-        p = procfs_file_alloca(pid, "cwd");
-
-        return get_process_link_contents(p, cwd);
-}
-
-int get_process_root(pid_t pid, char **root) {
-        const char *p;
-
-        assert(pid >= 0);
-
-        p = procfs_file_alloca(pid, "root");
-
-        return get_process_link_contents(p, root);
-}
-
-int get_process_environ(pid_t pid, char **env) {
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_free_ char *outcome = NULL;
-        int c;
-        const char *p;
-        size_t allocated = 0, sz = 0;
-
-        assert(pid >= 0);
-        assert(env);
-
-        p = procfs_file_alloca(pid, "environ");
-
-        f = fopen(p, "re");
-        if (!f) {
-                if (errno == ENOENT)
-                        return -ESRCH;
-                return -errno;
-        }
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-        while ((c = fgetc(f)) != EOF) {
-                if (!GREEDY_REALLOC(outcome, allocated, sz + 5))
-                        return -ENOMEM;
-
-                if (c == '\0')
-                        outcome[sz++] = '\n';
-                else
-                        sz += cescape_char(c, outcome + sz);
-        }
-
-        if (!outcome) {
-                outcome = strdup("");
-                if (!outcome)
-                        return -ENOMEM;
-        } else
-                outcome[sz] = '\0';
-
-        *env = TAKE_PTR(outcome);
-
-        return 0;
-}
-
-int get_process_ppid(pid_t pid, pid_t *_ppid) {
-        int r;
-        _cleanup_free_ char *line = NULL;
-        long unsigned ppid;
-        const char *p;
-
-        assert(pid >= 0);
-        assert(_ppid);
-
-        if (pid == 0 || pid == getpid_cached()) {
-                *_ppid = getppid();
-                return 0;
-        }
-
-        p = procfs_file_alloca(pid, "stat");
-        r = read_one_line_file(p, &line);
-        if (r == -ENOENT)
-                return -ESRCH;
-        if (r < 0)
-                return r;
-
-        /* Let's skip the pid and comm fields. The latter is enclosed
-         * in () but does not escape any () in its value, so let's
-         * skip over it manually */
-
-        p = strrchr(line, ')');
-        if (!p)
-                return -EIO;
-
-        p++;
-
-        if (sscanf(p, " "
-                   "%*c "  /* state */
-                   "%lu ", /* ppid */
-                   &ppid) != 1)
-                return -EIO;
-
-        if ((long unsigned) (pid_t) ppid != ppid)
-                return -ERANGE;
-
-        *_ppid = (pid_t) ppid;
-
-        return 0;
-}
-
-int wait_for_terminate(pid_t pid, siginfo_t *status) {
-        siginfo_t dummy;
-
-        assert(pid >= 1);
-
-        if (!status)
-                status = &dummy;
-
-        for (;;) {
-                zero(*status);
-
-                if (waitid(P_PID, pid, status, WEXITED) < 0) {
-
-                        if (errno == EINTR)
-                                continue;
-
-                        return negative_errno();
-                }
-
-                return 0;
-        }
-}
-
-/*
- * Return values:
- * < 0 : wait_for_terminate() failed to get the state of the
- *       process, the process was terminated by a signal, or
- *       failed for an unknown reason.
- * >=0 : The process terminated normally, and its exit code is
- *       returned.
- *
- * That is, success is indicated by a return value of zero, and an
- * error is indicated by a non-zero value.
- *
- * A warning is emitted if the process terminates abnormally,
- * and also if it returns non-zero unless check_exit_code is true.
- */
-int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags) {
-        _cleanup_free_ char *buffer = NULL;
-        siginfo_t status;
-        int r, prio;
-
-        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_full_errno(prio, r, "Failed to wait for %s: %m", strna(name));
-
-        if (status.si_code == CLD_EXITED) {
-                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_full(prio, "%s terminated by signal %s.", strna(name), signal_to_string(status.si_status));
-                return -EPROTO;
-        }
-
-        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.
- *
- * This call assumes SIGCHLD has been blocked already, in particular before the child to wait for has been forked off
- * to remain entirely race-free.
- */
-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;
-}
-
-void sigkill_wait(pid_t pid) {
-        assert(pid > 1);
-
-        if (kill(pid, SIGKILL) > 0)
-                (void) wait_for_terminate(pid, NULL);
-}
-
-void sigkill_waitp(pid_t *pid) {
-        PROTECT_ERRNO;
-
-        if (!pid)
-                return;
-        if (*pid <= 1)
-                return;
-
-        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;
-
-        r = kill(pid, sig) < 0 ? -errno : 0;
-
-        /* If this worked, also send SIGCONT, unless we already just sent a SIGCONT, or SIGKILL was sent which isn't
-         * affected by a process being suspended anyway. */
-        if (r >= 0 && !IN_SET(sig, SIGCONT, SIGKILL))
-                (void) kill(pid, SIGCONT);
-
-        return r;
-}
-
-int getenv_for_pid(pid_t pid, const char *field, char **ret) {
-        _cleanup_fclose_ FILE *f = NULL;
-        char *value = NULL;
-        bool done = false;
-        const char *path;
-        size_t l;
-
-        assert(pid >= 0);
-        assert(field);
-        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");
-
-        f = fopen(path, "re");
-        if (!f) {
-                if (errno == ENOENT)
-                        return -ESRCH;
-
-                return -errno;
-        }
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-        l = strlen(field);
-
-        do {
-                char line[LINE_MAX];
-                size_t i;
-
-                for (i = 0; i < sizeof(line)-1; i++) {
-                        int c;
-
-                        c = getc(f);
-                        if (_unlikely_(c == EOF)) {
-                                done = true;
-                                break;
-                        } else if (c == 0)
-                                break;
-
-                        line[i] = c;
-                }
-                line[i] = 0;
-
-                if (strneq(line, field, l) && line[l] == '=') {
-                        value = strdup(line + l + 1);
-                        if (!value)
-                                return -ENOMEM;
-
-                        *ret = value;
-                        return 1;
-                }
-
-        } while (!done);
-
-        *ret = NULL;
-        return 0;
-}
-
-bool pid_is_unwaited(pid_t pid) {
-        /* Checks whether a PID is still valid at all, including a zombie */
-
-        if (pid < 0)
-                return false;
-
-        if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */
-                return true;
-
-        if (pid == getpid_cached())
-                return true;
-
-        if (kill(pid, 0) >= 0)
-                return true;
-
-        return errno != ESRCH;
-}
-
-bool pid_is_alive(pid_t pid) {
-        int r;
-
-        /* Checks whether a PID is still valid and not a zombie */
-
-        if (pid < 0)
-                return false;
-
-        if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */
-                return true;
-
-        if (pid == getpid_cached())
-                return true;
-
-        r = get_process_state(pid);
-        if (IN_SET(r, -ESRCH, 'Z'))
-                return false;
-
-        return true;
-}
-
-int pid_from_same_root_fs(pid_t pid) {
-        const char *root;
-
-        if (pid < 0)
-                return false;
-
-        if (pid == 0 || pid == getpid_cached())
-                return true;
-
-        root = procfs_file_alloca(pid, "root");
-
-        return files_same(root, "/proc/1/root", 0);
-}
-#endif /* NM_IGNORED */
-
-bool is_main_thread(void) {
-        static thread_local int cached = 0;
-
-        if (_unlikely_(cached == 0))
-                cached = getpid_cached() == gettid() ? 1 : -1;
-
-        return cached > 0;
-}
-
-#if 0 /* NM_IGNORED */
-_noreturn_ void freeze(void) {
-
-        log_close();
-
-        /* Make sure nobody waits for us on a socket anymore */
-        close_all_fds(NULL, 0);
-
-        sync();
-
-        /* 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();
-}
-
-bool oom_score_adjust_is_valid(int oa) {
-        return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX;
-}
-
-unsigned long personality_from_string(const char *p) {
-        int architecture;
-
-        if (!p)
-                return PERSONALITY_INVALID;
-
-        /* Parse a personality specifier. We use our own identifiers that indicate specific ABIs, rather than just
-         * hints regarding the register size, since we want to keep things open for multiple locally supported ABIs for
-         * the same register size. */
-
-        architecture = architecture_from_string(p);
-        if (architecture < 0)
-                return PERSONALITY_INVALID;
-
-        if (architecture == native_architecture())
-                return PER_LINUX;
-#ifdef SECONDARY_ARCHITECTURE
-        if (architecture == SECONDARY_ARCHITECTURE)
-                return PER_LINUX32;
-#endif
-
-        return PERSONALITY_INVALID;
-}
-
-const char* personality_to_string(unsigned long p) {
-        int architecture = _ARCHITECTURE_INVALID;
-
-        if (p == PER_LINUX)
-                architecture = native_architecture();
-#ifdef SECONDARY_ARCHITECTURE
-        else if (p == PER_LINUX32)
-                architecture = SECONDARY_ARCHITECTURE;
-#endif
-
-        if (architecture < 0)
-                return NULL;
-
-        return architecture_to_string(architecture);
-}
-
-int safe_personality(unsigned long p) {
-        int ret;
-
-        /* So here's the deal, personality() is weirdly defined by glibc. In some cases it returns a failure via errno,
-         * and in others as negative return value containing an errno-like value. Let's work around this: this is a
-         * wrapper that uses errno if it is set, and uses the return value otherwise. And then it sets both errno and
-         * the return value indicating the same issue, so that we are definitely on the safe side.
-         *
-         * See https://github.com/systemd/systemd/issues/6737 */
-
-        errno = 0;
-        ret = personality(p);
-        if (ret < 0) {
-                if (errno != 0)
-                        return -errno;
-
-                errno = -ret;
-        }
-
-        return ret;
-}
-
-int opinionated_personality(unsigned long *ret) {
-        int current;
-
-        /* Returns the current personality, or PERSONALITY_INVALID if we can't determine it. This function is a bit
-         * opinionated though, and ignores all the finer-grained bits and exotic personalities, only distinguishing the
-         * two most relevant personalities: PER_LINUX and PER_LINUX32. */
-
-        current = safe_personality(PERSONALITY_INVALID);
-        if (current < 0)
-                return current;
-
-        if (((unsigned long) current & 0xffff) == PER_LINUX32)
-                *ret = PER_LINUX32;
-        else
-                *ret = PER_LINUX;
-
-        return 0;
-}
-
-void valgrind_summary_hack(void) {
-#if HAVE_VALGRIND_VALGRIND_H
-        if (getpid_cached() == 1 && RUNNING_ON_VALGRIND) {
-                pid_t pid;
-                pid = raw_clone(SIGCHLD);
-                if (pid < 0)
-                        log_emergency_errno(errno, "Failed to fork off valgrind helper: %m");
-                else if (pid == 0)
-                        exit(EXIT_SUCCESS);
-                else {
-                        log_info("Spawned valgrind helper as PID "PID_FMT".", pid);
-                        (void) wait_for_terminate(pid, NULL);
-                }
-        }
-#endif
-}
-
-int pid_compare_func(const void *a, const void *b) {
-        const pid_t *p = a, *q = b;
-
-        /* Suitable for usage in qsort() */
-        return CMP(*p, *q);
-}
-
-int ioprio_parse_priority(const char *s, int *ret) {
-        int i, r;
-
-        assert(s);
-        assert(ret);
-
-        r = safe_atoi(s, &i);
-        if (r < 0)
-                return r;
-
-        if (!ioprio_priority_is_valid(i))
-                return -EINVAL;
-
-        *ret = i;
-        return 0;
-}
-#endif /* NM_IGNORED */
-
-/* The cached PID, possible values:
- *
- *     == UNSET [0]  → cache not initialized yet
- *     == BUSY [-1]  → some thread is initializing it at the moment
- *     any other     → the cached PID
- */
-
-#define CACHED_PID_UNSET ((pid_t) 0)
-#define CACHED_PID_BUSY ((pid_t) -1)
-
-static pid_t cached_pid = CACHED_PID_UNSET;
-
-void reset_cached_pid(void) {
-        /* Invoked in the child after a fork(), i.e. at the first moment the PID changed */
-        cached_pid = CACHED_PID_UNSET;
-}
-
-/* We use glibc __register_atfork() + __dso_handle directly here, as they are not included in the glibc
- * headers. __register_atfork() is mostly equivalent to pthread_atfork(), but doesn't require us to link against
- * libpthread, as it is part of glibc anyway. */
-extern int __register_atfork(void (*prepare) (void), void (*parent) (void), void (*child) (void), void *dso_handle);
-extern void* __dso_handle __attribute__ ((__weak__));
-
-pid_t getpid_cached(void) {
-        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
-         * system call each time. This restores glibc behaviour from before 2.24, when getpid() was unconditionally
-         * cached. Starting with 2.24 getpid() started to become prohibitively expensive when used for detecting when
-         * objects were used across fork()s. With this caching the old behaviour is somewhat restored.
-         *
-         * https://bugzilla.redhat.com/show_bug.cgi?id=1443976
-         * https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=c579f48edba88380635ab98cb612030e3ed8691e
-         */
-
-        current_value = __sync_val_compare_and_swap(&cached_pid, CACHED_PID_UNSET, CACHED_PID_BUSY);
-
-        switch (current_value) {
-
-        case CACHED_PID_UNSET: { /* Not initialized yet, then do so now */
-                pid_t new_pid;
-
-                new_pid = 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;
-                        }
-
-                        installed = true;
-                }
-
-                cached_pid = new_pid;
-                return new_pid;
-        }
-
-        case CACHED_PID_BUSY: /* Somebody else is currently initializing */
-                return raw_getpid();
-
-        default: /* Properly initialized */
-                return current_value;
-        }
-}
-
-#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_SET(flags, FORK_NEW_MOUNTNS | FORK_MOUNTNS_SLAVE)) {
-
-                /* Optionally, make sure we never propagate mounts to the host. */
-
-                if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) {
-                        log_full_errno(prio, errno, "Failed to remount root directory as MS_SLAVE: %m");
-                        _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[], size_t n_except, pid_t *ret_pid, const char *path, ...) {
-        bool stdout_is_tty, stderr_is_tty;
-        size_t 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 = newa(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);
-}
-
-int set_oom_score_adjust(int value) {
-        char t[DECIMAL_STR_MAX(int)];
-
-        sprintf(t, "%i", value);
-
-        return write_string_file("/proc/self/oom_score_adj", t,
-                                 WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER);
-}
-
-static const char *const ioprio_class_table[] = {
-        [IOPRIO_CLASS_NONE] = "none",
-        [IOPRIO_CLASS_RT] = "realtime",
-        [IOPRIO_CLASS_BE] = "best-effort",
-        [IOPRIO_CLASS_IDLE] = "idle"
-};
-
-DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, IOPRIO_N_CLASSES);
-
-static const char *const sigchld_code_table[] = {
-        [CLD_EXITED] = "exited",
-        [CLD_KILLED] = "killed",
-        [CLD_DUMPED] = "dumped",
-        [CLD_TRAPPED] = "trapped",
-        [CLD_STOPPED] = "stopped",
-        [CLD_CONTINUED] = "continued",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int);
-
-static const char* const sched_policy_table[] = {
-        [SCHED_OTHER] = "other",
-        [SCHED_BATCH] = "batch",
-        [SCHED_IDLE] = "idle",
-        [SCHED_FIFO] = "fifo",
-        [SCHED_RR] = "rr"
-};
-
-DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX);
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/process-util.h b/src/systemd/src/basic/process-util.h
deleted file mode 100644
index e11164bd..00000000
--- a/src/systemd/src/basic/process-util.h
+++ /dev/null
@@ -1,200 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <alloca.h>
-#include <errno.h>
-#include <sched.h>
-#include <signal.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/resource.h>
-#include <sys/types.h>
-
-#include "format-util.h"
-#include "ioprio.h"
-#include "macro.h"
-#include "time-util.h"
-
-#define procfs_file_alloca(pid, field)                                  \
-        ({                                                              \
-                pid_t _pid_ = (pid);                                    \
-                const char *_r_;                                        \
-                if (_pid_ == 0) {                                       \
-                        _r_ = ("/proc/self/" field);                    \
-                } else {                                                \
-                        _r_ = alloca(STRLEN("/proc/") + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \
-                        sprintf((char*) _r_, "/proc/"PID_FMT"/" field, _pid_);                       \
-                }                                                       \
-                _r_;                                                    \
-        })
-
-int get_process_state(pid_t pid);
-int get_process_comm(pid_t pid, char **name);
-int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line);
-int get_process_exe(pid_t pid, char **name);
-int get_process_uid(pid_t pid, uid_t *uid);
-int get_process_gid(pid_t pid, gid_t *gid);
-int get_process_capeff(pid_t pid, char **capeff);
-int get_process_cwd(pid_t pid, char **cwd);
-int get_process_root(pid_t pid, char **root);
-int get_process_environ(pid_t pid, char **environ);
-int get_process_ppid(pid_t pid, pid_t *ppid);
-
-int wait_for_terminate(pid_t pid, siginfo_t *status);
-
-typedef enum WaitFlags {
-        WAIT_LOG_ABNORMAL             = 1 << 0,
-        WAIT_LOG_NON_ZERO_EXIT_STATUS = 1 << 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);
-
-int rename_process(const char name[]);
-int is_kernel_thread(pid_t pid);
-
-int getenv_for_pid(pid_t pid, const char *field, char **_value);
-
-bool pid_is_alive(pid_t pid);
-bool pid_is_unwaited(pid_t pid);
-int pid_from_same_root_fs(pid_t pid);
-
-bool is_main_thread(void);
-
-_noreturn_ void freeze(void);
-
-bool oom_score_adjust_is_valid(int oa);
-
-#ifndef PERSONALITY_INVALID
-/* personality(7) documents that 0xffffffffUL is used for querying the
- * current personality, hence let's use that here as error
- * indicator. */
-#define PERSONALITY_INVALID 0xffffffffLU
-#endif
-
-unsigned long personality_from_string(const char *p);
-const char *personality_to_string(unsigned long);
-
-int safe_personality(unsigned long p);
-int opinionated_personality(unsigned long *ret);
-
-int ioprio_class_to_string_alloc(int i, char **s);
-int ioprio_class_from_string(const char *s);
-
-const char *sigchld_code_to_string(int i) _const_;
-int sigchld_code_from_string(const char *s) _pure_;
-
-int sched_policy_to_string_alloc(int i, char **s);
-int sched_policy_from_string(const char *s);
-
-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);
-
-int pid_compare_func(const void *a, const void *b);
-
-#if 0 /* NM_IGNORED */
-static inline bool nice_is_valid(int n) {
-        return n >= PRIO_MIN && n < PRIO_MAX;
-}
-
-static inline bool sched_policy_is_valid(int i) {
-        return IN_SET(i, SCHED_OTHER, SCHED_BATCH, SCHED_IDLE, SCHED_FIFO, SCHED_RR);
-}
-
-static inline bool sched_priority_is_valid(int i) {
-        return i >= 0 && i <= sched_get_priority_max(SCHED_RR);
-}
-
-static inline bool ioprio_class_is_valid(int i) {
-        return IN_SET(i, IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE);
-}
-
-static inline bool ioprio_priority_is_valid(int i) {
-        return i >= 0 && i < IOPRIO_BE_NR;
-}
-
-static inline bool pid_is_valid(pid_t p) {
-        return p > 0;
-}
-
-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 = 1 << 0,
-        FORK_CLOSE_ALL_FDS = 1 << 1,
-        FORK_DEATHSIG      = 1 << 2,
-        FORK_NULL_STDIO    = 1 << 3,
-        FORK_REOPEN_LOG    = 1 << 4,
-        FORK_LOG           = 1 << 5,
-        FORK_WAIT          = 1 << 6,
-        FORK_NEW_MOUNTNS   = 1 << 7,
-        FORK_MOUNTNS_SLAVE = 1 << 8,
-} 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[], size_t n_except, pid_t *pid, const char *path, ...);
-
-int set_oom_score_adjust(int value);
-
-#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)
-
-/* Like TAKE_PTR() but for child PIDs, resetting them to 0 */
-#define TAKE_PID(pid)                           \
-        ({                                      \
-                pid_t _pid_ = (pid);            \
-                (pid) = 0;                      \
-                _pid_;                          \
-        })
diff --git a/src/systemd/src/basic/random-util.c b/src/systemd/src/basic/random-util.c
deleted file mode 100644
index 5c07e067..00000000
--- a/src/systemd/src/basic/random-util.c
+++ /dev/null
@@ -1,220 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#ifdef __x86_64__
-#include <cpuid.h>
-#endif
-
-#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>
-
-#if HAVE_SYS_AUXV_H
-#  include <sys/auxv.h>
-#endif
-
-#if USE_SYS_RANDOM_H
-#  include <sys/random.h>
-#else
-#  include <linux/random.h>
-#endif
-
-#include "fd-util.h"
-#include "io-util.h"
-#include "missing.h"
-#include "random-util.h"
-#include "time-util.h"
-
-
-int rdrand64(uint64_t *ret) {
-
-#ifdef __x86_64__
-        static int have_rdrand = -1;
-        unsigned char err;
-
-        if (have_rdrand < 0) {
-                uint32_t eax, ebx, ecx, edx;
-
-                /* Check if RDRAND is supported by the CPU */
-                if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0) {
-                        have_rdrand = false;
-                        return -EOPNOTSUPP;
-                }
-
-                have_rdrand = !!(ecx & (1U << 30));
-        }
-
-        if (have_rdrand == 0)
-                return -EOPNOTSUPP;
-
-        asm volatile("rdrand %0;"
-                     "setc %1"
-                     : "=r" (*ret),
-                       "=qm" (err));
-        if (!err)
-                return -EAGAIN;
-
-        return 0;
-#else
-        return -EOPNOTSUPP;
-#endif
-}
-
-int acquire_random_bytes(void *p, size_t n, bool high_quality_required) {
-        static int have_syscall = -1;
-
-        _cleanup_close_ int fd = -1;
-        size_t already_done = 0;
-        int r;
-
-        /* Gathers some randomness from the kernel. This call will never block. If
-         * high_quality_required, it will always return some data from the kernel,
-         * regardless of whether the random pool is fully initialized or not.
-         * Otherwise, it will return success if at least some random bytes were
-         * successfully acquired, and an error if the kernel has no entropy whatsover
-         * for us. */
-
-        /* Use the getrandom() syscall unless we know we don't have it. */
-        if (have_syscall != 0 && !HAS_FEATURE_MEMORY_SANITIZER) {
-#if !HAVE_GETRANDOM
-                /* NetworkManager Note: systemd calls the syscall directly in this case. Don't add that workaround.
-                 * If you don't compile against a libc that provides getrandom(), you don't get it. */
-                r = -1;
-                errno = ENOSYS;
-#else
-                r = getrandom(p, n, GRND_NONBLOCK);
-#endif
-                if (r > 0) {
-                        have_syscall = true;
-                        if ((size_t) r == n)
-                                return 0;
-                        if (!high_quality_required) {
-                                /* Fill in the remaining bytes using pseudorandom values */
-                                pseudorandom_bytes((uint8_t*) p + r, n - r);
-                                return 0;
-                        }
-
-                        already_done = r;
-                } else if (errno == ENOSYS)
-                          /* We lack the syscall, continue with reading from /dev/urandom. */
-                          have_syscall = false;
-                else if (errno == EAGAIN) {
-                        /* The kernel has no entropy whatsoever. Let's remember to
-                         * use the syscall the next time again though.
-                         *
-                         * If high_quality_required is false, return an error so that
-                         * random_bytes() can produce some pseudorandom
-                         * bytes. Otherwise, fall back to /dev/urandom, which we know
-                         * is empty, but the kernel will produce some bytes for us on
-                         * a best-effort basis. */
-                        have_syscall = true;
-
-                        if (!high_quality_required) {
-                                uint64_t u;
-                                size_t k;
-
-                                /* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality
-                                 * randomness is not required, as we don't trust it (who does?). Note that we only do a
-                                 * single iteration of RDRAND here, even though the Intel docs suggest calling this in
-                                 * a tight loop of 10 invocatins or so. That's because we don't really care about the
-                                 * quality here. */
-
-                                if (rdrand64(&u) < 0)
-                                        return -ENODATA;
-
-                                k = MIN(n, sizeof(u));
-                                memcpy(p, &u, k);
-
-                                /* We only get 64bit out of RDRAND, the rest let's fill up with pseudo-random crap. */
-                                pseudorandom_bytes((uint8_t*) p + k, n - k);
-                                return 0;
-                        }
-                } else
-                        return -errno;
-        }
-
-        fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
-        if (fd < 0)
-                return errno == ENOENT ? -ENOSYS : -errno;
-
-        return loop_read_exact(fd, (uint8_t*) p + already_done, n - already_done, true);
-}
-
-void initialize_srand(void) {
-        static bool srand_called = false;
-        unsigned x;
-#if HAVE_SYS_AUXV_H
-        void *auxv;
-#endif
-
-        if (srand_called)
-                return;
-
-#if HAVE_SYS_AUXV_H
-        /* The kernel provides us with 16 bytes of entropy in auxv, so let's
-         * try to make use of that to seed the pseudo-random generator. It's
-         * better than nothing... */
-
-        auxv = (void*) getauxval(AT_RANDOM);
-        if (auxv) {
-                assert_cc(sizeof(x) <= 16);
-                memcpy(&x, auxv, sizeof(x));
-        } else
-#endif
-                x = 0;
-
-        x ^= (unsigned) now(CLOCK_REALTIME);
-        x ^= (unsigned) gettid();
-
-        srand(x);
-        srand_called = true;
-}
-
-/* INT_MAX gives us only 31 bits, so use 24 out of that. */
-#if RAND_MAX >= INT_MAX
-#  define RAND_STEP 3
-#else
-/* SHORT_INT_MAX or lower gives at most 15 bits, we just just 8 out of that. */
-#  define RAND_STEP 1
-#endif
-
-void pseudorandom_bytes(void *p, size_t n) {
-        uint8_t *q;
-
-        initialize_srand();
-
-        for (q = p; q < (uint8_t*) p + n; q += RAND_STEP) {
-                unsigned rr;
-
-                rr = (unsigned) rand();
-
-#if RAND_STEP >= 3
-                if ((size_t) (q - (uint8_t*) p + 2) < n)
-                        q[2] = rr >> 16;
-#endif
-#if RAND_STEP >= 2
-                if ((size_t) (q - (uint8_t*) p + 1) < n)
-                        q[1] = rr >> 8;
-#endif
-                q[0] = rr;
-        }
-}
-
-void random_bytes(void *p, size_t n) {
-        int r;
-
-        r = acquire_random_bytes(p, n, false);
-        if (r >= 0)
-                return;
-
-        /* If some idiot made /dev/urandom unavailable to us, or the
-         * kernel has no entropy, use a PRNG instead. */
-        return pseudorandom_bytes(p, n);
-}
diff --git a/src/systemd/src/basic/random-util.h b/src/systemd/src/basic/random-util.h
deleted file mode 100644
index affcc9ac..00000000
--- a/src/systemd/src/basic/random-util.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-
-int acquire_random_bytes(void *p, size_t n, bool high_quality_required);
-void pseudorandom_bytes(void *p, size_t n);
-void random_bytes(void *p, size_t n);
-void initialize_srand(void);
-
-static inline uint64_t random_u64(void) {
-        uint64_t u;
-        random_bytes(&u, sizeof(u));
-        return u;
-}
-
-static inline uint32_t random_u32(void) {
-        uint32_t u;
-        random_bytes(&u, sizeof(u));
-        return u;
-}
-
-int rdrand64(uint64_t *ret);
diff --git a/src/systemd/src/basic/refcnt.h b/src/systemd/src/basic/refcnt.h
deleted file mode 100644
index d2be6086..00000000
--- a/src/systemd/src/basic/refcnt.h
+++ /dev/null
@@ -1,16 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-/* A type-safe atomic refcounter.
- *
- * DO NOT USE THIS UNLESS YOU ACTUALLY CARE ABOUT THREAD SAFETY! */
-
-typedef struct {
-        volatile unsigned _value;
-} RefCount;
-
-#define REFCNT_GET(r) ((r)._value)
-#define REFCNT_INC(r) (__sync_add_and_fetch(&(r)._value, 1))
-#define REFCNT_DEC(r) (__sync_sub_and_fetch(&(r)._value, 1))
-
-#define REFCNT_INIT ((RefCount) { ._value = 1 })
diff --git a/src/systemd/src/basic/set.h b/src/systemd/src/basic/set.h
deleted file mode 100644
index 66471381..00000000
--- a/src/systemd/src/basic/set.h
+++ /dev/null
@@ -1,132 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include "extract-word.h"
-#include "hashmap.h"
-#include "macro.h"
-
-Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
-#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS)
-
-static inline Set *set_free(Set *s) {
-        internal_hashmap_free(HASHMAP_BASE(s));
-        return NULL;
-}
-
-static inline Set *set_free_free(Set *s) {
-        internal_hashmap_free_free(HASHMAP_BASE(s));
-        return NULL;
-}
-
-/* no set_free_free_free */
-
-static inline Set *set_copy(Set *s) {
-        return (Set*) internal_hashmap_copy(HASHMAP_BASE(s));
-}
-
-int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
-#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS)
-
-int set_put(Set *s, const void *key);
-/* no set_update */
-/* no set_replace */
-static inline void *set_get(Set *s, void *key) {
-        return internal_hashmap_get(HASHMAP_BASE(s), key);
-}
-/* no set_get2 */
-
-static inline bool set_contains(Set *s, const void *key) {
-        return internal_hashmap_contains(HASHMAP_BASE(s), key);
-}
-
-static inline void *set_remove(Set *s, const void *key) {
-        return internal_hashmap_remove(HASHMAP_BASE(s), key);
-}
-
-/* no set_remove2 */
-/* no set_remove_value */
-int set_remove_and_put(Set *s, const void *old_key, const void *new_key);
-/* no set_remove_and_replace */
-int set_merge(Set *s, Set *other);
-
-static inline int set_reserve(Set *h, unsigned entries_add) {
-        return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
-}
-
-static inline int set_move(Set *s, Set *other) {
-        return internal_hashmap_move(HASHMAP_BASE(s), HASHMAP_BASE(other));
-}
-
-static inline int set_move_one(Set *s, Set *other, const void *key) {
-        return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key);
-}
-
-static inline unsigned set_size(Set *s) {
-        return internal_hashmap_size(HASHMAP_BASE(s));
-}
-
-static inline bool set_isempty(Set *s) {
-        return set_size(s) == 0;
-}
-
-static inline unsigned set_buckets(Set *s) {
-        return internal_hashmap_buckets(HASHMAP_BASE(s));
-}
-
-bool set_iterate(Set *s, Iterator *i, void **value);
-
-static inline void set_clear(Set *s) {
-        internal_hashmap_clear(HASHMAP_BASE(s));
-}
-
-static inline void set_clear_free(Set *s) {
-        internal_hashmap_clear_free(HASHMAP_BASE(s));
-}
-
-/* no set_clear_free_free */
-
-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 */
-
-static inline void *set_first(Set *s) {
-        return internal_hashmap_first(HASHMAP_BASE(s));
-}
-
-/* no set_next */
-
-static inline char **set_get_strv(Set *s) {
-        return internal_hashmap_get_strv(HASHMAP_BASE(s));
-}
-
-int set_consume(Set *s, void *value);
-int set_put_strdup(Set *s, const char *p);
-int set_put_strdupv(Set *s, char **l);
-int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags flags);
-
-#define SET_FOREACH(e, s, i) \
-        for ((i) = ITERATOR_FIRST; set_iterate((s), &(i), (void**)&(e)); )
-
-#define SET_FOREACH_MOVE(e, d, s)                                       \
-        for (; ({ e = set_first(s); assert_se(!e || set_move_one(d, s, e) >= 0); e; }); )
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free);
-DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free);
-
-#define _cleanup_set_free_ _cleanup_(set_freep)
-#define _cleanup_set_free_free_ _cleanup_(set_free_freep)
diff --git a/src/systemd/src/basic/signal-util.h b/src/systemd/src/basic/signal-util.h
deleted file mode 100644
index 92f2804c..00000000
--- a/src/systemd/src/basic/signal-util.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <signal.h>
-
-#include "macro.h"
-
-int reset_all_signal_handlers(void);
-int reset_signal_mask(void);
-
-int ignore_signals(int sig, ...);
-int default_signals(int sig, ...);
-int sigaction_many(const struct sigaction *sa, ...);
-
-int sigset_add_many(sigset_t *ss, ...);
-int sigprocmask_many(int how, sigset_t *old, ...);
-
-const char *signal_to_string(int i) _const_;
-int signal_from_string(const char *s) _pure_;
-
-void nop_signal_handler(int sig);
-
-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;                                                                \
-        })
-
-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/siphash24.h b/src/systemd/src/basic/siphash24.h
deleted file mode 100644
index 77bb9a8c..00000000
--- a/src/systemd/src/basic/siphash24.h
+++ /dev/null
@@ -1,53 +0,0 @@
-#pragma once
-
-#include <inttypes.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/types.h>
-
-#if 0 /* NM_IGNORED */
-struct siphash {
-        uint64_t v0;
-        uint64_t v1;
-        uint64_t v2;
-        uint64_t v3;
-        uint64_t padding;
-        size_t inlen;
-};
-#else /* NM_IGNORED */
-struct siphash {
-	CSipHash _csiphash;
-};
-
-static inline void
-siphash24_init (struct siphash *state, const uint8_t k[16])
-{
-	c_siphash_init ((CSipHash *) state, k);
-}
-
-static inline void
-siphash24_compress (const void *in, size_t inlen, struct siphash *state)
-{
-	c_siphash_append ((CSipHash *) state, in, inlen);
-}
-
-static inline uint64_t
-siphash24_finalize (struct siphash *state)
-{
-	return c_siphash_finalize ((CSipHash *) state);
-}
-
-static inline uint64_t
-siphash24 (const void *in, size_t inlen, const uint8_t k[16])
-{
-	return c_siphash_hash (k, in, inlen);
-}
-#endif /* NM_IGNORED */
-
-void siphash24_init(struct siphash *state, const uint8_t k[16]);
-void siphash24_compress(const void *in, size_t inlen, struct siphash *state);
-#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state))
-
-uint64_t siphash24_finalize(struct siphash *state);
-
-uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]);
diff --git a/src/systemd/src/basic/socket-util.c b/src/systemd/src/basic/socket-util.c
deleted file mode 100644
index acc22901..00000000
--- a/src/systemd/src/basic/socket-util.c
+++ /dev/null
@@ -1,1254 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <limits.h>
-#include <net/if.h>
-#include <netdb.h>
-#include <netinet/ip.h>
-#include <poll.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "alloc-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "format-util.h"
-#include "log.h"
-#include "macro.h"
-#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"
-#include "strv.h"
-#include "user-util.h"
-#include "utf8.h"
-#include "util.h"
-
-#if 0 /* NM_IGNORED */
-#if ENABLE_IDN
-#  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) {
-        _cleanup_free_ char *n = NULL;
-        char *e;
-        int r;
-
-        assert(a);
-        assert(s);
-
-        zero(*a);
-        a->type = SOCK_STREAM;
-
-        if (*s == '[') {
-                uint16_t port;
-
-                /* IPv6 in [x:.....:z]:p notation */
-
-                e = strchr(s+1, ']');
-                if (!e)
-                        return -EINVAL;
-
-                n = strndup(s+1, e-s-1);
-                if (!n)
-                        return -ENOMEM;
-
-                errno = 0;
-                if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0)
-                        return errno > 0 ? -errno : -EINVAL;
-
-                e++;
-                if (*e != ':')
-                        return -EINVAL;
-
-                e++;
-                r = parse_ip_port(e, &port);
-                if (r < 0)
-                        return r;
-
-                a->sockaddr.in6.sin6_family = AF_INET6;
-                a->sockaddr.in6.sin6_port = htobe16(port);
-                a->size = sizeof(struct sockaddr_in6);
-
-        } else if (*s == '/') {
-                /* AF_UNIX socket */
-
-                size_t l;
-
-                l = strlen(s);
-                if (l >= sizeof(a->sockaddr.un.sun_path))
-                        return -EINVAL;
-
-                a->sockaddr.un.sun_family = AF_UNIX;
-                memcpy(a->sockaddr.un.sun_path, s, l);
-                a->size = offsetof(struct sockaddr_un, sun_path) + l + 1;
-
-        } else if (*s == '@') {
-                /* Abstract AF_UNIX socket */
-                size_t l;
-
-                l = strlen(s+1);
-                if (l >= sizeof(a->sockaddr.un.sun_path) - 1)
-                        return -EINVAL;
-
-                a->sockaddr.un.sun_family = AF_UNIX;
-                memcpy(a->sockaddr.un.sun_path+1, s+1, l);
-                a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l;
-
-        } else if (startswith(s, "vsock:")) {
-                /* AF_VSOCK socket in vsock:cid:port notation */
-                const char *cid_start = s + STRLEN("vsock:");
-                unsigned port;
-
-                e = strchr(cid_start, ':');
-                if (!e)
-                        return -EINVAL;
-
-                r = safe_atou(e+1, &port);
-                if (r < 0)
-                        return r;
-
-                n = strndup(cid_start, e - cid_start);
-                if (!n)
-                        return -ENOMEM;
-
-                if (!isempty(n)) {
-                        r = safe_atou(n, &a->sockaddr.vm.svm_cid);
-                        if (r < 0)
-                                return r;
-                } else
-                        a->sockaddr.vm.svm_cid = VMADDR_CID_ANY;
-
-                a->sockaddr.vm.svm_family = AF_VSOCK;
-                a->sockaddr.vm.svm_port = port;
-                a->size = sizeof(struct sockaddr_vm);
-
-        } else {
-                uint16_t port;
-
-                e = strchr(s, ':');
-                if (e) {
-                        r = parse_ip_port(e + 1, &port);
-                        if (r < 0)
-                                return r;
-
-                        n = strndup(s, e-s);
-                        if (!n)
-                                return -ENOMEM;
-
-                        /* IPv4 in w.x.y.z:p notation? */
-                        r = inet_pton(AF_INET, n, &a->sockaddr.in.sin_addr);
-                        if (r < 0)
-                                return -errno;
-
-                        if (r > 0) {
-                                /* Gotcha, it's a traditional IPv4 address */
-                                a->sockaddr.in.sin_family = AF_INET;
-                                a->sockaddr.in.sin_port = htobe16(port);
-                                a->size = sizeof(struct sockaddr_in);
-                        } else {
-                                unsigned idx;
-
-                                if (strlen(n) > IF_NAMESIZE-1)
-                                        return -EINVAL;
-
-                                /* Uh, our last resort, an interface name */
-                                idx = if_nametoindex(n);
-                                if (idx == 0)
-                                        return -EINVAL;
-
-                                a->sockaddr.in6.sin6_family = AF_INET6;
-                                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);
-                        }
-                } else {
-
-                        /* Just a port */
-                        r = parse_ip_port(s, &port);
-                        if (r < 0)
-                                return r;
-
-                        if (socket_ipv6_is_supported()) {
-                                a->sockaddr.in6.sin6_family = AF_INET6;
-                                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(port);
-                                a->sockaddr.in.sin_addr.s_addr = INADDR_ANY;
-                                a->size = sizeof(struct sockaddr_in);
-                        }
-                }
-        }
-
-        return 0;
-}
-
-int socket_address_parse_and_warn(SocketAddress *a, const char *s) {
-        SocketAddress b;
-        int r;
-
-        /* Similar to socket_address_parse() but warns for IPv6 sockets when we don't support them. */
-
-        r = socket_address_parse(&b, s);
-        if (r < 0)
-                return r;
-
-        if (!socket_ipv6_is_supported() && b.sockaddr.sa.sa_family == AF_INET6) {
-                log_warning("Binding to IPv6 address not available since kernel does not support IPv6.");
-                return -EAFNOSUPPORT;
-        }
-
-        *a = b;
-        return 0;
-}
-
-int socket_address_parse_netlink(SocketAddress *a, const char *s) {
-        int family;
-        unsigned group = 0;
-        _cleanup_free_ char *sfamily = NULL;
-        assert(a);
-        assert(s);
-
-        zero(*a);
-        a->type = SOCK_RAW;
-
-        errno = 0;
-        if (sscanf(s, "%ms %u", &sfamily, &group) < 1)
-                return errno > 0 ? -errno : -EINVAL;
-
-        family = netlink_family_from_string(sfamily);
-        if (family < 0)
-                return -EINVAL;
-
-        a->sockaddr.nl.nl_family = AF_NETLINK;
-        a->sockaddr.nl.nl_groups = group;
-
-        a->type = SOCK_RAW;
-        a->size = sizeof(struct sockaddr_nl);
-        a->protocol = family;
-
-        return 0;
-}
-
-int socket_address_verify(const SocketAddress *a) {
-        assert(a);
-
-        switch (socket_address_family(a)) {
-
-        case AF_INET:
-                if (a->size != sizeof(struct sockaddr_in))
-                        return -EINVAL;
-
-                if (a->sockaddr.in.sin_port == 0)
-                        return -EINVAL;
-
-                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM))
-                        return -EINVAL;
-
-                return 0;
-
-        case AF_INET6:
-                if (a->size != sizeof(struct sockaddr_in6))
-                        return -EINVAL;
-
-                if (a->sockaddr.in6.sin6_port == 0)
-                        return -EINVAL;
-
-                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM))
-                        return -EINVAL;
-
-                return 0;
-
-        case AF_UNIX:
-                if (a->size < offsetof(struct sockaddr_un, sun_path))
-                        return -EINVAL;
-
-                if (a->size > offsetof(struct sockaddr_un, sun_path)) {
-
-                        if (a->sockaddr.un.sun_path[0] != 0) {
-                                char *e;
-
-                                /* path */
-                                e = memchr(a->sockaddr.un.sun_path, 0, sizeof(a->sockaddr.un.sun_path));
-                                if (!e)
-                                        return -EINVAL;
-
-                                if (a->size != offsetof(struct sockaddr_un, sun_path) + (e - a->sockaddr.un.sun_path) + 1)
-                                        return -EINVAL;
-                        }
-                }
-
-                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET))
-                        return -EINVAL;
-
-                return 0;
-
-        case AF_NETLINK:
-
-                if (a->size != sizeof(struct sockaddr_nl))
-                        return -EINVAL;
-
-                if (!IN_SET(a->type, SOCK_RAW, SOCK_DGRAM))
-                        return -EINVAL;
-
-                return 0;
-
-        case AF_VSOCK:
-                if (a->size != sizeof(struct sockaddr_vm))
-                        return -EINVAL;
-
-                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM))
-                        return -EINVAL;
-
-                return 0;
-
-        default:
-                return -EAFNOSUPPORT;
-        }
-}
-
-int socket_address_print(const SocketAddress *a, char **ret) {
-        int r;
-
-        assert(a);
-        assert(ret);
-
-        r = socket_address_verify(a);
-        if (r < 0)
-                return r;
-
-        if (socket_address_family(a) == AF_NETLINK) {
-                _cleanup_free_ char *sfamily = NULL;
-
-                r = netlink_family_to_string_alloc(a->protocol, &sfamily);
-                if (r < 0)
-                        return r;
-
-                r = asprintf(ret, "%s %u", sfamily, a->sockaddr.nl.nl_groups);
-                if (r < 0)
-                        return -ENOMEM;
-
-                return 0;
-        }
-
-        return sockaddr_pretty(&a->sockaddr.sa, a->size, false, true, ret);
-}
-
-bool socket_address_can_accept(const SocketAddress *a) {
-        assert(a);
-
-        return
-                IN_SET(a->type, SOCK_STREAM, SOCK_SEQPACKET);
-}
-
-bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) {
-        assert(a);
-        assert(b);
-
-        /* Invalid addresses are unequal to all */
-        if (socket_address_verify(a) < 0 ||
-            socket_address_verify(b) < 0)
-                return false;
-
-        if (a->type != b->type)
-                return false;
-
-        if (socket_address_family(a) != socket_address_family(b))
-                return false;
-
-        switch (socket_address_family(a)) {
-
-        case AF_INET:
-                if (a->sockaddr.in.sin_addr.s_addr != b->sockaddr.in.sin_addr.s_addr)
-                        return false;
-
-                if (a->sockaddr.in.sin_port != b->sockaddr.in.sin_port)
-                        return false;
-
-                break;
-
-        case AF_INET6:
-                if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0)
-                        return false;
-
-                if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port)
-                        return false;
-
-                break;
-
-        case AF_UNIX:
-                if (a->size <= offsetof(struct sockaddr_un, sun_path) ||
-                    b->size <= offsetof(struct sockaddr_un, sun_path))
-                        return false;
-
-                if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0))
-                        return false;
-
-                if (a->sockaddr.un.sun_path[0]) {
-                        if (!path_equal_or_files_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, 0))
-                                return false;
-                } else {
-                        if (a->size != b->size)
-                                return false;
-
-                        if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, a->size) != 0)
-                                return false;
-                }
-
-                break;
-
-        case AF_NETLINK:
-                if (a->protocol != b->protocol)
-                        return false;
-
-                if (a->sockaddr.nl.nl_groups != b->sockaddr.nl.nl_groups)
-                        return false;
-
-                break;
-
-        case AF_VSOCK:
-                if (a->sockaddr.vm.svm_cid != b->sockaddr.vm.svm_cid)
-                        return false;
-
-                if (a->sockaddr.vm.svm_port != b->sockaddr.vm.svm_port)
-                        return false;
-
-                break;
-
-        default:
-                /* Cannot compare, so we assume the addresses are different */
-                return false;
-        }
-
-        return true;
-}
-
-bool socket_address_is(const SocketAddress *a, const char *s, int type) {
-        struct SocketAddress b;
-
-        assert(a);
-        assert(s);
-
-        if (socket_address_parse(&b, s) < 0)
-                return false;
-
-        b.type = type;
-
-        return socket_address_equal(a, &b);
-}
-
-bool socket_address_is_netlink(const SocketAddress *a, const char *s) {
-        struct SocketAddress b;
-
-        assert(a);
-        assert(s);
-
-        if (socket_address_parse_netlink(&b, s) < 0)
-                return false;
-
-        return socket_address_equal(a, &b);
-}
-
-const char* socket_address_get_path(const SocketAddress *a) {
-        assert(a);
-
-        if (socket_address_family(a) != AF_UNIX)
-                return NULL;
-
-        if (a->sockaddr.un.sun_path[0] == 0)
-                return NULL;
-
-        return a->sockaddr.un.sun_path;
-}
-
-bool socket_ipv6_is_supported(void) {
-        if (access("/proc/net/if_inet6", F_OK) != 0)
-                return false;
-
-        return true;
-}
-
-bool socket_address_matches_fd(const SocketAddress *a, int fd) {
-        SocketAddress b;
-        socklen_t solen;
-
-        assert(a);
-        assert(fd >= 0);
-
-        b.size = sizeof(b.sockaddr);
-        if (getsockname(fd, &b.sockaddr.sa, &b.size) < 0)
-                return false;
-
-        if (b.sockaddr.sa.sa_family != a->sockaddr.sa.sa_family)
-                return false;
-
-        solen = sizeof(b.type);
-        if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &b.type, &solen) < 0)
-                return false;
-
-        if (b.type != a->type)
-                return false;
-
-        if (a->protocol != 0)  {
-                solen = sizeof(b.protocol);
-                if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &b.protocol, &solen) < 0)
-                        return false;
-
-                if (b.protocol != a->protocol)
-                        return false;
-        }
-
-        return socket_address_equal(a, &b);
-}
-
-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:
-                *ret_port = be16toh(sa->in.sin_port);
-                return 0;
-
-        case AF_INET6:
-                *ret_port = be16toh(sa->in6.sin6_port);
-                return 0;
-
-        case AF_VSOCK:
-                *ret_port = sa->vm.svm_port;
-                return 0;
-
-        default:
-                return -EAFNOSUPPORT;
-        }
-}
-
-int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret) {
-        union sockaddr_union *sa = (union sockaddr_union*) _sa;
-        char *p;
-        int r;
-
-        assert(sa);
-        assert(salen >= sizeof(sa->sa.sa_family));
-
-        switch (sa->sa.sa_family) {
-
-        case AF_INET: {
-                uint32_t a;
-
-                a = be32toh(sa->in.sin_addr.s_addr);
-
-                if (include_port)
-                        r = asprintf(&p,
-                                     "%u.%u.%u.%u:%u",
-                                     a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF,
-                                     be16toh(sa->in.sin_port));
-                else
-                        r = asprintf(&p,
-                                     "%u.%u.%u.%u",
-                                     a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF);
-                if (r < 0)
-                        return -ENOMEM;
-                break;
-        }
-
-        case AF_INET6: {
-                static const unsigned char ipv4_prefix[] = {
-                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF
-                };
-
-                if (translate_ipv6 &&
-                    memcmp(&sa->in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) {
-                        const uint8_t *a = sa->in6.sin6_addr.s6_addr+12;
-                        if (include_port)
-                                r = asprintf(&p,
-                                             "%u.%u.%u.%u:%u",
-                                             a[0], a[1], a[2], a[3],
-                                             be16toh(sa->in6.sin6_port));
-                        else
-                                r = asprintf(&p,
-                                             "%u.%u.%u.%u",
-                                             a[0], a[1], a[2], a[3]);
-                        if (r < 0)
-                                return -ENOMEM;
-                } else {
-                        char a[INET6_ADDRSTRLEN];
-
-                        inet_ntop(AF_INET6, &sa->in6.sin6_addr, a, sizeof(a));
-
-                        if (include_port) {
-                                r = asprintf(&p,
-                                             "[%s]:%u",
-                                             a,
-                                             be16toh(sa->in6.sin6_port));
-                                if (r < 0)
-                                        return -ENOMEM;
-                        } else {
-                                p = strdup(a);
-                                if (!p)
-                                        return -ENOMEM;
-                        }
-                }
-
-                break;
-        }
-
-        case AF_UNIX:
-                if (salen <= offsetof(struct sockaddr_un, sun_path)) {
-                        p = strdup("<unnamed>");
-                        if (!p)
-                                return -ENOMEM;
-
-                } else if (sa->un.sun_path[0] == 0) {
-                        /* abstract */
-
-                        /* FIXME: We assume we can print the
-                         * socket path here and that it hasn't
-                         * more than one NUL byte. That is
-                         * actually an invalid assumption */
-
-                        p = new(char, sizeof(sa->un.sun_path)+1);
-                        if (!p)
-                                return -ENOMEM;
-
-                        p[0] = '@';
-                        memcpy(p+1, sa->un.sun_path+1, sizeof(sa->un.sun_path)-1);
-                        p[sizeof(sa->un.sun_path)] = 0;
-
-                } else {
-                        p = strndup(sa->un.sun_path, sizeof(sa->un.sun_path));
-                        if (!p)
-                                return -ENOMEM;
-                }
-
-                break;
-
-        case AF_VSOCK:
-                if (include_port)
-                        r = asprintf(&p,
-                                     "vsock:%u:%u",
-                                     sa->vm.svm_cid,
-                                     sa->vm.svm_port);
-                else
-                        r = asprintf(&p, "vsock:%u", sa->vm.svm_cid);
-                if (r < 0)
-                        return -ENOMEM;
-                break;
-
-        default:
-                return -EOPNOTSUPP;
-        }
-
-        *ret = p;
-        return 0;
-}
-
-int getpeername_pretty(int fd, bool include_port, char **ret) {
-        union sockaddr_union sa;
-        socklen_t salen = sizeof(sa);
-        int r;
-
-        assert(fd >= 0);
-        assert(ret);
-
-        if (getpeername(fd, &sa.sa, &salen) < 0)
-                return -errno;
-
-        if (sa.sa.sa_family == AF_UNIX) {
-                struct ucred ucred = {};
-
-                /* UNIX connection sockets are anonymous, so let's use
-                 * PID/UID as pretty credentials instead */
-
-                r = getpeercred(fd, &ucred);
-                if (r < 0)
-                        return r;
-
-                if (asprintf(ret, "PID "PID_FMT"/UID "UID_FMT, ucred.pid, ucred.uid) < 0)
-                        return -ENOMEM;
-
-                return 0;
-        }
-
-        /* For remote sockets we translate IPv6 addresses back to IPv4
-         * if applicable, since that's nicer. */
-
-        return sockaddr_pretty(&sa.sa, salen, true, include_port, ret);
-}
-
-int getsockname_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;
-
-        /* For local sockets we do not translate IPv6 addresses back
-         * to IPv6 if applicable, since this is usually used for
-         * listening sockets where the difference between IPv4 and
-         * IPv6 matters. */
-
-        return sockaddr_pretty(&sa.sa, salen, false, true, ret);
-}
-
-int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) {
-        int r;
-        char host[NI_MAXHOST], *ret;
-
-        assert(_ret);
-
-        r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, IDN_FLAGS);
-        if (r != 0) {
-                int saved_errno = errno;
-
-                r = sockaddr_pretty(&sa->sa, salen, true, true, &ret);
-                if (r < 0)
-                        return r;
-
-                log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret);
-        } else {
-                ret = strdup(host);
-                if (!ret)
-                        return -ENOMEM;
-        }
-
-        *_ret = ret;
-        return 0;
-}
-
-int socket_address_unlink(SocketAddress *a) {
-        assert(a);
-
-        if (socket_address_family(a) != AF_UNIX)
-                return 0;
-
-        if (a->sockaddr.un.sun_path[0] == 0)
-                return 0;
-
-        if (unlink(a->sockaddr.un.sun_path) < 0)
-                return -errno;
-
-        return 1;
-}
-
-static const char* const netlink_family_table[] = {
-        [NETLINK_ROUTE] = "route",
-        [NETLINK_FIREWALL] = "firewall",
-        [NETLINK_INET_DIAG] = "inet-diag",
-        [NETLINK_NFLOG] = "nflog",
-        [NETLINK_XFRM] = "xfrm",
-        [NETLINK_SELINUX] = "selinux",
-        [NETLINK_ISCSI] = "iscsi",
-        [NETLINK_AUDIT] = "audit",
-        [NETLINK_FIB_LOOKUP] = "fib-lookup",
-        [NETLINK_CONNECTOR] = "connector",
-        [NETLINK_NETFILTER] = "netfilter",
-        [NETLINK_IP6_FW] = "ip6-fw",
-        [NETLINK_DNRTMSG] = "dnrtmsg",
-        [NETLINK_KOBJECT_UEVENT] = "kobject-uevent",
-        [NETLINK_GENERIC] = "generic",
-        [NETLINK_SCSITRANSPORT] = "scsitransport",
-        [NETLINK_ECRYPTFS] = "ecryptfs",
-        [NETLINK_RDMA] = "rdma",
-};
-
-DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(netlink_family, int, INT_MAX);
-
-static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = {
-        [SOCKET_ADDRESS_DEFAULT] = "default",
-        [SOCKET_ADDRESS_BOTH] = "both",
-        [SOCKET_ADDRESS_IPV6_ONLY] = "ipv6-only"
-};
-
-DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only);
-
-SocketAddressBindIPv6Only socket_address_bind_ipv6_only_or_bool_from_string(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);
-
-        if (a->sa.sa_family != b->sa.sa_family)
-                return false;
-
-        if (a->sa.sa_family == AF_INET)
-                return a->in.sin_addr.s_addr == b->in.sin_addr.s_addr;
-
-        if (a->sa.sa_family == AF_INET6)
-                return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)) == 0;
-
-        if (a->sa.sa_family == AF_VSOCK)
-                return a->vm.svm_cid == b->vm.svm_cid;
-
-        return false;
-}
-
-int fd_inc_sndbuf(int fd, size_t n) {
-        int r, value;
-        socklen_t l = sizeof(value);
-
-        r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l);
-        if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2)
-                return 0;
-
-        /* If we have the privileges we will ignore the kernel limit. */
-
-        value = (int) n;
-        if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0)
-                if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0)
-                        return -errno;
-
-        return 1;
-}
-
-int fd_inc_rcvbuf(int fd, size_t n) {
-        int r, value;
-        socklen_t l = sizeof(value);
-
-        r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l);
-        if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2)
-                return 0;
-
-        /* If we have the privileges we will ignore the kernel limit. */
-
-        value = (int) n;
-        if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0)
-                if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0)
-                        return -errno;
-        return 1;
-}
-
-static const char* const ip_tos_table[] = {
-        [IPTOS_LOWDELAY] = "low-delay",
-        [IPTOS_THROUGHPUT] = "throughput",
-        [IPTOS_RELIABILITY] = "reliability",
-        [IPTOS_LOWCOST] = "low-cost",
-};
-
-DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff);
-
-bool ifname_valid(const char *p) {
-        bool numeric = true;
-
-        /* Checks whether a network interface name is valid. This is inspired by dev_valid_name() in the kernel sources
-         * but slightly stricter, as we only allow non-control, non-space ASCII characters in the interface name. We
-         * also don't permit names that only container numbers, to avoid confusion with numeric interface indexes. */
-
-        if (isempty(p))
-                return false;
-
-        if (strlen(p) >= IFNAMSIZ)
-                return false;
-
-        if (dot_or_dot_dot(p))
-                return false;
-
-        while (*p) {
-                if ((unsigned char) *p >= 127U)
-                        return false;
-
-                if ((unsigned char) *p <= 32U)
-                        return false;
-
-                if (IN_SET(*p, ':', '/'))
-                        return false;
-
-                numeric = numeric && (*p >= '0' && *p <= '9');
-                p++;
-        }
-
-        if (numeric)
-                return false;
-
-        return true;
-}
-
-bool address_label_valid(const char *p) {
-
-        if (isempty(p))
-                return false;
-
-        if (strlen(p) >= IFNAMSIZ)
-                return false;
-
-        while (*p) {
-                if ((uint8_t) *p >= 127U)
-                        return false;
-
-                if ((uint8_t) *p <= 31U)
-                        return false;
-                p++;
-        }
-
-        return true;
-}
-
-int getpeercred(int fd, struct ucred *ucred) {
-        socklen_t n = sizeof(struct ucred);
-        struct ucred u;
-        int r;
-
-        assert(fd >= 0);
-        assert(ucred);
-
-        r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n);
-        if (r < 0)
-                return -errno;
-
-        if (n != sizeof(struct ucred))
-                return -EIO;
-
-        /* 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;
-
-        assert(fd >= 0);
-        assert(ret);
-
-        for (;;) {
-                s = new0(char, n+1);
-                if (!s)
-                        return -ENOMEM;
-
-                if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0)
-                        break;
-
-                if (errno != ERANGE)
-                        return -errno;
-
-                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;
-
-                if (getsockopt(fd, SOL_SOCKET, SO_PEERGROUPS, d, &n) >= 0)
-                        break;
-
-                if (errno != ERANGE)
-                        return -errno;
-
-                d = mfree(d);
-        }
-
-        assert_se(n % sizeof(gid_t) == 0);
-        n /= sizeof(gid_t);
-
-        if ((socklen_t) (int) n != n)
-                return -E2BIG;
-
-        *ret = TAKE_PTR(d);
-
-        return (int) n;
-}
-
-ssize_t send_one_fd_iov_sa(
-                int transport_fd,
-                int fd,
-                struct iovec *iov, size_t iovlen,
-                const struct sockaddr *sa, socklen_t len,
-                int flags) {
-
-        union {
-                struct cmsghdr cmsghdr;
-                uint8_t buf[CMSG_SPACE(sizeof(int))];
-        } control = {};
-        struct msghdr mh = {
-                .msg_name = (struct sockaddr*) sa,
-                .msg_namelen = len,
-                .msg_iov = iov,
-                .msg_iovlen = iovlen,
-        };
-        ssize_t k;
-
-        assert(transport_fd >= 0);
-
-        /*
-         * We need either an FD or data to send.
-         * If there's nothing, return an error.
-         */
-        if (fd < 0 && !iov)
-                return -EINVAL;
-
-        if (fd >= 0) {
-                struct cmsghdr *cmsg;
-
-                mh.msg_control = &control;
-                mh.msg_controllen = sizeof(control);
-
-                cmsg = CMSG_FIRSTHDR(&mh);
-                cmsg->cmsg_level = SOL_SOCKET;
-                cmsg->cmsg_type = SCM_RIGHTS;
-                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
-                memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
-
-                mh.msg_controllen = CMSG_SPACE(sizeof(int));
-        }
-        k = sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags);
-        if (k < 0)
-                return (ssize_t) -errno;
-
-        return k;
-}
-
-int send_one_fd_sa(
-                int transport_fd,
-                int fd,
-                const struct sockaddr *sa, socklen_t len,
-                int flags) {
-
-        assert(fd >= 0);
-
-        return (int) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, sa, len, flags);
-}
-
-ssize_t receive_one_fd_iov(
-                int transport_fd,
-                struct iovec *iov, size_t iovlen,
-                int flags,
-                int *ret_fd) {
-
-        union {
-                struct cmsghdr cmsghdr;
-                uint8_t buf[CMSG_SPACE(sizeof(int))];
-        } control = {};
-        struct msghdr mh = {
-                .msg_control = &control,
-                .msg_controllen = sizeof(control),
-                .msg_iov = iov,
-                .msg_iovlen = iovlen,
-        };
-        struct cmsghdr *cmsg, *found = NULL;
-        ssize_t k;
-
-        assert(transport_fd >= 0);
-        assert(ret_fd);
-
-        /*
-         * Receive a single FD via @transport_fd. We don't care for
-         * the transport-type. We retrieve a single FD at most, so for
-         * packet-based transports, the caller must ensure to send
-         * only a single FD per packet.  This is best used in
-         * combination with send_one_fd().
-         */
-
-        k = recvmsg(transport_fd, &mh, MSG_CMSG_CLOEXEC | flags);
-        if (k < 0)
-                return (ssize_t) -errno;
-
-        CMSG_FOREACH(cmsg, &mh) {
-                if (cmsg->cmsg_level == SOL_SOCKET &&
-                    cmsg->cmsg_type == SCM_RIGHTS &&
-                    cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
-                        assert(!found);
-                        found = cmsg;
-                        break;
-                }
-        }
-
-        if (!found)
-                cmsg_close_all(&mh);
-
-        /* If didn't receive an FD or any data, return an error. */
-        if (k == 0 && !found)
-                return -EIO;
-
-        if (found)
-                *ret_fd = *(int*) CMSG_DATA(found);
-        else
-                *ret_fd = -1;
-
-        return k;
-}
-
-int receive_one_fd(int transport_fd, int flags) {
-        int fd;
-        ssize_t k;
-
-        k = receive_one_fd_iov(transport_fd, NULL, 0, flags, &fd);
-        if (k == 0)
-                return fd;
-
-        /* k must be negative, since receive_one_fd_iov() only returns
-         * a positive value if data was received through the iov. */
-        assert(k < 0);
-        return (int) k;
-}
-#endif /* NM_IGNORED */
-
-ssize_t next_datagram_size_fd(int fd) {
-        ssize_t l;
-        int k;
-
-        /* This is a bit like FIONREAD/SIOCINQ, however a bit more powerful. The difference being: recv(MSG_PEEK) will
-         * actually cause the next datagram in the queue to be validated regarding checksums, which FIONREAD doesn't
-         * do. This difference is actually of major importance as we need to be sure that the size returned here
-         * actually matches what we will read with recvmsg() next, as otherwise we might end up allocating a buffer of
-         * the wrong size. */
-
-        l = recv(fd, NULL, 0, MSG_PEEK|MSG_TRUNC);
-        if (l < 0) {
-                if (IN_SET(errno, EOPNOTSUPP, EFAULT))
-                        goto fallback;
-
-                return -errno;
-        }
-        if (l == 0)
-                goto fallback;
-
-        return l;
-
-fallback:
-        k = 0;
-
-        /* Some sockets (AF_PACKET) do not support null-sized recv() with MSG_TRUNC set, let's fall back to FIONREAD
-         * for them. Checksums don't matter for raw sockets anyway, hence this should be fine. */
-
-        if (ioctl(fd, FIONREAD, &k) < 0)
-                return -errno;
-
-        return (ssize_t) k;
-}
-
-#if 0 /* NM_IGNORED */
-int flush_accept(int fd) {
-
-        struct pollfd pollfd = {
-                .fd = fd,
-                .events = POLLIN,
-        };
-        int r;
-
-        /* Similar to flush_fd() but flushes all incoming connection by accepting them and immediately closing them. */
-
-        for (;;) {
-                int cfd;
-
-                r = poll(&pollfd, 1, 0);
-                if (r < 0) {
-                        if (errno == EINTR)
-                                continue;
-
-                        return -errno;
-
-                } else if (r == 0)
-                        return 0;
-
-                cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
-                if (cfd < 0) {
-                        if (errno == EINTR)
-                                continue;
-
-                        if (errno == EAGAIN)
-                                return 0;
-
-                        return -errno;
-                }
-
-                close(cfd);
-        }
-}
-
-struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length) {
-        struct cmsghdr *cmsg;
-
-        assert(mh);
-
-        CMSG_FOREACH(cmsg, mh)
-                if (cmsg->cmsg_level == level &&
-                    cmsg->cmsg_type == type &&
-                    (length == (socklen_t) -1 || length == cmsg->cmsg_len))
-                        return cmsg;
-
-        return NULL;
-}
-
-int socket_ioctl_fd(void) {
-        int fd;
-
-        /* Create a socket to invoke the various network interface ioctl()s on. Traditionally only AF_INET was good for
-         * that. Since kernel 4.6 AF_NETLINK works for this too. We first try to use AF_INET hence, but if that's not
-         * available (for example, because it is made unavailable via SECCOMP or such), we'll fall back to the more
-         * generic AF_NETLINK. */
-
-        fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, 0);
-        if (fd < 0)
-                fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_GENERIC);
-        if (fd < 0)
-                return -errno;
-
-        return fd;
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/socket-util.h b/src/systemd/src/basic/socket-util.h
deleted file mode 100644
index d7b814ae..00000000
--- a/src/systemd/src/basic/socket-util.h
+++ /dev/null
@@ -1,187 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <netinet/ether.h>
-#include <netinet/in.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-#include <linux/netlink.h>
-#include <linux/if_infiniband.h>
-#include <linux/if_packet.h>
-
-#include "macro.h"
-#include "missing.h"
-#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_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 {
-        union sockaddr_union sockaddr;
-
-        /* We store the size here explicitly due to the weird
-         * sockaddr_un semantics for abstract sockets */
-        socklen_t size;
-
-        /* Socket type, i.e. SOCK_STREAM, SOCK_DGRAM, ... */
-        int type;
-
-        /* Socket protocol, IPPROTO_xxx, usually 0, except for netlink */
-        int protocol;
-} SocketAddress;
-
-typedef enum SocketAddressBindIPv6Only {
-        SOCKET_ADDRESS_DEFAULT,
-        SOCKET_ADDRESS_BOTH,
-        SOCKET_ADDRESS_IPV6_ONLY,
-        _SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX,
-        _SOCKET_ADDRESS_BIND_IPV6_ONLY_INVALID = -1
-} 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);
-int socket_address_print(const SocketAddress *a, char **p);
-int socket_address_verify(const SocketAddress *a) _pure_;
-int socket_address_unlink(SocketAddress *a);
-
-bool socket_address_can_accept(const SocketAddress *a) _pure_;
-
-int socket_address_listen(
-                const SocketAddress *a,
-                int flags,
-                int backlog,
-                SocketAddressBindIPv6Only only,
-                const char *bind_to_device,
-                bool reuse_port,
-                bool free_bind,
-                bool transparent,
-                mode_t directory_mode,
-                mode_t socket_mode,
-                const char *label);
-int make_socket_fd(int log_level, const char* address, int type, int flags);
-
-bool socket_address_is(const SocketAddress *a, const char *s, int type);
-bool socket_address_is_netlink(const SocketAddress *a, const char *s);
-
-bool socket_address_matches_fd(const SocketAddress *a, int fd);
-
-bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_;
-
-const char* socket_address_get_path(const SocketAddress *a);
-
-bool socket_ipv6_is_supported(void);
-
-int sockaddr_port(const struct sockaddr *_sa, unsigned *port);
-
-int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret);
-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);
-
-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 socket_address_bind_ipv6_only_or_bool_from_string(const char *s);
-
-int netlink_family_to_string_alloc(int b, char **s);
-int netlink_family_from_string(const char *s) _pure_;
-
-bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b);
-
-int fd_inc_sndbuf(int fd, size_t n);
-int fd_inc_rcvbuf(int fd, size_t n);
-
-int ip_tos_to_string_alloc(int i, char **s);
-int ip_tos_from_string(const char *s);
-
-bool ifname_valid(const char *p);
-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);
-
-ssize_t send_one_fd_iov_sa(
-                int transport_fd,
-                int fd,
-                struct iovec *iov, size_t iovlen,
-                const struct sockaddr *sa, socklen_t len,
-                int flags);
-int send_one_fd_sa(int transport_fd,
-                   int fd,
-                   const struct sockaddr *sa, socklen_t len,
-                   int flags);
-#define send_one_fd_iov(transport_fd, fd, iov, iovlen, flags) send_one_fd_iov_sa(transport_fd, fd, iov, iovlen, NULL, 0, flags)
-#define send_one_fd(transport_fd, fd, flags) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, NULL, 0, flags)
-ssize_t receive_one_fd_iov(int transport_fd, struct iovec *iov, size_t iovlen, int flags, int *ret_fd);
-int receive_one_fd(int transport_fd, int flags);
-
-ssize_t next_datagram_size_fd(int fd);
-
-int flush_accept(int fd);
-
-#define CMSG_FOREACH(cmsg, mh)                                          \
-        for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg)))
-
-struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
-
-/*
- * Certain hardware address types (e.g Infiniband) do not fit into sll_addr
- * (8 bytes) and run over the structure. This macro returns the correct size that
- * must be passed to kernel.
- */
-#define SOCKADDR_LL_LEN(sa)                                             \
-        ({                                                              \
-                const struct sockaddr_ll *_sa = &(sa);                  \
-                size_t _mac_len = sizeof(_sa->sll_addr);                \
-                assert(_sa->sll_family == AF_PACKET);                   \
-                if (be16toh(_sa->sll_hatype) == ARPHRD_ETHER)           \
-                        _mac_len = MAX(_mac_len, (size_t) ETH_ALEN);    \
-                if (be16toh(_sa->sll_hatype) == ARPHRD_INFINIBAND)      \
-                        _mac_len = MAX(_mac_len, (size_t) INFINIBAND_ALEN); \
-                offsetof(struct sockaddr_ll, sll_addr) + _mac_len;      \
-        })
-
-/* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */
-#define SOCKADDR_UN_LEN(sa)                                             \
-        ({                                                              \
-                const struct sockaddr_un *_sa = &(sa);                  \
-                assert(_sa->sun_family == AF_UNIX);                     \
-                offsetof(struct sockaddr_un, sun_path) +                \
-                        (_sa->sun_path[0] == 0 ?                        \
-                         1 + strnlen(_sa->sun_path+1, sizeof(_sa->sun_path)-1) : \
-                         strnlen(_sa->sun_path, sizeof(_sa->sun_path))); \
-        })
-
-int socket_ioctl_fd(void);
diff --git a/src/systemd/src/basic/sparse-endian.h b/src/systemd/src/basic/sparse-endian.h
deleted file mode 100644
index 5e59de54..00000000
--- a/src/systemd/src/basic/sparse-endian.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/* 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
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- */
-#ifndef SPARSE_ENDIAN_H
-#define SPARSE_ENDIAN_H
-
-#include <byteswap.h>
-#include <endian.h>
-#include <stdint.h>
-
-#ifdef __CHECKER__
-#define __sd_bitwise __attribute__((bitwise))
-#define __sd_force __attribute__((force))
-#else
-#define __sd_bitwise
-#define __sd_force
-#endif
-
-typedef uint16_t __sd_bitwise le16_t;
-typedef uint16_t __sd_bitwise be16_t;
-typedef uint32_t __sd_bitwise le32_t;
-typedef uint32_t __sd_bitwise be32_t;
-typedef uint64_t __sd_bitwise le64_t;
-typedef uint64_t __sd_bitwise be64_t;
-
-#undef htobe16
-#undef htole16
-#undef be16toh
-#undef le16toh
-#undef htobe32
-#undef htole32
-#undef be32toh
-#undef le32toh
-#undef htobe64
-#undef htole64
-#undef be64toh
-#undef le64toh
-
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-#define bswap_16_on_le(x) __bswap_16(x)
-#define bswap_32_on_le(x) __bswap_32(x)
-#define bswap_64_on_le(x) __bswap_64(x)
-#define bswap_16_on_be(x) (x)
-#define bswap_32_on_be(x) (x)
-#define bswap_64_on_be(x) (x)
-#elif __BYTE_ORDER == __BIG_ENDIAN
-#define bswap_16_on_le(x) (x)
-#define bswap_32_on_le(x) (x)
-#define bswap_64_on_le(x) (x)
-#define bswap_16_on_be(x) __bswap_16(x)
-#define bswap_32_on_be(x) __bswap_32(x)
-#define bswap_64_on_be(x) __bswap_64(x)
-#endif
-
-static inline le16_t htole16(uint16_t value) { return (le16_t __sd_force) bswap_16_on_be(value); }
-static inline le32_t htole32(uint32_t value) { return (le32_t __sd_force) bswap_32_on_be(value); }
-static inline le64_t htole64(uint64_t value) { return (le64_t __sd_force) bswap_64_on_be(value); }
-
-static inline be16_t htobe16(uint16_t value) { return (be16_t __sd_force) bswap_16_on_le(value); }
-static inline be32_t htobe32(uint32_t value) { return (be32_t __sd_force) bswap_32_on_le(value); }
-static inline be64_t htobe64(uint64_t value) { return (be64_t __sd_force) bswap_64_on_le(value); }
-
-static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __sd_force)value); }
-static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __sd_force)value); }
-static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __sd_force)value); }
-
-static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __sd_force)value); }
-static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __sd_force)value); }
-static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __sd_force)value); }
-
-#undef __sd_bitwise
-#undef __sd_force
-
-#endif /* SPARSE_ENDIAN_H */
diff --git a/src/systemd/src/basic/stat-util.c b/src/systemd/src/basic/stat-util.c
deleted file mode 100644
index d645ce30..00000000
--- a/src/systemd/src/basic/stat-util.c
+++ /dev/null
@@ -1,272 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/magic.h>
-#include <sched.h>
-#include <sys/stat.h>
-#include <sys/statvfs.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "dirent-util.h"
-#include "fd-util.h"
-#include "fs-util.h"
-#include "macro.h"
-#include "missing.h"
-#include "stat-util.h"
-#include "string-util.h"
-
-#if 0 /* NM_IGNORED */
-int is_symlink(const char *path) {
-        struct stat info;
-
-        assert(path);
-
-        if (lstat(path, &info) < 0)
-                return -errno;
-
-        return !!S_ISLNK(info.st_mode);
-}
-
-int is_dir(const char* path, bool follow) {
-        struct stat st;
-        int r;
-
-        assert(path);
-
-        if (follow)
-                r = stat(path, &st);
-        else
-                r = lstat(path, &st);
-        if (r < 0)
-                return -errno;
-
-        return !!S_ISDIR(st.st_mode);
-}
-
-int is_dir_fd(int fd) {
-        struct stat st;
-        int r;
-
-        r = fstat(fd, &st);
-        if (r < 0)
-                return -errno;
-
-        return !!S_ISDIR(st.st_mode);
-}
-
-int is_device_node(const char *path) {
-        struct stat info;
-
-        assert(path);
-
-        if (lstat(path, &info) < 0)
-                return -errno;
-
-        return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode));
-}
-
-int dir_is_empty(const char *path) {
-        _cleanup_closedir_ DIR *d;
-        struct dirent *de;
-
-        d = opendir(path);
-        if (!d)
-                return -errno;
-
-        FOREACH_DIRENT(de, d, return -errno)
-                return 0;
-
-        return 1;
-}
-
-bool null_or_empty(struct stat *st) {
-        assert(st);
-
-        if (S_ISREG(st->st_mode) && st->st_size <= 0)
-                return true;
-
-        /* We don't want to hardcode the major/minor of /dev/null,
-         * hence we do a simpler "is this a device node?" check. */
-
-        if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
-                return true;
-
-        return false;
-}
-
-int null_or_empty_path(const char *fn) {
-        struct stat st;
-
-        assert(fn);
-
-        if (stat(fn, &st) < 0)
-                return -errno;
-
-        return null_or_empty(&st);
-}
-
-int null_or_empty_fd(int fd) {
-        struct stat st;
-
-        assert(fd >= 0);
-
-        if (fstat(fd, &st) < 0)
-                return -errno;
-
-        return null_or_empty(&st);
-}
-
-int path_is_read_only_fs(const char *path) {
-        struct statvfs st;
-
-        assert(path);
-
-        if (statvfs(path, &st) < 0)
-                return -errno;
-
-        if (st.f_flag & ST_RDONLY)
-                return true;
-
-        /* On NFS, statvfs() might not reflect whether we can actually
-         * write to the remote share. Let's try again with
-         * access(W_OK) which is more reliable, at least sometimes. */
-        if (access(path, W_OK) < 0 && errno == EROFS)
-                return true;
-
-        return false;
-}
-
-int files_same(const char *filea, const char *fileb, int flags) {
-        struct stat a, b;
-
-        assert(filea);
-        assert(fileb);
-
-        if (fstatat(AT_FDCWD, filea, &a, flags) < 0)
-                return -errno;
-
-        if (fstatat(AT_FDCWD, fileb, &b, flags) < 0)
-                return -errno;
-
-        return a.st_dev == b.st_dev &&
-               a.st_ino == b.st_ino;
-}
-
-bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) {
-        assert(s);
-        assert_cc(sizeof(statfs_f_type_t) >= sizeof(s->f_type));
-
-        return F_TYPE_EQUAL(s->f_type, magic_value);
-}
-
-int fd_is_fs_type(int fd, statfs_f_type_t magic_value) {
-        struct statfs s;
-
-        if (fstatfs(fd, &s) < 0)
-                return -errno;
-
-        return is_fs_type(&s, magic_value);
-}
-
-int path_is_fs_type(const char *path, statfs_f_type_t magic_value) {
-        _cleanup_close_ int fd = -1;
-
-        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH);
-        if (fd < 0)
-                return -errno;
-
-        return fd_is_fs_type(fd, magic_value);
-}
-
-bool is_temporary_fs(const struct statfs *s) {
-        return is_fs_type(s, TMPFS_MAGIC) ||
-                is_fs_type(s, RAMFS_MAGIC);
-}
-
-bool is_network_fs(const struct statfs *s) {
-        return is_fs_type(s, CIFS_MAGIC_NUMBER) ||
-                is_fs_type(s, CODA_SUPER_MAGIC) ||
-                is_fs_type(s, NCP_SUPER_MAGIC) ||
-                is_fs_type(s, NFS_SUPER_MAGIC) ||
-                is_fs_type(s, SMB_SUPER_MAGIC) ||
-                is_fs_type(s, V9FS_MAGIC) ||
-                is_fs_type(s, AFS_SUPER_MAGIC) ||
-                is_fs_type(s, OCFS2_SUPER_MAGIC);
-}
-
-int fd_is_temporary_fs(int fd) {
-        struct statfs s;
-
-        if (fstatfs(fd, &s) < 0)
-                return -errno;
-
-        return is_temporary_fs(&s);
-}
-
-int fd_is_network_fs(int fd) {
-        struct statfs s;
-
-        if (fstatfs(fd, &s) < 0)
-                return -errno;
-
-        return is_network_fs(&s);
-}
-
-int fd_is_network_ns(int fd) {
-        int r;
-
-        r = fd_is_fs_type(fd, NSFS_MAGIC);
-        if (r <= 0)
-                return r;
-
-        r = ioctl(fd, NS_GET_NSTYPE);
-        if (r < 0)
-                return -errno;
-
-        return r == CLONE_NEWNET;
-}
-
-int path_is_temporary_fs(const char *path) {
-        _cleanup_close_ int fd = -1;
-
-        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH);
-        if (fd < 0)
-                return -errno;
-
-        return fd_is_temporary_fs(fd);
-}
-#endif /* NM_IGNORED */
-
-int stat_verify_regular(const struct stat *st) {
-        assert(st);
-
-        /* Checks whether the specified stat() structure refers to a regular file. If not returns an appropriate error
-         * code. */
-
-        if (S_ISDIR(st->st_mode))
-                return -EISDIR;
-
-        if (S_ISLNK(st->st_mode))
-                return -ELOOP;
-
-        if (!S_ISREG(st->st_mode))
-                return -EBADFD;
-
-        return 0;
-}
-
-int fd_verify_regular(int fd) {
-        struct stat st;
-
-        assert(fd >= 0);
-
-        if (fstat(fd, &st) < 0)
-                return -errno;
-
-        return stat_verify_regular(&st);
-}
diff --git a/src/systemd/src/basic/stat-util.h b/src/systemd/src/basic/stat-util.h
deleted file mode 100644
index 1a725f1d..00000000
--- a/src/systemd/src/basic/stat-util.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <sys/stat.h>
-#include <sys/statfs.h>
-#include <sys/types.h>
-#include <sys/vfs.h>
-
-#include "macro.h"
-
-int is_symlink(const char *path);
-int is_dir(const char *path, bool follow);
-int is_dir_fd(int fd);
-int is_device_node(const char *path);
-
-int dir_is_empty(const char *path);
-
-static inline int dir_is_populated(const char *path) {
-        int r;
-        r = dir_is_empty(path);
-        if (r < 0)
-                return r;
-        return !r;
-}
-
-bool null_or_empty(struct stat *st) _pure_;
-int null_or_empty_path(const char *fn);
-int null_or_empty_fd(int fd);
-
-int path_is_read_only_fs(const char *path);
-
-int files_same(const char *filea, const char *fileb, int flags);
-
-/* The .f_type field of struct statfs is really weird defined on
- * different archs. Let's give its type a name. */
-typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t;
-
-bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) _pure_;
-int fd_is_fs_type(int fd, statfs_f_type_t magic_value);
-int path_is_fs_type(const char *path, statfs_f_type_t magic_value);
-
-bool is_temporary_fs(const struct statfs *s) _pure_;
-bool is_network_fs(const struct statfs *s) _pure_;
-
-int fd_is_temporary_fs(int fd);
-int fd_is_network_fs(int fd);
-
-int fd_is_network_ns(int fd);
-
-int path_is_temporary_fs(const char *path);
-
-/* Because statfs.t_type can be int on some architectures, we have to cast
- * the const magic to the type, otherwise the compiler warns about
- * signed/unsigned comparison, because the magic can be 32 bit unsigned.
- */
-#define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b)
-
-int stat_verify_regular(const struct stat *st);
-int fd_verify_regular(int fd);
diff --git a/src/systemd/src/basic/stdio-util.h b/src/systemd/src/basic/stdio-util.h
deleted file mode 100644
index 73c03274..00000000
--- a/src/systemd/src/basic/stdio-util.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <printf.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <sys/types.h>
-
-#include "macro.h"
-
-#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 {                                                                    \
-        int _argtypes[128];                                             \
-        size_t _i, _k;                                                  \
-        _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \
-        assert(_k < ELEMENTSOF(_argtypes));                             \
-        for (_i = 0; _i < _k; _i++) {                                   \
-                if (_argtypes[_i] & PA_FLAG_PTR)  {                     \
-                        (void) va_arg(ap, void*);                       \
-                        continue;                                       \
-                }                                                       \
-                                                                        \
-                switch (_argtypes[_i]) {                                \
-                case PA_INT:                                            \
-                case PA_INT|PA_FLAG_SHORT:                              \
-                case PA_CHAR:                                           \
-                        (void) va_arg(ap, int);                         \
-                        break;                                          \
-                case PA_INT|PA_FLAG_LONG:                               \
-                        (void) va_arg(ap, long int);                    \
-                        break;                                          \
-                case PA_INT|PA_FLAG_LONG_LONG:                          \
-                        (void) va_arg(ap, long long int);               \
-                        break;                                          \
-                case PA_WCHAR:                                          \
-                        (void) va_arg(ap, wchar_t);                     \
-                        break;                                          \
-                case PA_WSTRING:                                        \
-                case PA_STRING:                                         \
-                case PA_POINTER:                                        \
-                        (void) va_arg(ap, void*);                       \
-                        break;                                          \
-                case PA_FLOAT:                                          \
-                case PA_DOUBLE:                                         \
-                        (void) va_arg(ap, double);                      \
-                        break;                                          \
-                case PA_DOUBLE|PA_FLAG_LONG_DOUBLE:                     \
-                        (void) va_arg(ap, long double);                 \
-                        break;                                          \
-                default:                                                \
-                        assert_not_reached("Unknown format string argument."); \
-                }                                                       \
-        }                                                               \
-} while (false)
diff --git a/src/systemd/src/basic/string-table.c b/src/systemd/src/basic/string-table.c
deleted file mode 100644
index 94412ed2..00000000
--- a/src/systemd/src/basic/string-table.c
+++ /dev/null
@@ -1,19 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include "string-table.h"
-#include "string-util.h"
-
-ssize_t string_table_lookup(const char * const *table, size_t len, const char *key) {
-        size_t i;
-
-        if (!key)
-                return -1;
-
-        for (i = 0; i < len; ++i)
-                if (streq_ptr(table[i], key))
-                        return (ssize_t) i;
-
-        return -1;
-}
diff --git a/src/systemd/src/basic/string-table.h b/src/systemd/src/basic/string-table.h
deleted file mode 100644
index 9bd78793..00000000
--- a/src/systemd/src/basic/string-table.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#pragma once
-
-#include <errno.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-
-#include "macro.h"
-#include "parse-util.h"
-#include "string-util.h"
-
-ssize_t string_table_lookup(const char * const *table, size_t len, const char *key);
-
-/* For basic lookup tables with strictly enumerated entries */
-#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope)          \
-        scope const char *name##_to_string(type i) {                    \
-                if (i < 0 || i >= (type) ELEMENTSOF(name##_table))      \
-                        return NULL;                                    \
-                return name##_table[i];                                 \
-        }
-
-#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope)        \
-        scope type name##_from_string(const char *s) {                  \
-                return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \
-        }
-
-#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \
-        scope type name##_from_string(const char *s) {                  \
-                int b;                                                  \
-                if (!s)                                                 \
-                        return -1;                                      \
-                b = parse_boolean(s);                                   \
-                if (b == 0)                                             \
-                        return (type) 0;                                \
-                else if (b > 0)                                         \
-                        return yes;                                     \
-                return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \
-        }
-
-#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,scope) \
-        scope int name##_to_string_alloc(type i, char **str) {          \
-                char *s;                                                \
-                if (i < 0 || i > max)                                   \
-                        return -ERANGE;                                 \
-                if (i < (type) ELEMENTSOF(name##_table)) {              \
-                        s = strdup(name##_table[i]);                    \
-                        if (!s)                                         \
-                                return -ENOMEM;                         \
-                } else {                                                \
-                        if (asprintf(&s, "%i", i) < 0)                  \
-                                return -ENOMEM;                         \
-                }                                                       \
-                *str = s;                                               \
-                return 0;                                               \
-        }
-
-#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,scope) \
-        type name##_from_string(const char *s) {                        \
-                type i;                                                 \
-                unsigned u = 0;                                         \
-                if (!s)                                                 \
-                        return (type) -1;                               \
-                for (i = 0; i < (type) ELEMENTSOF(name##_table); i++)   \
-                        if (streq_ptr(name##_table[i], s))              \
-                                return i;                               \
-                if (safe_atou(s, &u) >= 0 && u <= max)                  \
-                        return (type) u;                                \
-                return (type) -1;                                       \
-        }                                                               \
-
-#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)
-
-#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)
-
-#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)
-#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static)
-#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static)
-
-#define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,)
-
-/* 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,)
-
-#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \
-        _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static)
-#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max) \
-        _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,static)
-
-#define DUMP_STRING_TABLE(name,type,max)                                \
-        do {                                                            \
-                type _k;                                                \
-                flockfile(stdout);                                      \
-                for (_k = 0; _k < (max); _k++) {                        \
-                        const char *_t;                                 \
-                        _t = name##_to_string(_k);                      \
-                        if (!_t)                                        \
-                                continue;                               \
-                        fputs_unlocked(_t, stdout);                     \
-                        fputc_unlocked('\n', stdout);                   \
-                }                                                       \
-                funlockfile(stdout);                                    \
-        } while(false)
diff --git a/src/systemd/src/basic/string-util.c b/src/systemd/src/basic/string-util.c
deleted file mode 100644
index 1747f35b..00000000
--- a/src/systemd/src/basic/string-util.c
+++ /dev/null
@@ -1,1079 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <stdarg.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdio_ext.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "alloc-util.h"
-#include "escape.h"
-#include "gunicode.h"
-#include "locale-util.h"
-#include "macro.h"
-#include "string-util.h"
-#include "terminal-util.h"
-#include "utf8.h"
-#include "util.h"
-#include "fileio.h"
-
-int strcmp_ptr(const char *a, const char *b) {
-
-        /* Like strcmp(), but tries to make sense of NULL pointers */
-        if (a && b)
-                return strcmp(a, b);
-
-        if (!a && b)
-                return -1;
-
-        if (a && !b)
-                return 1;
-
-        return 0;
-}
-
-char* endswith(const char *s, const char *postfix) {
-        size_t sl, pl;
-
-        assert(s);
-        assert(postfix);
-
-        sl = strlen(s);
-        pl = strlen(postfix);
-
-        if (pl == 0)
-                return (char*) s + sl;
-
-        if (sl < pl)
-                return NULL;
-
-        if (memcmp(s + sl - pl, postfix, pl) != 0)
-                return NULL;
-
-        return (char*) s + sl - pl;
-}
-
-char* endswith_no_case(const char *s, const char *postfix) {
-        size_t sl, pl;
-
-        assert(s);
-        assert(postfix);
-
-        sl = strlen(s);
-        pl = strlen(postfix);
-
-        if (pl == 0)
-                return (char*) s + sl;
-
-        if (sl < pl)
-                return NULL;
-
-        if (strcasecmp(s + sl - pl, postfix) != 0)
-                return NULL;
-
-        return (char*) s + sl - pl;
-}
-
-char* first_word(const char *s, const char *word) {
-        size_t sl, wl;
-        const char *p;
-
-        assert(s);
-        assert(word);
-
-        /* Checks if the string starts with the specified word, either
-         * followed by NUL or by whitespace. Returns a pointer to the
-         * NUL or the first character after the whitespace. */
-
-        sl = strlen(s);
-        wl = strlen(word);
-
-        if (sl < wl)
-                return NULL;
-
-        if (wl == 0)
-                return (char*) s;
-
-        if (memcmp(s, word, wl) != 0)
-                return NULL;
-
-        p = s + wl;
-        if (*p == 0)
-                return (char*) p;
-
-        if (!strchr(WHITESPACE, *p))
-                return NULL;
-
-        p += strspn(p, WHITESPACE);
-        return (char*) p;
-}
-
-static size_t strcspn_escaped(const char *s, const char *reject) {
-        bool escaped = false;
-        int n;
-
-        for (n=0; s[n]; n++) {
-                if (escaped)
-                        escaped = false;
-                else if (s[n] == '\\')
-                        escaped = true;
-                else if (strchr(reject, s[n]))
-                        break;
-        }
-
-        /* if s ends in \, return index of previous char */
-        return n - escaped;
-}
-
-/* Split a string into words. */
-const char* split(const char **state, size_t *l, const char *separator, bool quoted) {
-        const char *current;
-
-        current = *state;
-
-        if (!*current) {
-                assert(**state == '\0');
-                return NULL;
-        }
-
-        current += strspn(current, separator);
-        if (!*current) {
-                *state = current;
-                return NULL;
-        }
-
-        if (quoted && strchr("\'\"", *current)) {
-                char quotechars[2] = {*current, '\0'};
-
-                *l = strcspn_escaped(current + 1, quotechars);
-                if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] ||
-                    (current[*l + 2] && !strchr(separator, current[*l + 2]))) {
-                        /* right quote missing or garbage at the end */
-                        *state = current;
-                        return NULL;
-                }
-                *state = current++ + *l + 2;
-        } else if (quoted) {
-                *l = strcspn_escaped(current, separator);
-                if (current[*l] && !strchr(separator, current[*l])) {
-                        /* unfinished escape */
-                        *state = current;
-                        return NULL;
-                }
-                *state = current + *l;
-        } else {
-                *l = strcspn(current, separator);
-                *state = current + *l;
-        }
-
-        return current;
-}
-
-char *strnappend(const char *s, const char *suffix, size_t b) {
-        size_t a;
-        char *r;
-
-        if (!s && !suffix)
-                return strdup("");
-
-        if (!s)
-                return strndup(suffix, b);
-
-        if (!suffix)
-                return strdup(s);
-
-        assert(s);
-        assert(suffix);
-
-        a = strlen(s);
-        if (b > ((size_t) -1) - a)
-                return NULL;
-
-        r = new(char, a+b+1);
-        if (!r)
-                return NULL;
-
-        memcpy(r, s, a);
-        memcpy(r+a, suffix, b);
-        r[a+b] = 0;
-
-        return r;
-}
-
-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;
-        char *r, *p;
-
-        va_start(ap, x);
-
-        if (x) {
-                l = strlen(x);
-
-                for (;;) {
-                        const char *t;
-                        size_t n;
-
-                        t = va_arg(ap, const char *);
-                        if (!t)
-                                break;
-
-                        n = strlen(t);
-                        if (n > ((size_t) -1) - l) {
-                                va_end(ap);
-                                return NULL;
-                        }
-
-                        l += n;
-                }
-        } else
-                l = 0;
-
-        va_end(ap);
-
-        r = new(char, l+1);
-        if (!r)
-                return NULL;
-
-        if (x) {
-                p = stpcpy(r, x);
-
-                va_start(ap, x);
-
-                for (;;) {
-                        const char *t;
-
-                        t = va_arg(ap, const char *);
-                        if (!t)
-                                break;
-
-                        p = stpcpy(p, t);
-                }
-
-                va_end(ap);
-        } else
-                r[0] = 0;
-
-        return r;
-}
-
-char *strstrip(char *s) {
-        if (!s)
-                return NULL;
-
-        /* Drops trailing whitespace. Modifies the string in place. Returns pointer to first non-space character */
-
-        return delete_trailing_chars(skip_leading_chars(s, WHITESPACE), WHITESPACE);
-}
-
-char *delete_chars(char *s, const char *bad) {
-        char *f, *t;
-
-        /* 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))
-                        continue;
-
-                *(t++) = *f;
-        }
-
-        *t = 0;
-
-        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);
-
-        s[strcspn(s, NEWLINE)] = 0;
-        return s;
-}
-
-char ascii_tolower(char x) {
-
-        if (x >= 'A' && x <= 'Z')
-                return x - 'A' + 'a';
-
-        return x;
-}
-
-char ascii_toupper(char x) {
-
-        if (x >= 'a' && x <= 'z')
-                return x - 'a' + 'A';
-
-        return x;
-}
-
-char *ascii_strlower(char *t) {
-        char *p;
-
-        assert(t);
-
-        for (p = t; *p; p++)
-                *p = ascii_tolower(*p);
-
-        return t;
-}
-
-char *ascii_strupper(char *t) {
-        char *p;
-
-        assert(t);
-
-        for (p = t; *p; p++)
-                *p = ascii_toupper(*p);
-
-        return t;
-}
-
-char *ascii_strlower_n(char *t, size_t n) {
-        size_t i;
-
-        if (n <= 0)
-                return t;
-
-        for (i = 0; i < n; i++)
-                t[i] = ascii_tolower(t[i]);
-
-        return t;
-}
-
-int ascii_strcasecmp_n(const char *a, const char *b, size_t n) {
-
-        for (; n > 0; a++, b++, n--) {
-                int x, y;
-
-                x = (int) (uint8_t) ascii_tolower(*a);
-                y = (int) (uint8_t) ascii_tolower(*b);
-
-                if (x != y)
-                        return x - y;
-        }
-
-        return 0;
-}
-
-int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) {
-        int r;
-
-        r = ascii_strcasecmp_n(a, b, MIN(n, m));
-        if (r != 0)
-                return r;
-
-        if (n < m)
-                return -1;
-        else if (n > m)
-                return 1;
-        else
-                return 0;
-}
-
-bool chars_intersect(const char *a, const char *b) {
-        const char *p;
-
-        /* Returns true if any of the chars in a are in b. */
-        for (p = a; *p; p++)
-                if (strchr(b, *p))
-                        return true;
-
-        return false;
-}
-
-bool string_has_cc(const char *p, const char *ok) {
-        const char *t;
-
-        assert(p);
-
-        /*
-         * Check if a string contains control characters. If 'ok' is
-         * non-NULL it may be a string containing additional CCs to be
-         * considered OK.
-         */
-
-        for (t = p; *t; t++) {
-                if (ok && strchr(ok, *t))
-                        continue;
-
-                if (*t > 0 && *t < ' ')
-                        return true;
-
-                if (*t == 127)
-                        return true;
-        }
-
-        return false;
-}
-
-#if 0 /* NM_IGNORED */
-static int write_ellipsis(char *buf, bool unicode) {
-        if (unicode || is_locale_utf8()) {
-                buf[0] = 0xe2; /* tri-dot ellipsis: … */
-                buf[1] = 0x80;
-                buf[2] = 0xa6;
-        } else {
-                buf[0] = '.';
-                buf[1] = '.';
-                buf[2] = '.';
-        }
-
-        return 3;
-}
-
-static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
-        size_t x, need_space, suffix_len;
-        char *t;
-
-        assert(s);
-        assert(percent <= 100);
-        assert(new_length != (size_t) -1);
-
-        if (old_length <= new_length)
-                return strndup(s, old_length);
-
-        /* Special case short ellipsations */
-        switch (new_length) {
-
-        case 0:
-                return strdup("");
-
-        case 1:
-                if (is_locale_utf8())
-                        return strdup("…");
-                else
-                        return strdup(".");
-
-        case 2:
-                if (!is_locale_utf8())
-                        return strdup("..");
-
-                break;
-
-        default:
-                break;
-        }
-
-        /* Calculate how much space the ellipsis will take up. If we are in UTF-8 mode we only need space for one
-         * character ("…"), otherwise for three characters ("..."). Note that in both cases we need 3 bytes of storage,
-         * either for the UTF-8 encoded character or for three ASCII characters. */
-        need_space = is_locale_utf8() ? 1 : 3;
-
-        t = new(char, new_length+3);
-        if (!t)
-                return NULL;
-
-        assert(new_length >= need_space);
-
-        x = ((new_length - need_space) * percent + 50) / 100;
-        assert(x <= new_length - need_space);
-
-        memcpy(t, s, x);
-        write_ellipsis(t + x, false);
-        suffix_len = new_length - x - need_space;
-        memcpy(t + x + 3, s + old_length - suffix_len, suffix_len);
-        *(t + x + 3 + suffix_len) = '\0';
-
-        return t;
-}
-
-char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
-        size_t x, k, len, len2;
-        const char *i, *j;
-        char *e;
-        int r;
-
-        /* Note that 'old_length' refers to bytes in the string, while 'new_length' refers to character cells taken up
-         * on screen. This distinction doesn't matter for ASCII strings, but it does matter for non-ASCII UTF-8
-         * strings.
-         *
-         * Ellipsation is done in a locale-dependent way:
-         * 1. If the string passed in is fully ASCII and the current locale is not UTF-8, three dots are used ("...")
-         * 2. Otherwise, a unicode ellipsis is used ("…")
-         *
-         * In other words: you'll get a unicode ellipsis as soon as either the string contains non-ASCII characters or
-         * the current locale is UTF-8.
-         */
-
-        assert(s);
-        assert(percent <= 100);
-
-        if (new_length == (size_t) -1)
-                return strndup(s, old_length);
-
-        if (new_length == 0)
-                return strdup("");
-
-        /* If no multibyte characters use ascii_ellipsize_mem for speed */
-        if (ascii_is_valid_n(s, old_length))
-                return ascii_ellipsize_mem(s, old_length, new_length, percent);
-
-        x = ((new_length - 1) * percent) / 100;
-        assert(x <= new_length - 1);
-
-        k = 0;
-        for (i = s; i < s + old_length; i = utf8_next_char(i)) {
-                char32_t c;
-                int w;
-
-                r = utf8_encoded_to_unichar(i, &c);
-                if (r < 0)
-                        return NULL;
-
-                w = unichar_iswide(c) ? 2 : 1;
-                if (k + w <= x)
-                        k += w;
-                else
-                        break;
-        }
-
-        for (j = s + old_length; j > i; ) {
-                char32_t c;
-                int w;
-                const char *jj;
-
-                jj = utf8_prev_char(j);
-                r = utf8_encoded_to_unichar(jj, &c);
-                if (r < 0)
-                        return NULL;
-
-                w = unichar_iswide(c) ? 2 : 1;
-                if (k + w <= new_length) {
-                        k += w;
-                        j = jj;
-                } else
-                        break;
-        }
-        assert(i <= j);
-
-        /* we don't actually need to ellipsize */
-        if (i == j)
-                return memdup_suffix0(s, old_length);
-
-        /* make space for ellipsis, if possible */
-        if (j < s + old_length)
-                j = utf8_next_char(j);
-        else if (i > s)
-                i = utf8_prev_char(i);
-
-        len = i - s;
-        len2 = s + old_length - j;
-        e = new(char, len + 3 + len2 + 1);
-        if (!e)
-                return NULL;
-
-        /*
-        printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n",
-               old_length, new_length, x, len, len2, k);
-        */
-
-        memcpy(e, s, len);
-        write_ellipsis(e + len, true);
-        memcpy(e + len + 3, j, len2);
-        *(e + len + 3 + len2) = '\0';
-
-        return e;
-}
-
-char *cellescape(char *buf, size_t len, const char *s) {
-        /* Escape and ellipsize s into buffer buf of size len. Only non-control ASCII
-         * characters are copied as they are, everything else is escaped. The result
-         * is different then if escaping and ellipsization was performed in two
-         * separate steps, because each sequence is either stored in full or skipped.
-         *
-         * This function should be used for logging about strings which expected to
-         * be plain ASCII in a safe way.
-         *
-         * An ellipsis will be used if s is too long. It was always placed at the
-         * very end.
-         */
-
-        size_t i = 0, last_char_width[4] = {}, k = 0, j;
-
-        assert(len > 0); /* at least a terminating NUL */
-
-        for (;;) {
-                char four[4];
-                int w;
-
-                if (*s == 0) /* terminating NUL detected? then we are done! */
-                        goto done;
-
-                w = cescape_char(*s, four);
-                if (i + w + 1 > len) /* This character doesn't fit into the buffer anymore? In that case let's
-                                      * ellipsize at the previous location */
-                        break;
-
-                /* OK, there was space, let's add this escaped character to the buffer */
-                memcpy(buf + i, four, w);
-                i += w;
-
-                /* And remember its width in the ring buffer */
-                last_char_width[k] = w;
-                k = (k + 1) % 4;
-
-                s++;
-        }
-
-        /* Ellipsation is necessary. This means we might need to truncate the string again to make space for 4
-         * characters ideally, but the buffer is shorter than that in the first place take what we can get */
-        for (j = 0; j < ELEMENTSOF(last_char_width); j++) {
-
-                if (i + 4 <= len) /* nice, we reached our space goal */
-                        break;
-
-                k = k == 0 ? 3 : k - 1;
-                if (last_char_width[k] == 0) /* bummer, we reached the beginning of the strings */
-                        break;
-
-                assert(i >= last_char_width[k]);
-                i -= last_char_width[k];
-        }
-
-        if (i + 4 <= len) /* yay, enough space */
-                i += write_ellipsis(buf + i, false);
-        else if (i + 3 <= len) { /* only space for ".." */
-                buf[i++] = '.';
-                buf[i++] = '.';
-        } else if (i + 2 <= len) /* only space for a single "." */
-                buf[i++] = '.';
-        else
-                assert(i + 1 <= len);
-
- done:
-        buf[i] = '\0';
-        return buf;
-}
-#endif /* NM_IGNORED */
-
-bool nulstr_contains(const char *nulstr, const char *needle) {
-        const char *i;
-
-        if (!nulstr)
-                return false;
-
-        NULSTR_FOREACH(i, nulstr)
-                if (streq(i, needle))
-                        return true;
-
-        return false;
-}
-
-char* strshorten(char *s, size_t l) {
-        assert(s);
-
-        if (strnlen(s, l+1) > l)
-                s[l] = 0;
-
-        return s;
-}
-
-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;
-
-        assert(old_string);
-        assert(new_string);
-
-        if (!text)
-                return NULL;
-
-        old_len = strlen(old_string);
-        new_len = strlen(new_string);
-
-        l = strlen(text);
-        if (!GREEDY_REALLOC(ret, allocated, l+1))
-                return NULL;
-
-        f = text;
-        t = ret;
-        while (*f) {
-                size_t d, nl;
-
-                if (!startswith(f, old_string)) {
-                        *(t++) = *(f++);
-                        continue;
-                }
-
-                d = t - ret;
-                nl = l - old_len + new_len;
-
-                if (!GREEDY_REALLOC(ret, allocated, nl + 1))
-                        return mfree(ret);
-
-                l = nl;
-                t = ret + d;
-
-                t = stpcpy(t, new_string);
-                f += old_len;
-        }
-
-        *t = 0;
-        return ret;
-}
-
-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, size_t highlight[2]) {
-        const char *i, *begin = NULL;
-        enum {
-                STATE_OTHER,
-                STATE_ESCAPE,
-                STATE_CSI,
-                STATE_CSO,
-        } state = STATE_OTHER;
-        char *obuf = NULL;
-        size_t osz = 0, isz, shift[2] = {};
-        FILE *f;
-
-        assert(ibuf);
-        assert(*ibuf);
-
-        /* This does three things:
-         *
-         * 1. Replaces TABs by 8 spaces
-         * 2. Strips ANSI color sequences (a subset of CSI), i.e. ESC '[' … 'm' sequences
-         * 3. Strips ANSI operating system sequences (CSO), i.e. ESC ']' … BEL sequences
-         *
-         * Everything else will be left as it is. In particular other ANSI sequences are left as they are, as are any
-         * other special characters. Truncated ANSI sequences are left-as is too. This call is supposed to suppress the
-         * most basic formatting noise, but nothing else.
-         *
-         * Why care for CSO sequences? Well, to undo what terminal_urlify() and friends generate. */
-
-        isz = _isz ? *_isz : strlen(*ibuf);
-
-        f = open_memstream(&obuf, &osz);
-        if (!f)
-                return NULL;
-
-        /* 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++) {
-
-                switch (state) {
-
-                case STATE_OTHER:
-                        if (i >= *ibuf + isz) /* EOT */
-                                break;
-                        else if (*i == '\x1B')
-                                state = STATE_ESCAPE;
-                        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('\x1B', f);
-                                advance_offsets(i - *ibuf, highlight, shift, 1);
-                                break;
-                        } else if (*i == '[') { /* ANSI CSI */
-                                state = STATE_CSI;
-                                begin = i + 1;
-                        } else if (*i == ']') { /* ANSI CSO */
-                                state = STATE_CSO;
-                                begin = i + 1;
-                        } else {
-                                fputc('\x1B', f);
-                                fputc(*i, f);
-                                advance_offsets(i - *ibuf, highlight, shift, 1);
-                                state = STATE_OTHER;
-                        }
-
-                        break;
-
-                case STATE_CSI:
-
-                        if (i >= *ibuf + isz || /* EOT … */
-                            !strchr("01234567890;m", *i)) { /* … or invalid chars in sequence */
-                                fputc('\x1B', f);
-                                fputc('[', f);
-                                advance_offsets(i - *ibuf, highlight, shift, 2);
-                                state = STATE_OTHER;
-                                i = begin-1;
-                        } else if (*i == 'm')
-                                state = STATE_OTHER;
-
-                        break;
-
-                case STATE_CSO:
-
-                        if (i >= *ibuf + isz || /* EOT … */
-                            (*i != '\a' && (uint8_t) *i < 32U) || (uint8_t) *i > 126U) { /* … or invalid chars in sequence */
-                                fputc('\x1B', f);
-                                fputc(']', f);
-                                advance_offsets(i - *ibuf, highlight, shift, 2);
-                                state = STATE_OTHER;
-                                i = begin-1;
-                        } else if (*i == '\a')
-                                state = STATE_OTHER;
-
-                        break;
-                }
-        }
-
-        if (fflush_and_check(f) < 0) {
-                fclose(f);
-                return mfree(obuf);
-        }
-
-        fclose(f);
-
-        free(*ibuf);
-        *ibuf = obuf;
-
-        if (_isz)
-                *_isz = osz;
-
-        if (highlight) {
-                highlight[0] += shift[0];
-                highlight[1] += shift[1];
-        }
-
-        return obuf;
-}
-
-#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);
-
-        need_separator = !isempty(*x);
-        l_separator = strlen_ptr(separator);
-
-        va_start(ap, separator);
-        for (;;) {
-                const char *t;
-                size_t n;
-
-                t = va_arg(ap, const char *);
-                if (!t)
-                        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, separator);
-        for (;;) {
-                const char *t;
-
-                t = va_arg(ap, const char *);
-                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;
-        char *r, *p;
-        unsigned i;
-
-        assert(s);
-
-        l = strlen(s);
-        p = r = malloc(l * n + 1);
-        if (!r)
-                return NULL;
-
-        for (i = 0; i < n; i++)
-                p = stpcpy(p, s);
-
-        *p = 0;
-        return r;
-}
-
-int split_pair(const char *s, const char *sep, char **l, char **r) {
-        char *x, *a, *b;
-
-        assert(s);
-        assert(sep);
-        assert(l);
-        assert(r);
-
-        if (isempty(sep))
-                return -EINVAL;
-
-        x = strstr(s, sep);
-        if (!x)
-                return -EINVAL;
-
-        a = strndup(s, x - s);
-        if (!a)
-                return -ENOMEM;
-
-        b = strdup(x + strlen(sep));
-        if (!b) {
-                free(a);
-                return -ENOMEM;
-        }
-
-        *l = a;
-        *r = b;
-
-        return 0;
-}
-
-int free_and_strdup(char **p, const char *s) {
-        char *t;
-
-        assert(p);
-
-        /* Replaces a string pointer with an strdup()ed new string,
-         * possibly freeing the old one. */
-
-        if (streq_ptr(*p, s))
-                return 0;
-
-        if (s) {
-                t = strdup(s);
-                if (!t)
-                        return -ENOMEM;
-        } else
-                t = NULL;
-
-        free(*p);
-        *p = t;
-
-        return 1;
-}
-
-#if !HAVE_EXPLICIT_BZERO
-/*
- * Pointer to memset is volatile so that compiler must de-reference
- * the pointer and can't assume that it points to any function in
- * particular (such as memset, which it then might further "optimize")
- * This approach is inspired by openssl's crypto/mem_clr.c.
- */
-typedef void *(*memset_t)(void *,int,size_t);
-
-static volatile memset_t memset_func = memset;
-
-void explicit_bzero(void *p, size_t l) {
-        memset_func(p, '\0', l);
-}
-#endif
-
-char* string_erase(char *x) {
-        if (!x)
-                return NULL;
-
-        /* A delicious drop of snake-oil! To be called on memory where
-         * we stored passphrases or so, after we used them. */
-        explicit_bzero(x, strlen(x));
-        return x;
-}
-
-char *string_free_erase(char *s) {
-        return mfree(string_erase(s));
-}
-
-bool string_is_safe(const char *p) {
-        const char *t;
-
-        if (!p)
-                return false;
-
-        for (t = p; *t; t++) {
-                if (*t > 0 && *t < ' ') /* no control characters */
-                        return false;
-
-                if (strchr(QUOTES "\\\x7f", *t))
-                        return false;
-        }
-
-        return true;
-}
diff --git a/src/systemd/src/basic/string-util.h b/src/systemd/src/basic/string-util.h
deleted file mode 100644
index c0cc4e78..00000000
--- a/src/systemd/src/basic/string-util.h
+++ /dev/null
@@ -1,230 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <alloca.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <string.h>
-
-#include "macro.h"
-
-/* What is interpreted as whitespace? */
-#define WHITESPACE        " \t\n\r"
-#define NEWLINE           "\n\r"
-#define QUOTES            "\"\'"
-#define COMMENTS          "#;"
-#define GLOB_CHARS        "*?["
-#define DIGITS            "0123456789"
-#define LOWERCASE_LETTERS "abcdefghijklmnopqrstuvwxyz"
-#define UPPERCASE_LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-#define LETTERS           LOWERCASE_LETTERS UPPERCASE_LETTERS
-#define ALPHANUMERICAL    LETTERS DIGITS
-#define HEXDIGITS         DIGITS "abcdefABCDEF"
-
-#define streq(a,b) (strcmp((a),(b)) == 0)
-#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0)
-#define strcaseeq(a,b) (strcasecmp((a),(b)) == 0)
-#define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0)
-
-int strcmp_ptr(const char *a, const char *b) _pure_;
-
-static inline bool streq_ptr(const char *a, const char *b) {
-        return strcmp_ptr(a, b) == 0;
-}
-
-static inline const char* strempty(const char *s) {
-        return s ?: "";
-}
-
-static inline const char* strnull(const char *s) {
-        return s ?: "(null)";
-}
-
-static inline const char *strna(const char *s) {
-        return s ?: "n/a";
-}
-
-static inline bool isempty(const char *p) {
-        return !p || !p[0];
-}
-
-static inline const char *empty_to_null(const char *p) {
-        return isempty(p) ? NULL : p;
-}
-
-static inline const char *empty_to_dash(const char *str) {
-        return isempty(str) ? "-" : str;
-}
-
-static inline char *startswith(const char *s, const char *prefix) {
-        size_t l;
-
-        l = strlen(prefix);
-        if (strncmp(s, prefix, l) == 0)
-                return (char*) s + l;
-
-        return NULL;
-}
-
-static inline char *startswith_no_case(const char *s, const char *prefix) {
-        size_t l;
-
-        l = strlen(prefix);
-        if (strncasecmp(s, prefix, l) == 0)
-                return (char*) s + l;
-
-        return NULL;
-}
-
-char *endswith(const char *s, const char *postfix) _pure_;
-char *endswith_no_case(const char *s, const char *postfix) _pure_;
-
-char *first_word(const char *s, const char *word) _pure_;
-
-const char* split(const char **state, size_t *l, const char *separator, bool quoted);
-
-#define FOREACH_WORD(word, length, s, state)                            \
-        _FOREACH_WORD(word, length, s, WHITESPACE, false, state)
-
-#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state)       \
-        _FOREACH_WORD(word, length, s, separator, false, state)
-
-#define _FOREACH_WORD(word, length, s, separator, quoted, state)        \
-        for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted)))
-
-char *strappend(const char *s, const char *suffix);
-char *strnappend(const char *s, const char *suffix, size_t length);
-
-char *strjoin_real(const char *x, ...) _sentinel_;
-#define strjoin(a, ...) strjoin_real((a), __VA_ARGS__, NULL)
-
-#define strjoina(a, ...)                                                \
-        ({                                                              \
-                const char *_appendees_[] = { a, __VA_ARGS__ };         \
-                char *_d_, *_p_;                                        \
-                size_t _len_ = 0;                                          \
-                size_t _i_;                                           \
-                for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \
-                        _len_ += strlen(_appendees_[_i_]);              \
-                _p_ = _d_ = alloca(_len_ + 1);                          \
-                for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \
-                        _p_ = stpcpy(_p_, _appendees_[_i_]);            \
-                *_p_ = 0;                                               \
-                _d_;                                                    \
-        })
-
-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);
-
-char ascii_toupper(char x);
-char *ascii_strupper(char *s);
-
-int ascii_strcasecmp_n(const char *a, const char *b, size_t n);
-int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m);
-
-bool chars_intersect(const char *a, const char *b) _pure_;
-
-static inline bool _pure_ in_charset(const char *s, const char* charset) {
-        assert(s);
-        assert(charset);
-        return s[strspn(s, charset)] == '\0';
-}
-
-bool string_has_cc(const char *p, const char *ok) _pure_;
-
-char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent);
-static inline char *ellipsize(const char *s, size_t length, unsigned percent) {
-        return ellipsize_mem(s, strlen(s), length, percent);
-}
-
-char *cellescape(char *buf, size_t len, const char *s);
-
-/* This limit is arbitrary, enough to give some idea what the string contains */
-#define CELLESCAPE_DEFAULT_LENGTH 64
-
-bool nulstr_contains(const char *nulstr, const char *needle);
-
-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 **ibuf, size_t *_isz, size_t highlight[2]);
-
-char *strextend_with_separator(char **x, const char *separator, ...) _sentinel_;
-
-#define strextend(x, ...) strextend_with_separator(x, NULL, __VA_ARGS__)
-
-char *strrep(const char *s, unsigned n);
-
-int split_pair(const char *s, const char *sep, char **l, char **r);
-
-int free_and_strdup(char **p, const char *s);
-
-/* Normal memmem() requires haystack to be nonnull, which is annoying for zero-length buffers */
-static inline void *memmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) {
-
-        if (needlelen <= 0)
-                return (void*) haystack;
-
-        if (haystacklen < needlelen)
-                return NULL;
-
-        assert(haystack);
-        assert(needle);
-
-        return memmem(haystack, haystacklen, needle, needlelen);
-}
-
-#if !HAVE_EXPLICIT_BZERO
-void explicit_bzero(void *p, size_t l);
-#endif
-
-char *string_erase(char *x);
-
-char *string_free_erase(char *s);
-DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase);
-#define _cleanup_string_free_erase_ _cleanup_(string_free_erasep)
-
-bool string_is_safe(const char *p) _pure_;
-
-static inline size_t strlen_ptr(const char *s) {
-        if (!s)
-                return 0;
-
-        return strlen(s);
-}
-
-/* Like startswith(), but operates on arbitrary memory blocks */
-static inline void *memory_startswith(const void *p, size_t sz, const char *token) {
-        size_t n;
-
-        assert(token);
-
-        n = strlen(token);
-        if (sz < n)
-                return NULL;
-
-        assert(p);
-
-        if (memcmp(p, token, n) != 0)
-                return NULL;
-
-        return (uint8_t*) p + n;
-}
diff --git a/src/systemd/src/basic/strv.c b/src/systemd/src/basic/strv.c
deleted file mode 100644
index 6f80b317..00000000
--- a/src/systemd/src/basic/strv.c
+++ /dev/null
@@ -1,888 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <fnmatch.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "alloc-util.h"
-#include "escape.h"
-#include "extract-word.h"
-#include "fileio.h"
-#include "string-util.h"
-#include "strv.h"
-#include "util.h"
-
-char *strv_find(char **l, const char *name) {
-        char **i;
-
-        assert(name);
-
-        STRV_FOREACH(i, l)
-                if (streq(*i, name))
-                        return *i;
-
-        return NULL;
-}
-
-char *strv_find_prefix(char **l, const char *name) {
-        char **i;
-
-        assert(name);
-
-        STRV_FOREACH(i, l)
-                if (startswith(*i, name))
-                        return *i;
-
-        return NULL;
-}
-
-char *strv_find_startswith(char **l, const char *name) {
-        char **i, *e;
-
-        assert(name);
-
-        /* Like strv_find_prefix, but actually returns only the
-         * suffix, not the whole item */
-
-        STRV_FOREACH(i, l) {
-                e = startswith(*i, name);
-                if (e)
-                        return e;
-        }
-
-        return NULL;
-}
-
-void strv_clear(char **l) {
-        char **k;
-
-        if (!l)
-                return;
-
-        for (k = l; *k; k++)
-                free(*k);
-
-        *l = NULL;
-}
-
-char **strv_free(char **l) {
-        strv_clear(l);
-        return mfree(l);
-}
-
-char **strv_free_erase(char **l) {
-        char **i;
-
-        STRV_FOREACH(i, l)
-                string_erase(*i);
-
-        return strv_free(l);
-}
-
-char **strv_copy(char * const *l) {
-        char **r, **k;
-
-        k = r = new(char*, strv_length(l) + 1);
-        if (!r)
-                return NULL;
-
-        if (l)
-                for (; *l; k++, l++) {
-                        *k = strdup(*l);
-                        if (!*k) {
-                                strv_free(r);
-                                return NULL;
-                        }
-                }
-
-        *k = NULL;
-        return r;
-}
-
-size_t strv_length(char * const *l) {
-        size_t n = 0;
-
-        if (!l)
-                return 0;
-
-        for (; *l; l++)
-                n++;
-
-        return n;
-}
-
-char **strv_new_ap(const char *x, va_list ap) {
-        const char *s;
-        _cleanup_strv_free_ char **a = NULL;
-        size_t n = 0, i = 0;
-        va_list aq;
-
-        /* As a special trick we ignore all listed strings that equal
-         * STRV_IGNORE. This is supposed to be used with the
-         * STRV_IFNOTNULL() macro to include possibly NULL strings in
-         * the string list. */
-
-        if (x) {
-                n = x == STRV_IGNORE ? 0 : 1;
-
-                va_copy(aq, ap);
-                while ((s = va_arg(aq, const char*))) {
-                        if (s == STRV_IGNORE)
-                                continue;
-
-                        n++;
-                }
-
-                va_end(aq);
-        }
-
-        a = new(char*, n+1);
-        if (!a)
-                return NULL;
-
-        if (x) {
-                if (x != STRV_IGNORE) {
-                        a[i] = strdup(x);
-                        if (!a[i])
-                                return NULL;
-                        i++;
-                }
-
-                while ((s = va_arg(ap, const char*))) {
-
-                        if (s == STRV_IGNORE)
-                                continue;
-
-                        a[i] = strdup(s);
-                        if (!a[i])
-                                return NULL;
-
-                        i++;
-                }
-        }
-
-        a[i] = NULL;
-
-        return TAKE_PTR(a);
-}
-
-char **strv_new(const char *x, ...) {
-        char **r;
-        va_list ap;
-
-        va_start(ap, x);
-        r = strv_new_ap(x, ap);
-        va_end(ap);
-
-        return r;
-}
-
-int strv_extend_strv(char ***a, char **b, bool filter_duplicates) {
-        char **s, **t;
-        size_t p, q, i = 0, j;
-
-        assert(a);
-
-        if (strv_isempty(b))
-                return 0;
-
-        p = strv_length(*a);
-        q = strv_length(b);
-
-        t = reallocarray(*a, p + q + 1, sizeof(char *));
-        if (!t)
-                return -ENOMEM;
-
-        t[p] = NULL;
-        *a = t;
-
-        STRV_FOREACH(s, b) {
-
-                if (filter_duplicates && strv_contains(t, *s))
-                        continue;
-
-                t[p+i] = strdup(*s);
-                if (!t[p+i])
-                        goto rollback;
-
-                i++;
-                t[p+i] = NULL;
-        }
-
-        assert(i <= q);
-
-        return (int) i;
-
-rollback:
-        for (j = 0; j < i; j++)
-                free(t[p + j]);
-
-        t[p] = NULL;
-        return -ENOMEM;
-}
-
-int strv_extend_strv_concat(char ***a, char **b, const char *suffix) {
-        int r;
-        char **s;
-
-        STRV_FOREACH(s, b) {
-                char *v;
-
-                v = strappend(*s, suffix);
-                if (!v)
-                        return -ENOMEM;
-
-                r = strv_push(a, v);
-                if (r < 0) {
-                        free(v);
-                        return r;
-                }
-        }
-
-        return 0;
-}
-
-char **strv_split(const char *s, const char *separator) {
-        const char *word, *state;
-        size_t l;
-        size_t n, i;
-        char **r;
-
-        assert(s);
-
-        s += strspn(s, separator);
-        if (isempty(s))
-                return new0(char*, 1);
-
-        n = 0;
-        FOREACH_WORD_SEPARATOR(word, l, s, separator, state)
-                n++;
-
-        r = new(char*, n+1);
-        if (!r)
-                return NULL;
-
-        i = 0;
-        FOREACH_WORD_SEPARATOR(word, l, s, separator, state) {
-                r[i] = strndup(word, l);
-                if (!r[i]) {
-                        strv_free(r);
-                        return NULL;
-                }
-
-                i++;
-        }
-
-        r[i] = NULL;
-        return r;
-}
-
-char **strv_split_newlines(const char *s) {
-        char **l;
-        size_t n;
-
-        assert(s);
-
-        /* Special version of strv_split() that splits on newlines and
-         * suppresses an empty string at the end */
-
-        l = strv_split(s, NEWLINE);
-        if (!l)
-                return NULL;
-
-        n = strv_length(l);
-        if (n <= 0)
-                return l;
-
-        if (isempty(l[n - 1]))
-                l[n - 1] = mfree(l[n - 1]);
-
-        return l;
-}
-
-#if 0 /* NM_IGNORED */
-int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags) {
-        _cleanup_strv_free_ char **l = NULL;
-        size_t n = 0, allocated = 0;
-        int r;
-
-        assert(t);
-        assert(s);
-
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
-
-                r = extract_first_word(&s, &word, separators, flags);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
-
-                if (!GREEDY_REALLOC(l, allocated, n + 2))
-                        return -ENOMEM;
-
-                l[n++] = TAKE_PTR(word);
-
-                l[n] = NULL;
-        }
-
-        if (!l) {
-                l = new0(char*, 1);
-                if (!l)
-                        return -ENOMEM;
-        }
-
-        *t = TAKE_PTR(l);
-
-        return (int) n;
-}
-#endif /* NM_IGNORED */
-
-char *strv_join(char **l, const char *separator) {
-        char *r, *e;
-        char **s;
-        size_t n, k;
-
-        if (!separator)
-                separator = " ";
-
-        k = strlen(separator);
-
-        n = 0;
-        STRV_FOREACH(s, l) {
-                if (s != l)
-                        n += k;
-                n += strlen(*s);
-        }
-
-        r = new(char, n+1);
-        if (!r)
-                return NULL;
-
-        e = r;
-        STRV_FOREACH(s, l) {
-                if (s != l)
-                        e = stpcpy(e, separator);
-
-                e = stpcpy(e, *s);
-        }
-
-        *e = 0;
-
-        return r;
-}
-
-int strv_push(char ***l, char *value) {
-        char **c;
-        size_t n, m;
-
-        if (!value)
-                return 0;
-
-        n = strv_length(*l);
-
-        /* Increase and check for overflow */
-        m = n + 2;
-        if (m < n)
-                return -ENOMEM;
-
-        c = reallocarray(*l, m, sizeof(char*));
-        if (!c)
-                return -ENOMEM;
-
-        c[n] = value;
-        c[n+1] = NULL;
-
-        *l = c;
-        return 0;
-}
-
-int strv_push_pair(char ***l, char *a, char *b) {
-        char **c;
-        size_t n, m;
-
-        if (!a && !b)
-                return 0;
-
-        n = strv_length(*l);
-
-        /* increase and check for overflow */
-        m = n + !!a + !!b + 1;
-        if (m < n)
-                return -ENOMEM;
-
-        c = reallocarray(*l, m, sizeof(char*));
-        if (!c)
-                return -ENOMEM;
-
-        if (a)
-                c[n++] = a;
-        if (b)
-                c[n++] = b;
-        c[n] = NULL;
-
-        *l = c;
-        return 0;
-}
-
-int strv_insert(char ***l, size_t position, char *value) {
-        char **c;
-        size_t n, m, i;
-
-        if (!value)
-                return 0;
-
-        n = strv_length(*l);
-        position = MIN(position, n);
-
-        /* increase and check for overflow */
-        m = n + 2;
-        if (m < n)
-                return -ENOMEM;
-
-        c = new(char*, m);
-        if (!c)
-                return -ENOMEM;
-
-        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[n+1] = NULL;
-
-        free(*l);
-        *l = c;
-
-        return 0;
-}
-
-int strv_consume(char ***l, char *value) {
-        int r;
-
-        r = strv_push(l, value);
-        if (r < 0)
-                free(value);
-
-        return r;
-}
-
-int strv_consume_pair(char ***l, char *a, char *b) {
-        int r;
-
-        r = strv_push_pair(l, a, b);
-        if (r < 0) {
-                free(a);
-                free(b);
-        }
-
-        return r;
-}
-
-int strv_consume_prepend(char ***l, char *value) {
-        int r;
-
-        r = strv_push_prepend(l, value);
-        if (r < 0)
-                free(value);
-
-        return r;
-}
-
-int strv_extend(char ***l, const char *value) {
-        char *v;
-
-        if (!value)
-                return 0;
-
-        v = strdup(value);
-        if (!v)
-                return -ENOMEM;
-
-        return strv_consume(l, v);
-}
-
-int strv_extend_front(char ***l, const char *value) {
-        size_t n, m;
-        char *v, **c;
-
-        assert(l);
-
-        /* Like strv_extend(), but prepends rather than appends the new entry */
-
-        if (!value)
-                return 0;
-
-        n = strv_length(*l);
-
-        /* Increase and overflow check. */
-        m = n + 2;
-        if (m < n)
-                return -ENOMEM;
-
-        v = strdup(value);
-        if (!v)
-                return -ENOMEM;
-
-        c = reallocarray(*l, m, sizeof(char*));
-        if (!c) {
-                free(v);
-                return -ENOMEM;
-        }
-
-        memmove(c+1, c, n * sizeof(char*));
-        c[0] = v;
-        c[n+1] = NULL;
-
-        *l = c;
-        return 0;
-}
-
-char **strv_uniq(char **l) {
-        char **i;
-
-        /* Drops duplicate entries. The first identical string will be
-         * kept, the others dropped */
-
-        STRV_FOREACH(i, l)
-                strv_remove(i+1, *i);
-
-        return l;
-}
-
-bool strv_is_uniq(char **l) {
-        char **i;
-
-        STRV_FOREACH(i, l)
-                if (strv_find(i+1, *i))
-                        return false;
-
-        return true;
-}
-
-char **strv_remove(char **l, const char *s) {
-        char **f, **t;
-
-        if (!l)
-                return NULL;
-
-        assert(s);
-
-        /* Drops every occurrence of s in the string list, edits
-         * in-place. */
-
-        for (f = t = l; *f; f++)
-                if (streq(*f, s))
-                        free(*f);
-                else
-                        *(t++) = *f;
-
-        *t = NULL;
-        return l;
-}
-
-char **strv_parse_nulstr(const char *s, size_t l) {
-        /* l is the length of the input data, which will be split at NULs into
-         * elements of the resulting strv. Hence, the number of items in the resulting strv
-         * will be equal to one plus the number of NUL bytes in the l bytes starting at s,
-         * unless s[l-1] is NUL, in which case the final empty string is not stored in
-         * the resulting strv, and length is equal to the number of NUL bytes.
-         *
-         * Note that contrary to a normal nulstr which cannot contain empty strings, because
-         * the input data is terminated by any two consequent NUL bytes, this parser accepts
-         * empty strings in s.
-         */
-
-        const char *p;
-        size_t c = 0, i = 0;
-        char **v;
-
-        assert(s || l <= 0);
-
-        if (l <= 0)
-                return new0(char*, 1);
-
-        for (p = s; p < s + l; p++)
-                if (*p == 0)
-                        c++;
-
-        if (s[l-1] != 0)
-                c++;
-
-        v = new0(char*, c+1);
-        if (!v)
-                return NULL;
-
-        p = s;
-        while (p < s + l) {
-                const char *e;
-
-                e = memchr(p, 0, s + l - p);
-
-                v[i] = strndup(p, e ? e - p : s + l - p);
-                if (!v[i]) {
-                        strv_free(v);
-                        return NULL;
-                }
-
-                i++;
-
-                if (!e)
-                        break;
-
-                p = e + 1;
-        }
-
-        assert(i == c);
-
-        return v;
-}
-
-char **strv_split_nulstr(const char *s) {
-        const char *i;
-        char **r = NULL;
-
-        NULSTR_FOREACH(i, s)
-                if (strv_extend(&r, i) < 0) {
-                        strv_free(r);
-                        return NULL;
-                }
-
-        if (!r)
-                return strv_new(NULL, NULL);
-
-        return r;
-}
-
-int strv_make_nulstr(char **l, char **p, size_t *q) {
-        /* A valid nulstr with two NULs at the end will be created, but
-         * q will be the length without the two trailing NULs. Thus the output
-         * string is a valid nulstr and can be iterated over using NULSTR_FOREACH,
-         * and can also be parsed by strv_parse_nulstr as long as the length
-         * is provided separately.
-         */
-
-        size_t n_allocated = 0, n = 0;
-        _cleanup_free_ char *m = NULL;
-        char **i;
-
-        assert(p);
-        assert(q);
-
-        STRV_FOREACH(i, l) {
-                size_t z;
-
-                z = strlen(*i);
-
-                if (!GREEDY_REALLOC(m, n_allocated, n + z + 2))
-                        return -ENOMEM;
-
-                memcpy(m + n, *i, z + 1);
-                n += z + 1;
-        }
-
-        if (!m) {
-                m = new0(char, 1);
-                if (!m)
-                        return -ENOMEM;
-                n = 1;
-        } else
-                /* make sure there is a second extra NUL at the end of resulting nulstr */
-                m[n] = '\0';
-
-        assert(n > 0);
-        *p = m;
-        *q = n - 1;
-
-        m = NULL;
-
-        return 0;
-}
-
-bool strv_overlap(char **a, char **b) {
-        char **i;
-
-        STRV_FOREACH(i, a)
-                if (strv_contains(b, *i))
-                        return true;
-
-        return false;
-}
-
-static int str_compare(const void *_a, const void *_b) {
-        const char **a = (const char**) _a, **b = (const char**) _b;
-
-        return strcmp(*a, *b);
-}
-
-char **strv_sort(char **l) {
-        qsort_safe(l, strv_length(l), sizeof(char*), str_compare);
-        return l;
-}
-
-bool strv_equal(char **a, char **b) {
-
-        if (strv_isempty(a))
-                return strv_isempty(b);
-
-        if (strv_isempty(b))
-                return false;
-
-        for ( ; *a || *b; ++a, ++b)
-                if (!streq_ptr(*a, *b))
-                        return false;
-
-        return true;
-}
-
-void strv_print(char **l) {
-        char **s;
-
-        STRV_FOREACH(s, l)
-                puts(*s);
-}
-
-int strv_extendf(char ***l, const char *format, ...) {
-        va_list ap;
-        char *x;
-        int r;
-
-        va_start(ap, format);
-        r = vasprintf(&x, format, ap);
-        va_end(ap);
-
-        if (r < 0)
-                return -ENOMEM;
-
-        return strv_consume(l, x);
-}
-
-char **strv_reverse(char **l) {
-        size_t n, i;
-
-        n = strv_length(l);
-        if (n <= 1)
-                return l;
-
-        for (i = 0; i < n / 2; i++)
-                SWAP_TWO(l[i], l[n-1-i]);
-
-        return l;
-}
-
-char **strv_shell_escape(char **l, const char *bad) {
-        char **s;
-
-        /* Escapes every character in every string in l that is in bad,
-         * edits in-place, does not roll-back on error. */
-
-        STRV_FOREACH(s, l) {
-                char *v;
-
-                v = shell_escape(*s, bad);
-                if (!v)
-                        return NULL;
-
-                free(*s);
-                *s = v;
-        }
-
-        return l;
-}
-
-bool strv_fnmatch(char* const* patterns, const char *s, int flags) {
-        char* const* p;
-
-        STRV_FOREACH(p, patterns)
-                if (fnmatch(*p, s, flags) == 0)
-                        return true;
-
-        return false;
-}
-
-char ***strv_free_free(char ***l) {
-        char ***i;
-
-        if (!l)
-                return NULL;
-
-        for (i = l; *i; i++)
-                strv_free(*i);
-
-        return mfree(l);
-}
-
-char **strv_skip(char **l, size_t n) {
-
-        while (n > 0) {
-                if (strv_isempty(l))
-                        return l;
-
-                l++, n--;
-        }
-
-        return l;
-}
-
-int strv_extend_n(char ***l, const char *value, size_t n) {
-        size_t i, j, k;
-        char **nl;
-
-        assert(l);
-
-        if (!value)
-                return 0;
-        if (n == 0)
-                return 0;
-
-        /* Adds the value n times to l */
-
-        k = strv_length(*l);
-
-        nl = reallocarray(*l, k + n + 1, sizeof(char *));
-        if (!nl)
-                return -ENOMEM;
-
-        *l = nl;
-
-        for (i = k; i < k + n; i++) {
-                nl[i] = strdup(value);
-                if (!nl[i])
-                        goto rollback;
-        }
-
-        nl[i] = NULL;
-        return 0;
-
-rollback:
-        for (j = k; j < i; j++)
-                free(nl[j]);
-
-        nl[k] = NULL;
-        return -ENOMEM;
-}
-
-int fputstrv(FILE *f, char **l, const char *separator, bool *space) {
-        bool b = false;
-        char **s;
-        int r;
-
-        /* Like fputs(), but for strv, and with a less stupid argument order */
-
-        if (!space)
-                space = &b;
-
-        STRV_FOREACH(s, l) {
-                r = fputs_with_space(f, *s, separator, space);
-                if (r < 0)
-                        return r;
-        }
-
-        return 0;
-}
diff --git a/src/systemd/src/basic/strv.h b/src/systemd/src/basic/strv.h
deleted file mode 100644
index 51d03db9..00000000
--- a/src/systemd/src/basic/strv.h
+++ /dev/null
@@ -1,177 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <fnmatch.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stddef.h>
-
-#include "alloc-util.h"
-#include "extract-word.h"
-#include "macro.h"
-#include "util.h"
-
-char *strv_find(char **l, const char *name) _pure_;
-char *strv_find_prefix(char **l, const char *name) _pure_;
-char *strv_find_startswith(char **l, const char *name) _pure_;
-
-char **strv_free(char **l);
-DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free);
-#define _cleanup_strv_free_ _cleanup_(strv_freep)
-
-char **strv_free_erase(char **l);
-DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase);
-#define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep)
-
-void strv_clear(char **l);
-
-char **strv_copy(char * const *l);
-size_t strv_length(char * const *l) _pure_;
-
-int strv_extend_strv(char ***a, char **b, bool filter_duplicates);
-int strv_extend_strv_concat(char ***a, char **b, const char *suffix);
-int strv_extend(char ***l, const char *value);
-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_insert(char ***l, size_t 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);
-
-char **strv_remove(char **l, const char *s);
-char **strv_uniq(char **l);
-bool strv_is_uniq(char **l);
-
-bool strv_equal(char **a, char **b);
-
-#define strv_contains(l, s) (!!strv_find((l), (s)))
-
-char **strv_new(const char *x, ...) _sentinel_;
-char **strv_new_ap(const char *x, va_list ap);
-
-#define STRV_IGNORE ((const char *) -1)
-
-static inline const char* STRV_IFNOTNULL(const char *x) {
-        return x ? x : STRV_IGNORE;
-}
-
-static inline bool strv_isempty(char * const *l) {
-        return !l || !*l;
-}
-
-char **strv_split(const char *s, const char *separator);
-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_parse_nulstr(const char *s, size_t l);
-char **strv_split_nulstr(const char *s);
-int strv_make_nulstr(char **l, char **p, size_t *n);
-
-bool strv_overlap(char **a, char **b) _pure_;
-
-#define STRV_FOREACH(s, l)                      \
-        for ((s) = (l); (s) && *(s); (s)++)
-
-#define STRV_FOREACH_BACKWARDS(s, l)                                \
-        for (s = ({                                                 \
-                        char **_l = l;                              \
-                        _l ? _l + strv_length(_l) - 1U : NULL;      \
-                        });                                         \
-             (l) && ((s) >= (l));                                   \
-             (s)--)
-
-#define STRV_FOREACH_PAIR(x, y, l)               \
-        for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1))
-
-char **strv_sort(char **l);
-void strv_print(char **l);
-
-#define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL }))
-
-#define STRV_MAKE_EMPTY ((char*[1]) { NULL })
-
-#define strv_from_stdarg_alloca(first)                          \
-        ({                                                      \
-                char **_l;                                      \
-                                                                \
-                if (!first)                                     \
-                        _l = (char**) &first;                   \
-                else {                                          \
-                        size_t _n;                              \
-                        va_list _ap;                            \
-                                                                \
-                        _n = 1;                                 \
-                        va_start(_ap, first);                   \
-                        while (va_arg(_ap, char*))              \
-                                _n++;                           \
-                        va_end(_ap);                            \
-                                                                \
-                        _l = newa(char*, _n+1);                 \
-                        _l[_n = 0] = (char*) first;             \
-                        va_start(_ap, first);                   \
-                        for (;;) {                              \
-                                _l[++_n] = va_arg(_ap, char*);  \
-                                if (!_l[_n])                    \
-                                        break;                  \
-                        }                                       \
-                        va_end(_ap);                            \
-                }                                               \
-                _l;                                             \
-        })
-
-#define STR_IN_SET(x, ...) strv_contains(STRV_MAKE(__VA_ARGS__), x)
-#define STRPTR_IN_SET(x, ...)                                    \
-        ({                                                       \
-                const char* _x = (x);                            \
-                _x && strv_contains(STRV_MAKE(__VA_ARGS__), _x); \
-        })
-
-#define FOREACH_STRING(x, ...)                               \
-        for (char **_l = ({                                  \
-                char **_ll = STRV_MAKE(__VA_ARGS__);         \
-                x = _ll ? _ll[0] : NULL;                     \
-                _ll;                                         \
-        });                                                  \
-        _l && *_l;                                           \
-        x = ({                                               \
-                _l ++;                                       \
-                _l[0];                                       \
-        }))
-
-char **strv_reverse(char **l);
-char **strv_shell_escape(char **l, const char *bad);
-
-bool strv_fnmatch(char* const* patterns, const char *s, int flags);
-
-static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, int flags) {
-        assert(s);
-        return strv_isempty(patterns) ||
-               strv_fnmatch(patterns, s, flags);
-}
-
-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
deleted file mode 100644
index c0f53a47..00000000
--- a/src/systemd/src/basic/time-util.c
+++ /dev/null
@@ -1,1503 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <ctype.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/timerfd.h>
-#include <sys/timex.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "alloc-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "fs-util.h"
-#include "io-util.h"
-#include "log.h"
-#include "macro.h"
-#include "parse-util.h"
-#include "path-util.h"
-#include "process-util.h"
-#include "stat-util.h"
-#include "string-util.h"
-#include "strv.h"
-#include "time-util.h"
-
-static clockid_t map_clock_id(clockid_t c) {
-
-        /* Some more exotic archs (s390, ppc, …) lack the "ALARM" flavour of the clocks. Thus, clock_gettime() will
-         * fail for them. Since they are essentially the same as their non-ALARM pendants (their only difference is
-         * when timers are set on them), let's just map them accordingly. This way, we can get the correct time even on
-         * those archs. */
-
-        switch (c) {
-
-        case CLOCK_BOOTTIME_ALARM:
-                return CLOCK_BOOTTIME;
-
-        case CLOCK_REALTIME_ALARM:
-                return CLOCK_REALTIME;
-
-        default:
-                return c;
-        }
-}
-
-usec_t now(clockid_t clock_id) {
-        struct timespec ts;
-
-        assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
-
-        return timespec_load(&ts);
-}
-
-nsec_t now_nsec(clockid_t clock_id) {
-        struct timespec ts;
-
-        assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
-
-        return timespec_load_nsec(&ts);
-}
-
-dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
-        assert(ts);
-
-        ts->realtime = now(CLOCK_REALTIME);
-        ts->monotonic = now(CLOCK_MONOTONIC);
-
-        return ts;
-}
-
-triple_timestamp* triple_timestamp_get(triple_timestamp *ts) {
-        assert(ts);
-
-        ts->realtime = now(CLOCK_REALTIME);
-        ts->monotonic = now(CLOCK_MONOTONIC);
-        ts->boottime = clock_boottime_supported() ? now(CLOCK_BOOTTIME) : USEC_INFINITY;
-
-        return ts;
-}
-
-dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
-        int64_t delta;
-        assert(ts);
-
-        if (u == USEC_INFINITY || u <= 0) {
-                ts->realtime = ts->monotonic = u;
-                return ts;
-        }
-
-        ts->realtime = u;
-
-        delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
-        ts->monotonic = usec_sub_signed(now(CLOCK_MONOTONIC), delta);
-
-        return ts;
-}
-
-triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) {
-        int64_t delta;
-
-        assert(ts);
-
-        if (u == USEC_INFINITY || u <= 0) {
-                ts->realtime = ts->monotonic = ts->boottime = u;
-                return ts;
-        }
-
-        ts->realtime = u;
-        delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
-        ts->monotonic = usec_sub_signed(now(CLOCK_MONOTONIC), delta);
-        ts->boottime = clock_boottime_supported() ? usec_sub_signed(now(CLOCK_BOOTTIME), delta) : USEC_INFINITY;
-
-        return ts;
-}
-
-dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
-        int64_t delta;
-        assert(ts);
-
-        if (u == USEC_INFINITY) {
-                ts->realtime = ts->monotonic = USEC_INFINITY;
-                return ts;
-        }
-
-        ts->monotonic = u;
-        delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u;
-        ts->realtime = usec_sub_signed(now(CLOCK_REALTIME), delta);
-
-        return ts;
-}
-
-dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u) {
-        int64_t delta;
-
-        if (u == USEC_INFINITY) {
-                ts->realtime = ts->monotonic = USEC_INFINITY;
-                return ts;
-        }
-
-        dual_timestamp_get(ts);
-        delta = (int64_t) now(clock_boottime_or_monotonic()) - (int64_t) u;
-        ts->realtime = usec_sub_signed(ts->realtime, delta);
-        ts->monotonic = usec_sub_signed(ts->monotonic, delta);
-
-        return ts;
-}
-
-usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
-
-        switch (clock) {
-
-        case CLOCK_REALTIME:
-        case CLOCK_REALTIME_ALARM:
-                return ts->realtime;
-
-        case CLOCK_MONOTONIC:
-                return ts->monotonic;
-
-        case CLOCK_BOOTTIME:
-        case CLOCK_BOOTTIME_ALARM:
-                return ts->boottime;
-
-        default:
-                return USEC_INFINITY;
-        }
-}
-
-usec_t timespec_load(const struct timespec *ts) {
-        assert(ts);
-
-        if (ts->tv_sec < 0 || ts->tv_nsec < 0)
-                return USEC_INFINITY;
-
-        if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
-                return USEC_INFINITY;
-
-        return
-                (usec_t) ts->tv_sec * USEC_PER_SEC +
-                (usec_t) ts->tv_nsec / NSEC_PER_USEC;
-}
-
-nsec_t timespec_load_nsec(const struct timespec *ts) {
-        assert(ts);
-
-        if (ts->tv_sec < 0 || ts->tv_nsec < 0)
-                return NSEC_INFINITY;
-
-        if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC)
-                return NSEC_INFINITY;
-
-        return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec;
-}
-
-struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
-        assert(ts);
-
-        if (u == USEC_INFINITY ||
-            u / USEC_PER_SEC >= TIME_T_MAX) {
-                ts->tv_sec = (time_t) -1;
-                ts->tv_nsec = (long) -1;
-                return ts;
-        }
-
-        ts->tv_sec = (time_t) (u / USEC_PER_SEC);
-        ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
-
-        return ts;
-}
-
-#if 0 /* NM_IGNORED */
-usec_t timeval_load(const struct timeval *tv) {
-        assert(tv);
-
-        if (tv->tv_sec < 0 || tv->tv_usec < 0)
-                return USEC_INFINITY;
-
-        if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC)
-                return USEC_INFINITY;
-
-        return
-                (usec_t) tv->tv_sec * USEC_PER_SEC +
-                (usec_t) tv->tv_usec;
-}
-
-struct timeval *timeval_store(struct timeval *tv, usec_t u) {
-        assert(tv);
-
-        if (u == USEC_INFINITY ||
-            u / USEC_PER_SEC > TIME_T_MAX) {
-                tv->tv_sec = (time_t) -1;
-                tv->tv_usec = (suseconds_t) -1;
-        } else {
-                tv->tv_sec = (time_t) (u / USEC_PER_SEC);
-                tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
-        }
-
-        return tv;
-}
-
-static char *format_timestamp_internal(
-                char *buf,
-                size_t l,
-                usec_t t,
-                bool utc,
-                bool us) {
-
-        /* The weekdays in non-localized (English) form. We use this instead of the localized form, so that our
-         * generated timestamps may be parsed with parse_timestamp(), and always read the same. */
-        static const char * const weekdays[] = {
-                [0] = "Sun",
-                [1] = "Mon",
-                [2] = "Tue",
-                [3] = "Wed",
-                [4] = "Thu",
-                [5] = "Fri",
-                [6] = "Sat",
-        };
-
-        struct tm tm;
-        time_t sec;
-        size_t n;
-
-        assert(buf);
-
-        if (l <
-            3 +                  /* week day */
-            1 + 10 +             /* space and date */
-            1 + 8 +              /* space and time */
-            (us ? 1 + 6 : 0) +   /* "." and microsecond part */
-            1 + 1 +              /* space and shortest possible zone */
-            1)
-                return NULL; /* Not enough space even for the shortest form. */
-        if (t <= 0 || t == USEC_INFINITY)
-                return NULL; /* Timestamp is unset */
-
-        /* Let's not format times with years > 9999 */
-        if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) {
-                assert(l >= strlen("--- XXXX-XX-XX XX:XX:XX") + 1);
-                strcpy(buf, "--- XXXX-XX-XX XX:XX:XX");
-                return buf;
-        }
-
-        sec = (time_t) (t / USEC_PER_SEC); /* Round down */
-
-        if (!localtime_or_gmtime_r(&sec, &tm, utc))
-                return NULL;
-
-        /* Start with the week day */
-        assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays));
-        memcpy(buf, weekdays[tm.tm_wday], 4);
-
-        /* Add the main components */
-        if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0)
-                return NULL; /* Doesn't fit */
-
-        /* Append the microseconds part, if that's requested */
-        if (us) {
-                n = strlen(buf);
-                if (n + 8 > l)
-                        return NULL; /* Microseconds part doesn't fit. */
-
-                sprintf(buf + n, ".%06"PRI_USEC, t % USEC_PER_SEC);
-        }
-
-        /* Append the timezone */
-        n = strlen(buf);
-        if (utc) {
-                /* If this is UTC then let's explicitly use the "UTC" string here, because gmtime_r() normally uses the
-                 * obsolete "GMT" instead. */
-                if (n + 5 > l)
-                        return NULL; /* "UTC" doesn't fit. */
-
-                strcpy(buf + n, " UTC");
-
-        } else if (!isempty(tm.tm_zone)) {
-                size_t tn;
-
-                /* An explicit timezone is specified, let's use it, if it fits */
-                tn = strlen(tm.tm_zone);
-                if (n + 1 + tn + 1 > l) {
-                        /* The full time zone does not fit in. Yuck. */
-
-                        if (n + 1 + _POSIX_TZNAME_MAX + 1 > l)
-                                return NULL; /* Not even enough space for the POSIX minimum (of 6)? In that case, complain that it doesn't fit */
-
-                        /* So the time zone doesn't fit in fully, but the caller passed enough space for the POSIX
-                         * minimum time zone length. In this case suppress the timezone entirely, in order not to dump
-                         * an overly long, hard to read string on the user. This should be safe, because the user will
-                         * assume the local timezone anyway if none is shown. And so does parse_timestamp(). */
-                } else {
-                        buf[n++] = ' ';
-                        strcpy(buf + n, tm.tm_zone);
-                }
-        }
-
-        return buf;
-}
-
-char *format_timestamp(char *buf, size_t l, usec_t t) {
-        return format_timestamp_internal(buf, l, t, false, false);
-}
-
-char *format_timestamp_utc(char *buf, size_t l, usec_t t) {
-        return format_timestamp_internal(buf, l, t, true, false);
-}
-
-char *format_timestamp_us(char *buf, size_t l, usec_t t) {
-        return format_timestamp_internal(buf, l, t, false, true);
-}
-
-char *format_timestamp_us_utc(char *buf, size_t l, usec_t t) {
-        return format_timestamp_internal(buf, l, t, true, true);
-}
-
-char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
-        const char *s;
-        usec_t n, d;
-
-        if (t <= 0 || t == USEC_INFINITY)
-                return NULL;
-
-        n = now(CLOCK_REALTIME);
-        if (n > t) {
-                d = n - t;
-                s = "ago";
-        } else {
-                d = t - n;
-                s = "left";
-        }
-
-        if (d >= USEC_PER_YEAR)
-                snprintf(buf, l, USEC_FMT " years " USEC_FMT " months %s",
-                         d / USEC_PER_YEAR,
-                         (d % USEC_PER_YEAR) / USEC_PER_MONTH, s);
-        else if (d >= USEC_PER_MONTH)
-                snprintf(buf, l, USEC_FMT " months " USEC_FMT " days %s",
-                         d / USEC_PER_MONTH,
-                         (d % USEC_PER_MONTH) / USEC_PER_DAY, s);
-        else if (d >= USEC_PER_WEEK)
-                snprintf(buf, l, USEC_FMT " weeks " USEC_FMT " days %s",
-                         d / USEC_PER_WEEK,
-                         (d % USEC_PER_WEEK) / USEC_PER_DAY, s);
-        else if (d >= 2*USEC_PER_DAY)
-                snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s);
-        else if (d >= 25*USEC_PER_HOUR)
-                snprintf(buf, l, "1 day " USEC_FMT "h %s",
-                         (d - USEC_PER_DAY) / USEC_PER_HOUR, s);
-        else if (d >= 6*USEC_PER_HOUR)
-                snprintf(buf, l, USEC_FMT "h %s",
-                         d / USEC_PER_HOUR, s);
-        else if (d >= USEC_PER_HOUR)
-                snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s",
-                         d / USEC_PER_HOUR,
-                         (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s);
-        else if (d >= 5*USEC_PER_MINUTE)
-                snprintf(buf, l, USEC_FMT "min %s",
-                         d / USEC_PER_MINUTE, s);
-        else if (d >= USEC_PER_MINUTE)
-                snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s",
-                         d / USEC_PER_MINUTE,
-                         (d % USEC_PER_MINUTE) / USEC_PER_SEC, s);
-        else if (d >= USEC_PER_SEC)
-                snprintf(buf, l, USEC_FMT "s %s",
-                         d / USEC_PER_SEC, s);
-        else if (d >= USEC_PER_MSEC)
-                snprintf(buf, l, USEC_FMT "ms %s",
-                         d / USEC_PER_MSEC, s);
-        else if (d > 0)
-                snprintf(buf, l, USEC_FMT"us %s",
-                         d, s);
-        else
-                snprintf(buf, l, "now");
-
-        buf[l-1] = 0;
-        return buf;
-}
-#endif /* NM_IGNORED */
-
-char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
-        static const struct {
-                const char *suffix;
-                usec_t usec;
-        } table[] = {
-                { "y",     USEC_PER_YEAR   },
-                { "month", USEC_PER_MONTH  },
-                { "w",     USEC_PER_WEEK   },
-                { "d",     USEC_PER_DAY    },
-                { "h",     USEC_PER_HOUR   },
-                { "min",   USEC_PER_MINUTE },
-                { "s",     USEC_PER_SEC    },
-                { "ms",    USEC_PER_MSEC   },
-                { "us",    1               },
-        };
-
-        size_t i;
-        char *p = buf;
-        bool something = false;
-
-        assert(buf);
-        assert(l > 0);
-
-        if (t == USEC_INFINITY) {
-                strncpy(p, "infinity", l-1);
-                p[l-1] = 0;
-                return p;
-        }
-
-        if (t <= 0) {
-                strncpy(p, "0", l-1);
-                p[l-1] = 0;
-                return p;
-        }
-
-        /* The result of this function can be parsed with parse_sec */
-
-        for (i = 0; i < ELEMENTSOF(table); i++) {
-                int k = 0;
-                size_t n;
-                bool done = false;
-                usec_t a, b;
-
-                if (t <= 0)
-                        break;
-
-                if (t < accuracy && something)
-                        break;
-
-                if (t < table[i].usec)
-                        continue;
-
-                if (l <= 1)
-                        break;
-
-                a = t / table[i].usec;
-                b = t % table[i].usec;
-
-                /* Let's see if we should shows this in dot notation */
-                if (t < USEC_PER_MINUTE && b > 0) {
-                        usec_t cc;
-                        signed char j;
-
-                        j = 0;
-                        for (cc = table[i].usec; cc > 1; cc /= 10)
-                                j++;
-
-                        for (cc = accuracy; cc > 1; cc /= 10) {
-                                b /= 10;
-                                j--;
-                        }
-
-                        if (j > 0) {
-                                k = snprintf(p, l,
-                                             "%s"USEC_FMT".%0*"PRI_USEC"%s",
-                                             p > buf ? " " : "",
-                                             a,
-                                             j,
-                                             b,
-                                             table[i].suffix);
-
-                                t = 0;
-                                done = true;
-                        }
-                }
-
-                /* No? Then let's show it normally */
-                if (!done) {
-                        k = snprintf(p, l,
-                                     "%s"USEC_FMT"%s",
-                                     p > buf ? " " : "",
-                                     a,
-                                     table[i].suffix);
-
-                        t = b;
-                }
-
-                n = MIN((size_t) k, l);
-
-                l -= n;
-                p += n;
-
-                something = true;
-        }
-
-        *p = 0;
-
-        return buf;
-}
-
-#if 0 /* NM_IGNORED */
-void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
-
-        assert(f);
-        assert(name);
-        assert(t);
-
-        if (!dual_timestamp_is_set(t))
-                return;
-
-        fprintf(f, "%s="USEC_FMT" "USEC_FMT"\n",
-                name,
-                t->realtime,
-                t->monotonic);
-}
-
-int dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
-        uint64_t a, b;
-        int r, pos;
-
-        assert(value);
-        assert(t);
-
-        pos = strspn(value, WHITESPACE);
-        if (value[pos] == '-')
-                return -EINVAL;
-        pos += strspn(value + pos, DIGITS);
-        pos += strspn(value + pos, WHITESPACE);
-        if (value[pos] == '-')
-                return -EINVAL;
-
-        r = sscanf(value, "%" PRIu64 "%" PRIu64 "%n", &a, &b, &pos);
-        if (r != 2) {
-                log_debug("Failed to parse dual timestamp value \"%s\".", value);
-                return -EINVAL;
-        }
-
-        if (value[pos] != '\0')
-                /* trailing garbage */
-                return -EINVAL;
-
-        t->realtime = a;
-        t->monotonic = b;
-
-        return 0;
-}
-
-int timestamp_deserialize(const char *value, usec_t *timestamp) {
-        int r;
-
-        assert(value);
-
-        r = safe_atou64(value, timestamp);
-        if (r < 0)
-                return log_debug_errno(r, "Failed to parse timestamp value \"%s\": %m", value);
-
-        return r;
-}
-
-static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
-        static const struct {
-                const char *name;
-                const int nr;
-        } day_nr[] = {
-                { "Sunday",    0 },
-                { "Sun",       0 },
-                { "Monday",    1 },
-                { "Mon",       1 },
-                { "Tuesday",   2 },
-                { "Tue",       2 },
-                { "Wednesday", 3 },
-                { "Wed",       3 },
-                { "Thursday",  4 },
-                { "Thu",       4 },
-                { "Friday",    5 },
-                { "Fri",       5 },
-                { "Saturday",  6 },
-                { "Sat",       6 },
-        };
-
-        const char *k, *utc = NULL, *tzn = NULL;
-        struct tm tm, copy;
-        time_t x;
-        usec_t x_usec, plus = 0, minus = 0, ret;
-        int r, weekday = -1, dst = -1;
-        size_t i;
-
-        /* Allowed syntaxes:
-         *
-         *   2012-09-22 16:34:22
-         *   2012-09-22 16:34     (seconds will be set to 0)
-         *   2012-09-22           (time will be set to 00:00:00)
-         *   16:34:22             (date will be set to today)
-         *   16:34                (date will be set to today, seconds to 0)
-         *   now
-         *   yesterday            (time is set to 00:00:00)
-         *   today                (time is set to 00:00:00)
-         *   tomorrow             (time is set to 00:00:00)
-         *   +5min
-         *   -5days
-         *   @2147483647          (seconds since epoch)
-         */
-
-        assert(t);
-        assert(usec);
-
-        if (t[0] == '@' && !with_tz)
-                return parse_sec(t + 1, usec);
-
-        ret = now(CLOCK_REALTIME);
-
-        if (!with_tz) {
-                if (streq(t, "now"))
-                        goto finish;
-
-                else if (t[0] == '+') {
-                        r = parse_sec(t+1, &plus);
-                        if (r < 0)
-                                return r;
-
-                        goto finish;
-
-                } else if (t[0] == '-') {
-                        r = parse_sec(t+1, &minus);
-                        if (r < 0)
-                                return r;
-
-                        goto finish;
-
-                } else if ((k = endswith(t, " ago"))) {
-                        t = strndupa(t, k - t);
-
-                        r = parse_sec(t, &minus);
-                        if (r < 0)
-                                return r;
-
-                        goto finish;
-
-                } else if ((k = endswith(t, " left"))) {
-                        t = strndupa(t, k - t);
-
-                        r = parse_sec(t, &plus);
-                        if (r < 0)
-                                return r;
-
-                        goto finish;
-                }
-
-                /* See if the timestamp is suffixed with UTC */
-                utc = endswith_no_case(t, " UTC");
-                if (utc)
-                        t = strndupa(t, utc - t);
-                else {
-                        const char *e = NULL;
-                        int j;
-
-                        tzset();
-
-                        /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only
-                         * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because
-                         * there are no nice APIs available to cover this. By accepting the local time zone strings, we make
-                         * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't
-                         * support arbitrary timezone specifications. */
-
-                        for (j = 0; j <= 1; j++) {
-
-                                if (isempty(tzname[j]))
-                                        continue;
-
-                                e = endswith_no_case(t, tzname[j]);
-                                if (!e)
-                                        continue;
-                                if (e == t)
-                                        continue;
-                                if (e[-1] != ' ')
-                                        continue;
-
-                                break;
-                        }
-
-                        if (IN_SET(j, 0, 1)) {
-                                /* Found one of the two timezones specified. */
-                                t = strndupa(t, e - t - 1);
-                                dst = j;
-                                tzn = tzname[j];
-                        }
-                }
-        }
-
-        x = (time_t) (ret / USEC_PER_SEC);
-        x_usec = 0;
-
-        if (!localtime_or_gmtime_r(&x, &tm, utc))
-                return -EINVAL;
-
-        tm.tm_isdst = dst;
-        if (!with_tz && tzn)
-                tm.tm_zone = tzn;
-
-        if (streq(t, "today")) {
-                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
-                goto from_tm;
-
-        } else if (streq(t, "yesterday")) {
-                tm.tm_mday--;
-                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
-                goto from_tm;
-
-        } else if (streq(t, "tomorrow")) {
-                tm.tm_mday++;
-                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
-                goto from_tm;
-        }
-
-        for (i = 0; i < ELEMENTSOF(day_nr); i++) {
-                size_t skip;
-
-                if (!startswith_no_case(t, day_nr[i].name))
-                        continue;
-
-                skip = strlen(day_nr[i].name);
-                if (t[skip] != ' ')
-                        continue;
-
-                weekday = day_nr[i].nr;
-                t += skip + 1;
-                break;
-        }
-
-        copy = tm;
-        k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
-        if (k) {
-                if (*k == '.')
-                        goto parse_usec;
-                else if (*k == 0)
-                        goto from_tm;
-        }
-
-        tm = copy;
-        k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
-        if (k) {
-                if (*k == '.')
-                        goto parse_usec;
-                else if (*k == 0)
-                        goto from_tm;
-        }
-
-        tm = copy;
-        k = strptime(t, "%y-%m-%d %H:%M", &tm);
-        if (k && *k == 0) {
-                tm.tm_sec = 0;
-                goto from_tm;
-        }
-
-        tm = copy;
-        k = strptime(t, "%Y-%m-%d %H:%M", &tm);
-        if (k && *k == 0) {
-                tm.tm_sec = 0;
-                goto from_tm;
-        }
-
-        tm = copy;
-        k = strptime(t, "%y-%m-%d", &tm);
-        if (k && *k == 0) {
-                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
-                goto from_tm;
-        }
-
-        tm = copy;
-        k = strptime(t, "%Y-%m-%d", &tm);
-        if (k && *k == 0) {
-                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
-                goto from_tm;
-        }
-
-        tm = copy;
-        k = strptime(t, "%H:%M:%S", &tm);
-        if (k) {
-                if (*k == '.')
-                        goto parse_usec;
-                else if (*k == 0)
-                        goto from_tm;
-        }
-
-        tm = copy;
-        k = strptime(t, "%H:%M", &tm);
-        if (k && *k == 0) {
-                tm.tm_sec = 0;
-                goto from_tm;
-        }
-
-        return -EINVAL;
-
-parse_usec:
-        {
-                unsigned add;
-
-                k++;
-                r = parse_fractional_part_u(&k, 6, &add);
-                if (r < 0)
-                        return -EINVAL;
-
-                if (*k)
-                        return -EINVAL;
-
-                x_usec = add;
-        }
-
-from_tm:
-        if (weekday >= 0 && tm.tm_wday != weekday)
-                return -EINVAL;
-
-        x = mktime_or_timegm(&tm, utc);
-        if (x < 0)
-                return -EINVAL;
-
-        ret = (usec_t) x * USEC_PER_SEC + x_usec;
-        if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
-                return -EINVAL;
-
-finish:
-        if (ret + plus < ret) /* overflow? */
-                return -EINVAL;
-        ret += plus;
-        if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
-                return -EINVAL;
-
-        if (ret >= minus)
-                ret -= minus;
-        else
-                return -EINVAL;
-
-        *usec = ret;
-
-        return 0;
-}
-
-typedef struct ParseTimestampResult {
-        usec_t usec;
-        int return_value;
-} ParseTimestampResult;
-
-int parse_timestamp(const char *t, usec_t *usec) {
-        char *last_space, *tz = NULL;
-        ParseTimestampResult *shared, tmp;
-        int r;
-
-        last_space = strrchr(t, ' ');
-        if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG))
-                tz = last_space + 1;
-
-        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();
-
-        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 r;
-        }
-        if (r == 0) {
-                bool with_tz = true;
-
-                if (setenv("TZ", tz, 1) != 0) {
-                        shared->return_value = negative_errno();
-                        _exit(EXIT_FAILURE);
-                }
-
-                tzset();
-
-                /* If there is a timezone that matches the tzname fields, leave the parsing to the implementation.
-                 * Otherwise just cut it off. */
-                with_tz = !STR_IN_SET(tz, tzname[0], tzname[1]);
-
-                /* Cut off the timezone if we dont need it. */
-                if (with_tz)
-                        t = strndupa(t, last_space - t);
-
-                shared->return_value = parse_timestamp_impl(t, &shared->usec, with_tz);
-
-                _exit(EXIT_SUCCESS);
-        }
-
-        tmp = *shared;
-        if (munmap(shared, sizeof *shared) != 0)
-                return negative_errno();
-
-        if (tmp.return_value == 0)
-                *usec = tmp.usec;
-
-        return tmp.return_value;
-}
-
-static char* extract_multiplier(char *p, usec_t *multiplier) {
-        static const struct {
-                const char *suffix;
-                usec_t usec;
-        } table[] = {
-                { "seconds", USEC_PER_SEC    },
-                { "second",  USEC_PER_SEC    },
-                { "sec",     USEC_PER_SEC    },
-                { "s",       USEC_PER_SEC    },
-                { "minutes", USEC_PER_MINUTE },
-                { "minute",  USEC_PER_MINUTE },
-                { "min",     USEC_PER_MINUTE },
-                { "months",  USEC_PER_MONTH  },
-                { "month",   USEC_PER_MONTH  },
-                { "M",       USEC_PER_MONTH  },
-                { "msec",    USEC_PER_MSEC   },
-                { "ms",      USEC_PER_MSEC   },
-                { "m",       USEC_PER_MINUTE },
-                { "hours",   USEC_PER_HOUR   },
-                { "hour",    USEC_PER_HOUR   },
-                { "hr",      USEC_PER_HOUR   },
-                { "h",       USEC_PER_HOUR   },
-                { "days",    USEC_PER_DAY    },
-                { "day",     USEC_PER_DAY    },
-                { "d",       USEC_PER_DAY    },
-                { "weeks",   USEC_PER_WEEK   },
-                { "week",    USEC_PER_WEEK   },
-                { "w",       USEC_PER_WEEK   },
-                { "years",   USEC_PER_YEAR   },
-                { "year",    USEC_PER_YEAR   },
-                { "y",       USEC_PER_YEAR   },
-                { "usec",    1ULL            },
-                { "us",      1ULL            },
-                { "µs",      1ULL            },
-        };
-        size_t i;
-
-        for (i = 0; i < ELEMENTSOF(table); i++) {
-                char *e;
-
-                e = startswith(p, table[i].suffix);
-                if (e) {
-                        *multiplier = table[i].usec;
-                        return e;
-                }
-        }
-
-        return p;
-}
-
-int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
-        const char *p, *s;
-        usec_t r = 0;
-        bool something = false;
-
-        assert(t);
-        assert(usec);
-        assert(default_unit > 0);
-
-        p = t;
-
-        p += strspn(p, WHITESPACE);
-        s = startswith(p, "infinity");
-        if (s) {
-                s += strspn(s, WHITESPACE);
-                if (*s != 0)
-                        return -EINVAL;
-
-                *usec = USEC_INFINITY;
-                return 0;
-        }
-
-        for (;;) {
-                usec_t multiplier = default_unit, k;
-                long long l, z = 0;
-                unsigned n = 0;
-                char *e;
-
-                p += strspn(p, WHITESPACE);
-
-                if (*p == 0) {
-                        if (!something)
-                                return -EINVAL;
-
-                        break;
-                }
-
-                if (*p == '-') /* Don't allow "-0" */
-                        return -ERANGE;
-
-                errno = 0;
-                l = strtoll(p, &e, 10);
-                if (errno > 0)
-                        return -errno;
-                if (l < 0)
-                        return -ERANGE;
-
-                if (*e == '.') {
-                        char *b = e + 1;
-
-                        /* Don't allow "0.-0", "3.+1" or "3. 1" */
-                        if (*b == '-' || *b == '+' || isspace(*b))
-                                return -EINVAL;
-
-                        errno = 0;
-                        z = strtoll(b, &e, 10);
-                        if (errno > 0)
-                                return -errno;
-                        if (z < 0)
-                                return -ERANGE;
-                        if (e == b)
-                                return -EINVAL;
-
-                        n = e - b;
-
-                } else if (e == p)
-                        return -EINVAL;
-
-                e += strspn(e, WHITESPACE);
-                p = extract_multiplier(e, &multiplier);
-
-                something = true;
-
-                k = (usec_t) z * multiplier;
-
-                for (; n > 0; n--)
-                        k /= 10;
-
-                r += (usec_t) l * multiplier + k;
-        }
-
-        *usec = r;
-
-        return 0;
-}
-
-int parse_sec(const char *t, usec_t *usec) {
-        return parse_time(t, usec, USEC_PER_SEC);
-}
-
-int parse_sec_fix_0(const char *t, usec_t *usec) {
-        assert(t);
-        assert(usec);
-
-        t += strspn(t, WHITESPACE);
-
-        if (streq(t, "0")) {
-                *usec = USEC_INFINITY;
-                return 0;
-        }
-
-        return parse_sec(t, usec);
-}
-
-int parse_nsec(const char *t, nsec_t *nsec) {
-        static const struct {
-                const char *suffix;
-                nsec_t nsec;
-        } table[] = {
-                { "seconds", NSEC_PER_SEC },
-                { "second", NSEC_PER_SEC },
-                { "sec", NSEC_PER_SEC },
-                { "s", NSEC_PER_SEC },
-                { "minutes", NSEC_PER_MINUTE },
-                { "minute", NSEC_PER_MINUTE },
-                { "min", NSEC_PER_MINUTE },
-                { "months", NSEC_PER_MONTH },
-                { "month", NSEC_PER_MONTH },
-                { "msec", NSEC_PER_MSEC },
-                { "ms", NSEC_PER_MSEC },
-                { "m", NSEC_PER_MINUTE },
-                { "hours", NSEC_PER_HOUR },
-                { "hour", NSEC_PER_HOUR },
-                { "hr", NSEC_PER_HOUR },
-                { "h", NSEC_PER_HOUR },
-                { "days", NSEC_PER_DAY },
-                { "day", NSEC_PER_DAY },
-                { "d", NSEC_PER_DAY },
-                { "weeks", NSEC_PER_WEEK },
-                { "week", NSEC_PER_WEEK },
-                { "w", NSEC_PER_WEEK },
-                { "years", NSEC_PER_YEAR },
-                { "year", NSEC_PER_YEAR },
-                { "y", NSEC_PER_YEAR },
-                { "usec", NSEC_PER_USEC },
-                { "us", NSEC_PER_USEC },
-                { "µs", NSEC_PER_USEC },
-                { "nsec", 1ULL },
-                { "ns", 1ULL },
-                { "", 1ULL }, /* default is nsec */
-        };
-
-        const char *p, *s;
-        nsec_t r = 0;
-        bool something = false;
-
-        assert(t);
-        assert(nsec);
-
-        p = t;
-
-        p += strspn(p, WHITESPACE);
-        s = startswith(p, "infinity");
-        if (s) {
-                s += strspn(s, WHITESPACE);
-                if (*s != 0)
-                        return -EINVAL;
-
-                *nsec = NSEC_INFINITY;
-                return 0;
-        }
-
-        for (;;) {
-                long long l, z = 0;
-                size_t n = 0, i;
-                char *e;
-
-                p += strspn(p, WHITESPACE);
-
-                if (*p == 0) {
-                        if (!something)
-                                return -EINVAL;
-
-                        break;
-                }
-
-                if (*p == '-')
-                        return -ERANGE;
-
-                errno = 0;
-                l = strtoll(p, &e, 10);
-                if (errno > 0)
-                        return -errno;
-                if (l < 0)
-                        return -ERANGE;
-
-                if (*e == '.') {
-                        char *b = e + 1;
-
-                        if (*b == '-' || *b == '+' || isspace(*b))
-                                return -EINVAL;
-
-                        errno = 0;
-                        z = strtoll(b, &e, 10);
-                        if (errno > 0)
-                                return -errno;
-                        if (z < 0)
-                                return -ERANGE;
-                        if (e == b)
-                                return -EINVAL;
-
-                        n = e - b;
-
-                } else if (e == p)
-                        return -EINVAL;
-
-                e += strspn(e, WHITESPACE);
-
-                for (i = 0; i < ELEMENTSOF(table); i++)
-                        if (startswith(e, table[i].suffix)) {
-                                nsec_t k = (nsec_t) z * table[i].nsec;
-
-                                for (; n > 0; n--)
-                                        k /= 10;
-
-                                r += (nsec_t) l * table[i].nsec + k;
-                                p = e + strlen(table[i].suffix);
-
-                                something = true;
-                                break;
-                        }
-
-                if (i >= ELEMENTSOF(table))
-                        return -EINVAL;
-
-        }
-
-        *nsec = r;
-
-        return 0;
-}
-
-bool ntp_synced(void) {
-        struct timex txc = {};
-
-        if (adjtimex(&txc) < 0)
-                return false;
-
-        if (txc.status & STA_UNSYNC)
-                return false;
-
-        return true;
-}
-
-int get_timezones(char ***ret) {
-        _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_strv_free_ char **zones = NULL;
-        size_t n_zones = 0, n_allocated = 0;
-
-        assert(ret);
-
-        zones = strv_new("UTC", NULL);
-        if (!zones)
-                return -ENOMEM;
-
-        n_allocated = 2;
-        n_zones = 1;
-
-        f = fopen("/usr/share/zoneinfo/zone.tab", "re");
-        if (f) {
-                char l[LINE_MAX];
-
-                FOREACH_LINE(l, f, return -errno) {
-                        char *p, *w;
-                        size_t k;
-
-                        p = strstrip(l);
-
-                        if (isempty(p) || *p == '#')
-                                continue;
-
-                        /* Skip over country code */
-                        p += strcspn(p, WHITESPACE);
-                        p += strspn(p, WHITESPACE);
-
-                        /* Skip over coordinates */
-                        p += strcspn(p, WHITESPACE);
-                        p += strspn(p, WHITESPACE);
-
-                        /* Found timezone name */
-                        k = strcspn(p, WHITESPACE);
-                        if (k <= 0)
-                                continue;
-
-                        w = strndup(p, k);
-                        if (!w)
-                                return -ENOMEM;
-
-                        if (!GREEDY_REALLOC(zones, n_allocated, n_zones + 2)) {
-                                free(w);
-                                return -ENOMEM;
-                        }
-
-                        zones[n_zones++] = w;
-                        zones[n_zones] = NULL;
-                }
-
-                strv_sort(zones);
-
-        } else if (errno != ENOENT)
-                return -errno;
-
-        *ret = TAKE_PTR(zones);
-
-        return 0;
-}
-#endif /* NM_IGNORED */
-
-bool timezone_is_valid(const char *name, int log_level) {
-        bool slash = false;
-        const char *p, *t;
-        _cleanup_close_ int fd = -1;
-        char buf[4];
-        int r;
-
-        if (isempty(name))
-                return false;
-
-        if (name[0] == '/')
-                return false;
-
-        for (p = name; *p; p++) {
-                if (!(*p >= '0' && *p <= '9') &&
-                    !(*p >= 'a' && *p <= 'z') &&
-                    !(*p >= 'A' && *p <= 'Z') &&
-                    !IN_SET(*p, '-', '_', '+', '/'))
-                        return false;
-
-                if (*p == '/') {
-
-                        if (slash)
-                                return false;
-
-                        slash = true;
-                } else
-                        slash = false;
-        }
-
-        if (slash)
-                return false;
-
-        if (p - name >= PATH_MAX)
-                return false;
-
-        t = strjoina("/usr/share/zoneinfo/", name);
-
-        fd = open(t, O_RDONLY|O_CLOEXEC);
-        if (fd < 0) {
-                log_full_errno(log_level, errno, "Failed to open timezone file '%s': %m", t);
-                return false;
-        }
-
-        r = fd_verify_regular(fd);
-        if (r < 0) {
-                log_full_errno(log_level, r, "Timezone file '%s' is not  a regular file: %m", t);
-                return false;
-        }
-
-        r = loop_read_exact(fd, buf, 4, false);
-        if (r < 0) {
-                log_full_errno(log_level, r, "Failed to read from timezone file '%s': %m", t);
-                return false;
-        }
-
-        /* Magic from tzfile(5) */
-        if (memcmp(buf, "TZif", 4) != 0) {
-                log_full(log_level, "Timezone file '%s' has wrong magic bytes", t);
-                return false;
-        }
-
-        return true;
-}
-
-bool clock_boottime_supported(void) {
-        static int supported = -1;
-
-        /* Note that this checks whether CLOCK_BOOTTIME is available in general as well as available for timerfds()! */
-
-        if (supported < 0) {
-                int fd;
-
-                fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK|TFD_CLOEXEC);
-                if (fd < 0)
-                        supported = false;
-                else {
-                        safe_close(fd);
-                        supported = true;
-                }
-        }
-
-        return supported;
-}
-
-clockid_t clock_boottime_or_monotonic(void) {
-        if (clock_boottime_supported())
-                return CLOCK_BOOTTIME;
-        else
-                return CLOCK_MONOTONIC;
-}
-
-bool clock_supported(clockid_t clock) {
-        struct timespec ts;
-
-        switch (clock) {
-
-        case CLOCK_MONOTONIC:
-        case CLOCK_REALTIME:
-                return true;
-
-        case CLOCK_BOOTTIME:
-                return clock_boottime_supported();
-
-        case CLOCK_BOOTTIME_ALARM:
-                if (!clock_boottime_supported())
-                        return false;
-
-                _fallthrough_;
-        default:
-                /* For everything else, check properly */
-                return clock_gettime(clock, &ts) >= 0;
-        }
-}
-
-#if 0 /* NM_IGNORED */
-int get_timezone(char **tz) {
-        _cleanup_free_ char *t = NULL;
-        const char *e;
-        char *z;
-        int r;
-
-        r = readlink_malloc("/etc/localtime", &t);
-        if (r < 0)
-                return r; /* returns EINVAL if not a symlink */
-
-        e = path_startswith(t, "/usr/share/zoneinfo/");
-        if (!e)
-                e = path_startswith(t, "../usr/share/zoneinfo/");
-        if (!e)
-                return -EINVAL;
-
-        if (!timezone_is_valid(e, LOG_DEBUG))
-                return -EINVAL;
-
-        z = strdup(e);
-        if (!z)
-                return -ENOMEM;
-
-        *tz = z;
-        return 0;
-}
-
-time_t mktime_or_timegm(struct tm *tm, bool utc) {
-        return utc ? timegm(tm) : mktime(tm);
-}
-
-struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) {
-        return utc ? gmtime_r(t, tm) : localtime_r(t, tm);
-}
-
-unsigned long usec_to_jiffies(usec_t u) {
-        static thread_local unsigned long hz = 0;
-        long r;
-
-        if (hz == 0) {
-                r = sysconf(_SC_CLK_TCK);
-
-                assert(r > 0);
-                hz = r;
-        }
-
-        return DIV_ROUND_UP(u , USEC_PER_SEC / hz);
-}
-
-usec_t usec_shift_clock(usec_t x, clockid_t from, clockid_t to) {
-        usec_t a, b;
-
-        if (x == USEC_INFINITY)
-                return USEC_INFINITY;
-        if (map_clock_id(from) == map_clock_id(to))
-                return x;
-
-        a = now(from);
-        b = now(to);
-
-        if (x > a)
-                /* x lies in the future */
-                return usec_add(b, usec_sub_unsigned(x, a));
-        else
-                /* x lies in the past */
-                return usec_sub_unsigned(b, usec_sub_unsigned(a, x));
-}
-
-bool in_utc_timezone(void) {
-        tzset();
-
-        return timezone == 0 && daylight == 0;
-}
-
-int time_change_fd(void) {
-
-        /* We only care for the cancellation event, hence we set the timeout to the latest possible value. */
-        static const struct itimerspec its = {
-                .it_value.tv_sec = TIME_T_MAX,
-        };
-
-        _cleanup_close_ int fd;
-
-        assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX));
-
-        /* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever CLOCK_REALTIME makes a jump relative to
-         * CLOCK_MONOTONIC. */
-
-        fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC);
-        if (fd < 0)
-                return -errno;
-
-        if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0)
-                return -errno;
-
-        return TAKE_FD(fd);
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/time-util.h b/src/systemd/src/basic/time-util.h
deleted file mode 100644
index 344f2dc5..00000000
--- a/src/systemd/src/basic/time-util.h
+++ /dev/null
@@ -1,183 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <inttypes.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <time.h>
-
-typedef uint64_t usec_t;
-typedef uint64_t nsec_t;
-
-#define PRI_NSEC PRIu64
-#define PRI_USEC PRIu64
-#define NSEC_FMT "%" PRI_NSEC
-#define USEC_FMT "%" PRI_USEC
-
-#include "macro.h"
-
-typedef struct dual_timestamp {
-        usec_t realtime;
-        usec_t monotonic;
-} dual_timestamp;
-
-typedef struct triple_timestamp {
-        usec_t realtime;
-        usec_t monotonic;
-        usec_t boottime;
-} triple_timestamp;
-
-#define USEC_INFINITY ((usec_t) -1)
-#define NSEC_INFINITY ((nsec_t) -1)
-
-#define MSEC_PER_SEC  1000ULL
-#define USEC_PER_SEC  ((usec_t) 1000000ULL)
-#define USEC_PER_MSEC ((usec_t) 1000ULL)
-#define NSEC_PER_SEC  ((nsec_t) 1000000000ULL)
-#define NSEC_PER_MSEC ((nsec_t) 1000000ULL)
-#define NSEC_PER_USEC ((nsec_t) 1000ULL)
-
-#define USEC_PER_MINUTE ((usec_t) (60ULL*USEC_PER_SEC))
-#define NSEC_PER_MINUTE ((nsec_t) (60ULL*NSEC_PER_SEC))
-#define USEC_PER_HOUR ((usec_t) (60ULL*USEC_PER_MINUTE))
-#define NSEC_PER_HOUR ((nsec_t) (60ULL*NSEC_PER_MINUTE))
-#define USEC_PER_DAY ((usec_t) (24ULL*USEC_PER_HOUR))
-#define NSEC_PER_DAY ((nsec_t) (24ULL*NSEC_PER_HOUR))
-#define USEC_PER_WEEK ((usec_t) (7ULL*USEC_PER_DAY))
-#define NSEC_PER_WEEK ((nsec_t) (7ULL*NSEC_PER_DAY))
-#define USEC_PER_MONTH ((usec_t) (2629800ULL*USEC_PER_SEC))
-#define NSEC_PER_MONTH ((nsec_t) (2629800ULL*NSEC_PER_SEC))
-#define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC))
-#define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC))
-
-/* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this
- * to 6. Let's rely on that. */
-#define FORMAT_TIMESTAMP_MAX (3+1+10+1+8+1+6+1+6+1)
-#define FORMAT_TIMESTAMP_WIDTH 28 /* when outputting, assume this width */
-#define FORMAT_TIMESTAMP_RELATIVE_MAX 256
-#define FORMAT_TIMESPAN_MAX 64
-
-#define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1)
-
-#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) {})
-#define TRIPLE_TIMESTAMP_NULL ((struct triple_timestamp) {})
-
-usec_t now(clockid_t clock);
-nsec_t now_nsec(clockid_t clock);
-
-dual_timestamp* dual_timestamp_get(dual_timestamp *ts);
-dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u);
-dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u);
-dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u);
-
-triple_timestamp* triple_timestamp_get(triple_timestamp *ts);
-triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u);
-
-#define DUAL_TIMESTAMP_HAS_CLOCK(clock)                               \
-        IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC)
-
-#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(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(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));
-}
-
-usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock);
-
-usec_t timespec_load(const struct timespec *ts) _pure_;
-nsec_t timespec_load_nsec(const struct timespec *ts) _pure_;
-struct timespec *timespec_store(struct timespec *ts, usec_t u);
-
-usec_t timeval_load(const struct timeval *tv) _pure_;
-struct timeval *timeval_store(struct timeval *tv, usec_t u);
-
-char *format_timestamp(char *buf, size_t l, usec_t t);
-char *format_timestamp_utc(char *buf, size_t l, usec_t t);
-char *format_timestamp_us(char *buf, size_t l, usec_t t);
-char *format_timestamp_us_utc(char *buf, size_t l, usec_t t);
-char *format_timestamp_relative(char *buf, size_t l, usec_t t);
-char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy);
-
-void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t);
-int dual_timestamp_deserialize(const char *value, dual_timestamp *t);
-int timestamp_deserialize(const char *value, usec_t *timestamp);
-
-int parse_timestamp(const char *t, usec_t *usec);
-
-int parse_sec(const char *t, usec_t *usec);
-int parse_sec_fix_0(const char *t, usec_t *usec);
-int parse_time(const char *t, usec_t *usec, usec_t default_unit);
-int parse_nsec(const char *t, nsec_t *nsec);
-
-bool ntp_synced(void);
-
-int get_timezones(char ***l);
-bool timezone_is_valid(const char *name, int log_level);
-
-bool clock_boottime_supported(void);
-bool clock_supported(clockid_t clock);
-clockid_t clock_boottime_or_monotonic(void);
-
-usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to);
-
-int get_timezone(char **timezone);
-
-time_t mktime_or_timegm(struct tm *tm, bool utc);
-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;
-
-        /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, and doesn't
-         * overflow. */
-
-        c = a + b;
-        if (c < a || c < b) /* overflow check */
-                return USEC_INFINITY;
-
-        return c;
-}
-
-static inline usec_t usec_sub_unsigned(usec_t timestamp, usec_t delta) {
-
-        if (timestamp == USEC_INFINITY) /* Make sure infinity doesn't degrade */
-                return USEC_INFINITY;
-        if (timestamp < delta)
-                return 0;
-
-        return timestamp - delta;
-}
-
-static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) {
-        if (delta < 0)
-                return usec_add(timestamp, (usec_t) (-delta));
-        else
-                return usec_sub_unsigned(timestamp, (usec_t) delta);
-}
-
-#if SIZEOF_TIME_T == 8
-/* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit year
- * territory. However, since we want to stay away from this in all timezones we take one day off. */
-#define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000)
-#elif SIZEOF_TIME_T == 4
-/* With a 32bit time_t we can't go beyond 2038... */
-#define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000)
-#else
-#error "Yuck, time_t is neither 4 nor 8 bytes wide?"
-#endif
-
-int time_change_fd(void);
diff --git a/src/systemd/src/basic/umask-util.h b/src/systemd/src/basic/umask-util.h
deleted file mode 100644
index e964292e..00000000
--- a/src/systemd/src/basic/umask-util.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include "macro.h"
-
-static inline void umaskp(mode_t *u) {
-        umask(*u);
-}
-
-#define _cleanup_umask_ _cleanup_(umaskp)
-
-struct _umask_struct_ {
-        mode_t mask;
-        bool quit;
-};
-
-static inline void _reset_umask_(struct _umask_struct_ *s) {
-        umask(s->mask);
-};
-
-#define RUN_WITH_UMASK(mask)                                            \
-        for (_cleanup_(_reset_umask_) struct _umask_struct_ _saved_umask_ = { umask(mask), false }; \
-             !_saved_umask_.quit ;                                      \
-             _saved_umask_.quit = true)
diff --git a/src/systemd/src/basic/utf8.c b/src/systemd/src/basic/utf8.c
deleted file mode 100644
index 7238bca7..00000000
--- a/src/systemd/src/basic/utf8.c
+++ /dev/null
@@ -1,453 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-/* Parts of this file are based on the GLIB utf8 validation functions. The
- * original license text follows. */
-
-/* gutf8.c - Operations on UTF-8 strings.
- *
- * Copyright (C) 1999 Tom Tromey
- * Copyright (C) 2000 Red Hat, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library 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
- */
-
-#include "nm-sd-adapt.h"
-
-#include <errno.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "alloc-util.h"
-#include "gunicode.h"
-#include "hexdecoct.h"
-#include "macro.h"
-#include "utf8.h"
-
-bool unichar_is_valid(char32_t ch) {
-
-        if (ch >= 0x110000) /* End of unicode space */
-                return false;
-        if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */
-                return false;
-        if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */
-                return false;
-        if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */
-                return false;
-
-        return true;
-}
-
-static bool unichar_is_control(char32_t ch) {
-
-        /*
-          0 to ' '-1 is the C0 range.
-          DEL=0x7F, and DEL+1 to 0x9F is C1 range.
-          '\t' is in C0 range, but more or less harmless and commonly used.
-        */
-
-        return (ch < ' ' && !IN_SET(ch, '\t', '\n')) ||
-                (0x7F <= ch && ch <= 0x9F);
-}
-
-/* count of characters used to encode one unicode char */
-static int utf8_encoded_expected_len(const char *str) {
-        unsigned char c;
-
-        assert(str);
-
-        c = (unsigned char) str[0];
-        if (c < 0x80)
-                return 1;
-        if ((c & 0xe0) == 0xc0)
-                return 2;
-        if ((c & 0xf0) == 0xe0)
-                return 3;
-        if ((c & 0xf8) == 0xf0)
-                return 4;
-        if ((c & 0xfc) == 0xf8)
-                return 5;
-        if ((c & 0xfe) == 0xfc)
-                return 6;
-
-        return 0;
-}
-
-/* decode one unicode char */
-int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) {
-        char32_t unichar;
-        int len, i;
-
-        assert(str);
-
-        len = utf8_encoded_expected_len(str);
-
-        switch (len) {
-        case 1:
-                *ret_unichar = (char32_t)str[0];
-                return 0;
-        case 2:
-                unichar = str[0] & 0x1f;
-                break;
-        case 3:
-                unichar = (char32_t)str[0] & 0x0f;
-                break;
-        case 4:
-                unichar = (char32_t)str[0] & 0x07;
-                break;
-        case 5:
-                unichar = (char32_t)str[0] & 0x03;
-                break;
-        case 6:
-                unichar = (char32_t)str[0] & 0x01;
-                break;
-        default:
-                return -EINVAL;
-        }
-
-        for (i = 1; i < len; i++) {
-                if (((char32_t)str[i] & 0xc0) != 0x80)
-                        return -EINVAL;
-                unichar <<= 6;
-                unichar |= (char32_t)str[i] & 0x3f;
-        }
-
-        *ret_unichar = unichar;
-
-        return 0;
-}
-
-bool utf8_is_printable_newline(const char* str, size_t length, bool newline) {
-        const char *p;
-
-        assert(str);
-
-        for (p = str; length;) {
-                int encoded_len, r;
-                char32_t val;
-
-                encoded_len = utf8_encoded_valid_unichar(p);
-                if (encoded_len < 0 ||
-                    (size_t) encoded_len > length)
-                        return false;
-
-                r = utf8_encoded_to_unichar(p, &val);
-                if (r < 0 ||
-                    unichar_is_control(val) ||
-                    (!newline && val == '\n'))
-                        return false;
-
-                length -= encoded_len;
-                p += encoded_len;
-        }
-
-        return true;
-}
-
-const char *utf8_is_valid(const char *str) {
-        const uint8_t *p;
-
-        assert(str);
-
-        for (p = (const uint8_t*) str; *p; ) {
-                int len;
-
-                len = utf8_encoded_valid_unichar((const char *)p);
-                if (len < 0)
-                        return NULL;
-
-                p += len;
-        }
-
-        return str;
-}
-
-char *utf8_escape_invalid(const char *str) {
-        char *p, *s;
-
-        assert(str);
-
-        p = s = malloc(strlen(str) * 4 + 1);
-        if (!p)
-                return NULL;
-
-        while (*str) {
-                int len;
-
-                len = utf8_encoded_valid_unichar(str);
-                if (len > 0) {
-                        s = mempcpy(s, str, len);
-                        str += len;
-                } else {
-                        s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER);
-                        str += 1;
-                }
-        }
-
-        *s = '\0';
-
-        return p;
-}
-
-char *utf8_escape_non_printable(const char *str) {
-        char *p, *s;
-
-        assert(str);
-
-        p = s = malloc(strlen(str) * 4 + 1);
-        if (!p)
-                return NULL;
-
-        while (*str) {
-                int len;
-
-                len = utf8_encoded_valid_unichar(str);
-                if (len > 0) {
-                        if (utf8_is_printable(str, len)) {
-                                s = mempcpy(s, str, len);
-                                str += len;
-                        } else {
-                                while (len > 0) {
-                                        *(s++) = '\\';
-                                        *(s++) = 'x';
-                                        *(s++) = hexchar((int) *str >> 4);
-                                        *(s++) = hexchar((int) *str);
-
-                                        str += 1;
-                                        len--;
-                                }
-                        }
-                } else {
-                        s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER);
-                        str += 1;
-                }
-        }
-
-        *s = '\0';
-
-        return p;
-}
-
-char *ascii_is_valid(const char *str) {
-        const char *p;
-
-        /* Check whether the string consists of valid ASCII bytes,
-         * i.e values between 0 and 127, inclusive. */
-
-        assert(str);
-
-        for (p = str; *p; p++)
-                if ((unsigned char) *p >= 128)
-                        return NULL;
-
-        return (char*) str;
-}
-
-char *ascii_is_valid_n(const char *str, size_t len) {
-        size_t i;
-
-        /* Very similar to ascii_is_valid(), but checks exactly len
-         * bytes and rejects any NULs in that range. */
-
-        assert(str);
-
-        for (i = 0; i < len; i++)
-                if ((unsigned char) str[i] >= 128 || str[i] == 0)
-                        return NULL;
-
-        return (char*) str;
-}
-
-/**
- * utf8_encode_unichar() - Encode single UCS-4 character as UTF-8
- * @out_utf8: output buffer of at least 4 bytes or NULL
- * @g: UCS-4 character to encode
- *
- * This encodes a single UCS-4 character as UTF-8 and writes it into @out_utf8.
- * The length of the character is returned. It is not zero-terminated! If the
- * output buffer is NULL, only the length is returned.
- *
- * Returns: The length in bytes that the UTF-8 representation does or would
- *          occupy.
- */
-size_t utf8_encode_unichar(char *out_utf8, char32_t g) {
-
-        if (g < (1 << 7)) {
-                if (out_utf8)
-                        out_utf8[0] = g & 0x7f;
-                return 1;
-        } else if (g < (1 << 11)) {
-                if (out_utf8) {
-                        out_utf8[0] = 0xc0 | ((g >> 6) & 0x1f);
-                        out_utf8[1] = 0x80 | (g & 0x3f);
-                }
-                return 2;
-        } else if (g < (1 << 16)) {
-                if (out_utf8) {
-                        out_utf8[0] = 0xe0 | ((g >> 12) & 0x0f);
-                        out_utf8[1] = 0x80 | ((g >> 6) & 0x3f);
-                        out_utf8[2] = 0x80 | (g & 0x3f);
-                }
-                return 3;
-        } else if (g < (1 << 21)) {
-                if (out_utf8) {
-                        out_utf8[0] = 0xf0 | ((g >> 18) & 0x07);
-                        out_utf8[1] = 0x80 | ((g >> 12) & 0x3f);
-                        out_utf8[2] = 0x80 | ((g >> 6) & 0x3f);
-                        out_utf8[3] = 0x80 | (g & 0x3f);
-                }
-                return 4;
-        }
-
-        return 0;
-}
-
-char *utf16_to_utf8(const void *s, size_t length) {
-        const uint8_t *f;
-        char *r, *t;
-
-        r = new(char, (length * 4 + 1) / 2 + 1);
-        if (!r)
-                return NULL;
-
-        f = s;
-        t = r;
-
-        while (f < (const uint8_t*) s + length) {
-                char16_t w1, w2;
-
-                /* see RFC 2781 section 2.2 */
-
-                w1 = f[1] << 8 | f[0];
-                f += 2;
-
-                if (!utf16_is_surrogate(w1)) {
-                        t += utf8_encode_unichar(t, w1);
-
-                        continue;
-                }
-
-                if (utf16_is_trailing_surrogate(w1))
-                        continue;
-                else if (f >= (const uint8_t*) s + length)
-                        break;
-
-                w2 = f[1] << 8 | f[0];
-                f += 2;
-
-                if (!utf16_is_trailing_surrogate(w2)) {
-                        f -= 2;
-                        continue;
-                }
-
-                t += utf8_encode_unichar(t, utf16_surrogate_pair_to_unichar(w1, w2));
-        }
-
-        *t = 0;
-        return r;
-}
-
-/* expected size used to encode one unicode char */
-static int utf8_unichar_to_encoded_len(char32_t unichar) {
-
-        if (unichar < 0x80)
-                return 1;
-        if (unichar < 0x800)
-                return 2;
-        if (unichar < 0x10000)
-                return 3;
-        if (unichar < 0x200000)
-                return 4;
-        if (unichar < 0x4000000)
-                return 5;
-
-        return 6;
-}
-
-/* validate one encoded unicode char and return its length */
-int utf8_encoded_valid_unichar(const char *str) {
-        int len, i, r;
-        char32_t unichar;
-
-        assert(str);
-
-        len = utf8_encoded_expected_len(str);
-        if (len == 0)
-                return -EINVAL;
-
-        /* ascii is valid */
-        if (len == 1)
-                return 1;
-
-        /* check if expected encoded chars are available */
-        for (i = 0; i < len; i++)
-                if ((str[i] & 0x80) != 0x80)
-                        return -EINVAL;
-
-        r = utf8_encoded_to_unichar(str, &unichar);
-        if (r < 0)
-                return r;
-
-        /* check if encoded length matches encoded value */
-        if (utf8_unichar_to_encoded_len(unichar) != len)
-                return -EINVAL;
-
-        /* check if value has valid range */
-        if (!unichar_is_valid(unichar))
-                return -EINVAL;
-
-        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;
-}
-
-#if 0 /* NM_IGNORED */
-size_t utf8_console_width(const char *str) {
-        size_t n = 0;
-
-        /* Returns the approximate width a string will take on screen when printed on a character cell
-         * terminal/console. */
-
-        while (*str != 0) {
-                char32_t c;
-
-                if (utf8_encoded_to_unichar(str, &c) < 0)
-                        return (size_t) -1;
-
-                str = utf8_next_char(str);
-
-                n += unichar_iswide(c) ? 2 : 1;
-        }
-
-        return n;
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/utf8.h b/src/systemd/src/basic/utf8.h
deleted file mode 100644
index f37f87ff..00000000
--- a/src/systemd/src/basic/utf8.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#if 0 /* NM_IGNORED */
-#include <uchar.h>
-#endif /* NM_IGNORED */
-
-#include "macro.h"
-#include "missing.h"
-
-#define UTF8_REPLACEMENT_CHARACTER "\xef\xbf\xbd"
-#define UTF8_BYTE_ORDER_MARK "\xef\xbb\xbf"
-
-bool unichar_is_valid(char32_t c);
-
-const char *utf8_is_valid(const char *s) _pure_;
-char *ascii_is_valid(const char *s) _pure_;
-char *ascii_is_valid_n(const char *str, size_t len);
-
-bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pure_;
-#define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true)
-
-char *utf8_escape_invalid(const char *s);
-char *utf8_escape_non_printable(const char *str);
-
-size_t utf8_encode_unichar(char *out_utf8, char32_t g);
-char *utf16_to_utf8(const void *s, size_t length);
-
-int utf8_encoded_valid_unichar(const char *str);
-int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar);
-
-static inline bool utf16_is_surrogate(char16_t c) {
-        return (0xd800 <= c && c <= 0xdfff);
-}
-
-static inline bool utf16_is_trailing_surrogate(char16_t c) {
-        return (0xdc00 <= c && c <= 0xdfff);
-}
-
-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);
-size_t utf8_console_width(const char *str);
diff --git a/src/systemd/src/basic/util.c b/src/systemd/src/basic/util.c
deleted file mode 100644
index 2c6101ea..00000000
--- a/src/systemd/src/basic/util.c
+++ /dev/null
@@ -1,609 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt.h"
-
-#include <alloca.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <sched.h>
-#include <signal.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#include <sys/prctl.h>
-#include <sys/statfs.h>
-#include <sys/sysmacros.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "alloc-util.h"
-#include "btrfs-util.h"
-#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"
-#include "format-util.h"
-#include "hashmap.h"
-#include "hostname-util.h"
-#include "log.h"
-#include "macro.h"
-#include "missing.h"
-#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"
-#include "string-util.h"
-#include "strv.h"
-#include "time-util.h"
-#include "umask-util.h"
-#include "user-util.h"
-#include "util.h"
-#include "virt.h"
-
-#if 0 /* NM_IGNORED */
-int saved_argc = 0;
-char **saved_argv = NULL;
-static int saved_in_initrd = -1;
-#endif /* NM_IGNORED */
-
-size_t page_size(void) {
-        static thread_local size_t pgsz = 0;
-        long r;
-
-        if (_likely_(pgsz > 0))
-                return pgsz;
-
-        r = sysconf(_SC_PAGESIZE);
-        assert(r > 0);
-
-        pgsz = (size_t) r;
-        return pgsz;
-}
-
-#if 0 /* NM_IGNORED */
-bool plymouth_running(void) {
-        return access("/run/plymouth/pid", F_OK) >= 0;
-}
-
-bool display_is_local(const char *display) {
-        assert(display);
-
-        return
-                display[0] == ':' &&
-                display[1] >= '0' &&
-                display[1] <= '9';
-}
-
-bool kexec_loaded(void) {
-       _cleanup_free_ char *s = NULL;
-
-       if (read_one_line_file("/sys/kernel/kexec_loaded", &s) < 0)
-               return false;
-
-       return s[0] == '1';
-}
-
-int prot_from_flags(int flags) {
-
-        switch (flags & O_ACCMODE) {
-
-        case O_RDONLY:
-                return PROT_READ;
-
-        case O_WRONLY:
-                return PROT_WRITE;
-
-        case O_RDWR:
-                return PROT_READ|PROT_WRITE;
-
-        default:
-                return -EINVAL;
-        }
-}
-
-bool in_initrd(void) {
-        struct statfs s;
-
-        if (saved_in_initrd >= 0)
-                return saved_in_initrd;
-
-        /* We make two checks here:
-         *
-         * 1. the flag file /etc/initrd-release must exist
-         * 2. the root file system must be a memory file system
-         *
-         * The second check is extra paranoia, since misdetecting an
-         * initrd can have bad consequences due the initrd
-         * emptying when transititioning to the main systemd.
-         */
-
-        saved_in_initrd = access("/etc/initrd-release", F_OK) >= 0 &&
-                          statfs("/", &s) >= 0 &&
-                          is_temporary_fs(&s);
-
-        return saved_in_initrd;
-}
-
-void in_initrd_force(bool value) {
-        saved_in_initrd = value;
-}
-
-/* hey glibc, APIs with callbacks without a user pointer are so useless */
-void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
-                 int (*compar) (const void *, const void *, void *), void *arg) {
-        size_t l, u, idx;
-        const void *p;
-        int comparison;
-
-        assert(!size_multiply_overflow(nmemb, size));
-
-        l = 0;
-        u = nmemb;
-        while (l < u) {
-                idx = (l + u) / 2;
-                p = (const uint8_t*) base + idx * size;
-                comparison = compar(key, p, arg);
-                if (comparison < 0)
-                        u = idx;
-                else if (comparison > 0)
-                        l = idx + 1;
-                else
-                        return (void *)p;
-        }
-        return NULL;
-}
-
-int on_ac_power(void) {
-        bool found_offline = false, found_online = false;
-        _cleanup_closedir_ DIR *d = NULL;
-        struct dirent *de;
-
-        d = opendir("/sys/class/power_supply");
-        if (!d)
-                return errno == ENOENT ? true : -errno;
-
-        FOREACH_DIRENT(de, d, return -errno) {
-                _cleanup_close_ int fd = -1, device = -1;
-                char contents[6];
-                ssize_t n;
-
-                device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY);
-                if (device < 0) {
-                        if (IN_SET(errno, ENOENT, ENOTDIR))
-                                continue;
-
-                        return -errno;
-                }
-
-                fd = openat(device, "type", O_RDONLY|O_CLOEXEC|O_NOCTTY);
-                if (fd < 0) {
-                        if (errno == ENOENT)
-                                continue;
-
-                        return -errno;
-                }
-
-                n = read(fd, contents, sizeof(contents));
-                if (n < 0)
-                        return -errno;
-
-                if (n != 6 || memcmp(contents, "Mains\n", 6))
-                        continue;
-
-                safe_close(fd);
-                fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY);
-                if (fd < 0) {
-                        if (errno == ENOENT)
-                                continue;
-
-                        return -errno;
-                }
-
-                n = read(fd, contents, sizeof(contents));
-                if (n < 0)
-                        return -errno;
-
-                if (n != 2 || contents[1] != '\n')
-                        return -EIO;
-
-                if (contents[0] == '1') {
-                        found_online = true;
-                        break;
-                } else if (contents[0] == '0')
-                        found_offline = true;
-                else
-                        return -EIO;
-        }
-
-        return found_online || !found_offline;
-}
-
-int container_get_leader(const char *machine, pid_t *pid) {
-        _cleanup_free_ char *s = NULL, *class = NULL;
-        const char *p;
-        pid_t leader;
-        int r;
-
-        assert(machine);
-        assert(pid);
-
-        if (streq(machine, ".host")) {
-                *pid = 1;
-                return 0;
-        }
-
-        if (!machine_name_is_valid(machine))
-                return -EINVAL;
-
-        p = strjoina("/run/systemd/machines/", machine);
-        r = parse_env_file(NULL, p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL);
-        if (r == -ENOENT)
-                return -EHOSTDOWN;
-        if (r < 0)
-                return r;
-        if (!s)
-                return -EIO;
-
-        if (!streq_ptr(class, "container"))
-                return -EIO;
-
-        r = parse_pid(s, &leader);
-        if (r < 0)
-                return r;
-        if (leader <= 1)
-                return -EIO;
-
-        *pid = leader;
-        return 0;
-}
-
-int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd) {
-        _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, netnsfd = -1, usernsfd = -1;
-        int rfd = -1;
-
-        assert(pid >= 0);
-
-        if (mntns_fd) {
-                const char *mntns;
-
-                mntns = procfs_file_alloca(pid, "ns/mnt");
-                mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
-                if (mntnsfd < 0)
-                        return -errno;
-        }
-
-        if (pidns_fd) {
-                const char *pidns;
-
-                pidns = procfs_file_alloca(pid, "ns/pid");
-                pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
-                if (pidnsfd < 0)
-                        return -errno;
-        }
-
-        if (netns_fd) {
-                const char *netns;
-
-                netns = procfs_file_alloca(pid, "ns/net");
-                netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
-                if (netnsfd < 0)
-                        return -errno;
-        }
-
-        if (userns_fd) {
-                const char *userns;
-
-                userns = procfs_file_alloca(pid, "ns/user");
-                usernsfd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
-                if (usernsfd < 0 && errno != ENOENT)
-                        return -errno;
-        }
-
-        if (root_fd) {
-                const char *root;
-
-                root = procfs_file_alloca(pid, "root");
-                rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
-                if (rfd < 0)
-                        return -errno;
-        }
-
-        if (pidns_fd)
-                *pidns_fd = pidnsfd;
-
-        if (mntns_fd)
-                *mntns_fd = mntnsfd;
-
-        if (netns_fd)
-                *netns_fd = netnsfd;
-
-        if (userns_fd)
-                *userns_fd = usernsfd;
-
-        if (root_fd)
-                *root_fd = rfd;
-
-        pidnsfd = mntnsfd = netnsfd = usernsfd = -1;
-
-        return 0;
-}
-
-int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) {
-        if (userns_fd >= 0) {
-                /* Can't setns to your own userns, since then you could
-                 * escalate from non-root to root in your own namespace, so
-                 * check if namespaces equal before attempting to enter. */
-                _cleanup_free_ char *userns_fd_path = NULL;
-                int r;
-                if (asprintf(&userns_fd_path, "/proc/self/fd/%d", userns_fd) < 0)
-                        return -ENOMEM;
-
-                r = files_same(userns_fd_path, "/proc/self/ns/user", 0);
-                if (r < 0)
-                        return r;
-                if (r)
-                        userns_fd = -1;
-        }
-
-        if (pidns_fd >= 0)
-                if (setns(pidns_fd, CLONE_NEWPID) < 0)
-                        return -errno;
-
-        if (mntns_fd >= 0)
-                if (setns(mntns_fd, CLONE_NEWNS) < 0)
-                        return -errno;
-
-        if (netns_fd >= 0)
-                if (setns(netns_fd, CLONE_NEWNET) < 0)
-                        return -errno;
-
-        if (userns_fd >= 0)
-                if (setns(userns_fd, CLONE_NEWUSER) < 0)
-                        return -errno;
-
-        if (root_fd >= 0) {
-                if (fchdir(root_fd) < 0)
-                        return -errno;
-
-                if (chroot(".") < 0)
-                        return -errno;
-        }
-
-        return reset_uid_gid();
-}
-
-uint64_t physical_memory(void) {
-        _cleanup_free_ char *root = NULL, *value = NULL;
-        uint64_t mem, lim;
-        size_t ps;
-        long sc;
-        int r;
-
-        /* We return this as uint64_t in case we are running as 32bit process on a 64bit kernel with huge amounts of
-         * memory.
-         *
-         * In order to support containers nicely that have a configured memory limit we'll take the minimum of the
-         * physically reported amount of memory and the limit configured for the root cgroup, if there is any. */
-
-        sc = sysconf(_SC_PHYS_PAGES);
-        assert(sc > 0);
-
-        ps = page_size();
-        mem = (uint64_t) sc * (uint64_t) ps;
-
-        r = cg_get_root_path(&root);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to determine root cgroup, ignoring cgroup memory limit: %m");
-                return mem;
-        }
-
-        r = cg_all_unified();
-        if (r < 0) {
-                log_debug_errno(r, "Failed to determine root unified mode, ignoring cgroup memory limit: %m");
-                return mem;
-        }
-        if (r > 0) {
-                r = cg_get_attribute("memory", root, "memory.max", &value);
-                if (r < 0) {
-                        log_debug_errno(r, "Failed to read memory.max cgroup attribute, ignoring cgroup memory limit: %m");
-                        return mem;
-                }
-
-                if (streq(value, "max"))
-                        return mem;
-        } else {
-                r = cg_get_attribute("memory", root, "memory.limit_in_bytes", &value);
-                if (r < 0) {
-                        log_debug_errno(r, "Failed to read memory.limit_in_bytes cgroup attribute, ignoring cgroup memory limit: %m");
-                        return mem;
-                }
-        }
-
-        r = safe_atou64(value, &lim);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to parse cgroup memory limit '%s', ignoring: %m", value);
-                return mem;
-        }
-        if (lim == UINT64_MAX)
-                return mem;
-
-        /* Make sure the limit is a multiple of our own page size */
-        lim /= ps;
-        lim *= ps;
-
-        return MIN(mem, lim);
-}
-
-uint64_t physical_memory_scale(uint64_t v, uint64_t max) {
-        uint64_t p, m, ps, r;
-
-        assert(max > 0);
-
-        /* Returns the physical memory size, multiplied by v divided by max. Returns UINT64_MAX on overflow. On success
-         * the result is a multiple of the page size (rounds down). */
-
-        ps = page_size();
-        assert(ps > 0);
-
-        p = physical_memory() / ps;
-        assert(p > 0);
-
-        m = p * v;
-        if (m / p != v)
-                return UINT64_MAX;
-
-        m /= max;
-
-        r = m * ps;
-        if (r / ps != m)
-                return UINT64_MAX;
-
-        return r;
-}
-
-uint64_t system_tasks_max(void) {
-
-        uint64_t a = TASKS_MAX, b = TASKS_MAX;
-        _cleanup_free_ char *root = NULL;
-        int r;
-
-        /* Determine the maximum number of tasks that may run on this system. We check three sources to determine this
-         * limit:
-         *
-         * a) the maximum tasks value the kernel allows on this architecture
-         * b) the cgroups pids_max attribute for the system
-         * c) the kernel's configured maximum PID value
-         *
-         * And then pick the smallest of the three */
-
-        r = procfs_tasks_get_limit(&a);
-        if (r < 0)
-                log_debug_errno(r, "Failed to read maximum number of tasks from /proc, ignoring: %m");
-
-        r = cg_get_root_path(&root);
-        if (r < 0)
-                log_debug_errno(r, "Failed to determine cgroup root path, ignoring: %m");
-        else {
-                _cleanup_free_ char *value = NULL;
-
-                r = cg_get_attribute("pids", root, "pids.max", &value);
-                if (r < 0)
-                        log_debug_errno(r, "Failed to read pids.max attribute of cgroup root, ignoring: %m");
-                else if (!streq(value, "max")) {
-                        r = safe_atou64(value, &b);
-                        if (r < 0)
-                                log_debug_errno(r, "Failed to parse pids.max attribute of cgroup root, ignoring: %m");
-                }
-        }
-
-        return MIN3(TASKS_MAX,
-                    a <= 0 ? TASKS_MAX : a,
-                    b <= 0 ? TASKS_MAX : b);
-}
-
-uint64_t system_tasks_max_scale(uint64_t v, uint64_t max) {
-        uint64_t t, m;
-
-        assert(max > 0);
-
-        /* Multiply the system's task value by the fraction v/max. Hence, if max==100 this calculates percentages
-         * relative to the system's maximum number of tasks. Returns UINT64_MAX on overflow. */
-
-        t = system_tasks_max();
-        assert(t > 0);
-
-        m = t * v;
-        if (m / t != v) /* overflow? */
-                return UINT64_MAX;
-
-        return m / max;
-}
-
-int version(void) {
-        puts(PACKAGE_STRING "\n"
-             SYSTEMD_FEATURES);
-        return 0;
-}
-
-/* This is a direct translation of str_verscmp from boot.c */
-static bool is_digit(int c) {
-        return c >= '0' && c <= '9';
-}
-
-static int c_order(int c) {
-        if (c == 0 || is_digit(c))
-                return 0;
-
-        if ((c >= 'a') && (c <= 'z'))
-                return c;
-
-        return c + 0x10000;
-}
-
-int str_verscmp(const char *s1, const char *s2) {
-        const char *os1, *os2;
-
-        assert(s1);
-        assert(s2);
-
-        os1 = s1;
-        os2 = s2;
-
-        while (*s1 || *s2) {
-                int first;
-
-                while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) {
-                        int order;
-
-                        order = c_order(*s1) - c_order(*s2);
-                        if (order != 0)
-                                return order;
-                        s1++;
-                        s2++;
-                }
-
-                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++;
-                }
-
-                if (is_digit(*s1))
-                        return 1;
-                if (is_digit(*s2))
-                        return -1;
-
-                if (first != 0)
-                        return first;
-        }
-
-        return strcmp(os1, os2);
-}
-
-/* Turn off core dumps but only if we're running outside of a container. */
-void disable_coredumps(void) {
-        int r;
-
-        if (detect_container() > 0)
-                return;
-
-        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
deleted file mode 100644
index 308933ac..00000000
--- a/src/systemd/src/basic/util.h
+++ /dev/null
@@ -1,209 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <alloca.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <locale.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/inotify.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/statfs.h>
-#include <sys/sysmacros.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "format-util.h"
-#include "macro.h"
-#include "missing.h"
-#include "time-util.h"
-
-size_t page_size(void) _pure_;
-#define PAGE_ALIGN(l) ALIGN_TO((l), page_size())
-
-static inline const char* yes_no(bool b) {
-        return b ? "yes" : "no";
-}
-
-static inline const char* true_false(bool b) {
-        return b ? "true" : "false";
-}
-
-static inline const char* one_zero(bool b) {
-        return b ? "1" : "0";
-}
-
-static inline const char* enable_disable(bool b) {
-        return b ? "enable" : "disable";
-}
-
-bool plymouth_running(void);
-
-bool display_is_local(const char *display) _pure_;
-
-#define NULSTR_FOREACH(i, l)                                    \
-        for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1)
-
-#define NULSTR_FOREACH_PAIR(i, j, l)                             \
-        for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i))
-
-extern int saved_argc;
-extern char **saved_argv;
-
-bool kexec_loaded(void);
-
-int prot_from_flags(int flags) _const_;
-
-bool in_initrd(void);
-void in_initrd_force(bool value);
-
-void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
-                 int (*compar) (const void *, const void *, void *),
-                 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.
- */
-static inline void qsort_safe(void *base, size_t nmemb, size_t size, comparison_fn_t compar) {
-        if (nmemb <= 1)
-                return;
-
-        assert(base);
-        qsort(base, nmemb, size, compar);
-}
-
-/* A wrapper around the above, but that adds typesafety: the element size is automatically derived from the type and so
- * is the prototype for the comparison function */
-#define typesafe_qsort(p, n, func)                                      \
-        ({                                                              \
-                int (*_func_)(const typeof(p[0])*, const typeof(p[0])*) = func; \
-                qsort_safe((p), (n), sizeof((p)[0]), (__compar_fn_t) _func_); \
-        })
-
-static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, int (*compar)(const void*, const void*, void*), void *userdata) {
-        if (nmemb <= 1)
-                return;
-
-        assert(base);
-        qsort_r(base, nmemb, size, compar, userdata);
-}
-
-/* Normal memcpy requires src to be nonnull. We do nothing if n is 0. */
-static inline void memcpy_safe(void *dst, const void *src, size_t n) {
-        if (n == 0)
-                return;
-        assert(src);
-        memcpy(dst, src, n);
-}
-
-/* Normal memcmp requires s1 and s2 to be nonnull. We do nothing if n is 0. */
-static inline int memcmp_safe(const void *s1, const void *s2, size_t n) {
-        if (n == 0)
-                return 0;
-        assert(s1);
-        assert(s2);
-        return memcmp(s1, s2, n);
-}
-
-int on_ac_power(void);
-
-#define memzero(x,l) (memset((x), 0, (l)))
-#define zero(x) (memzero(&(x), sizeof(x)))
-
-static inline void *mempset(void *s, int c, size_t n) {
-        memset(s, c, n);
-        return (uint8_t*)s + n;
-}
-
-static inline void _reset_errno_(int *saved_errno) {
-        errno = *saved_errno;
-}
-
-#define PROTECT_ERRNO _cleanup_(_reset_errno_) __attribute__((unused)) int _saved_errno_ = errno
-
-static inline int negative_errno(void) {
-        /* This helper should be used to shut up gcc if you know 'errno' is
-         * negative. Instead of "return -errno;", use "return negative_errno();"
-         * It will suppress bogus gcc warnings in case it assumes 'errno' might
-         * be 0 and thus the caller's error-handling might not be triggered. */
-        assert_return(errno > 0, -EINVAL);
-        return -errno;
-}
-
-static inline unsigned u64log2(uint64_t n) {
-#if __SIZEOF_LONG_LONG__ == 8
-        return (n > 1) ? (unsigned) __builtin_clzll(n) ^ 63U : 0;
-#else
-#error "Wut?"
-#endif
-}
-
-static inline unsigned u32ctz(uint32_t n) {
-#if __SIZEOF_INT__ == 4
-        return __builtin_ctz(n);
-#else
-#error "Wut?"
-#endif
-}
-
-static inline unsigned log2i(int x) {
-        assert(x > 0);
-
-        return __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1;
-}
-
-static inline unsigned log2u(unsigned x) {
-        assert(x > 0);
-
-        return sizeof(unsigned) * 8 - __builtin_clz(x) - 1;
-}
-
-static inline unsigned log2u_round_up(unsigned x) {
-        assert(x > 0);
-
-        if (x == 1)
-                return 0;
-
-        return log2u(x - 1) + 1;
-}
-
-int container_get_leader(const char *machine, pid_t *pid);
-
-int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd);
-int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
-
-uint64_t physical_memory(void);
-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 version(void);
-
-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 4fbecb54..154ba6b7 100644
--- a/src/systemd/src/libsystemd-network/arp-util.c
+++ b/src/systemd/src/libsystemd-network/arp-util.c
@@ -3,7 +3,7 @@
   Copyright © 2014 Axis Communications AB. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #include <linux/filter.h>
 #include <arpa/inet.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.c b/src/systemd/src/libsystemd-network/dhcp-identifier.c
index b1afbf00..7fc8248a 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.c
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.c
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #include <linux/if_infiniband.h>
 #include <net/if_arp.h>
@@ -13,13 +13,9 @@
 #include "network-internal.h"
 #include "siphash24.h"
 #include "sparse-endian.h"
+#include "stdio-util.h"
 #include "virt.h"
 
-#if 0 /* NM_IGNORED */
-#else /* NM_IGNORED */
-#include <net/if.h>
-#endif /* NM_IGNORED */
-
 #define SYSTEMD_PEN    43793
 #define HASH_KEY       SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
 #define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03)
@@ -123,8 +119,13 @@ int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) {
         assert(len);
 
         r = sd_id128_get_machine(&machine_id);
-        if (r < 0)
+        if (r < 0) {
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+                machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10);
+#else
                 return r;
+#endif
+        }
 
         unaligned_write_be16(&duid->type, DUID_TYPE_EN);
         unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN);
@@ -160,38 +161,37 @@ int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len) {
 }
 #endif
 
-int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_id) {
+int dhcp_identifier_set_iaid(
+                int ifindex,
+                const uint8_t *mac,
+                size_t mac_len,
+                bool legacy_unstable_byteorder,
+                void *_id) {
 #if 0 /* NM_IGNORED */
         /* name is a pointer to memory in the sd_device struct, so must
          * have the same scope */
         _cleanup_(sd_device_unrefp) sd_device *device = NULL;
-#else /* NM_IGNORED */
-        char name_buf[IF_NAMESIZE];
-#endif /* NM_IGNORED */
         const char *name = NULL;
         uint64_t id;
+        uint32_t id32;
 
-#if 0 /* NM_IGNORED */
         if (detect_container() <= 0) {
                 /* not in a container, udev will be around */
-                char ifindex_str[2 + DECIMAL_STR_MAX(int)];
-                int initialized, r;
+                char ifindex_str[1 + DECIMAL_STR_MAX(int)];
+                int r;
 
-                sprintf(ifindex_str, "n%d", ifindex);
+                xsprintf(ifindex_str, "n%d", ifindex);
                 if (sd_device_new_from_device_id(&device, ifindex_str) >= 0) {
-                        r = sd_device_get_is_initialized(device, &initialized);
+                        r = sd_device_get_is_initialized(device);
                         if (r < 0)
                                 return r;
-                        if (!initialized)
+                        if (r == 0)
                                 /* not yet ready */
                                 return -EBUSY;
 
                         name = net_get_name(device);
                 }
         }
-#else /* NM_IGNORED */
-        name = if_indextoname(ifindex, name_buf);
-#endif /* NM_IGNORED */
 
         if (name)
                 id = siphash24(name, strlen(name), HASH_KEY.bytes);
@@ -199,10 +199,23 @@ int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_i
                 /* fall back to MAC address if no predictable name available */
                 id = siphash24(mac, mac_len, HASH_KEY.bytes);
 
-        id = htole64(id);
+        id32 = (id & 0xffffffff) ^ (id >> 32);
 
-        /* fold into 32 bits */
-        unaligned_write_be32(_id, (id & 0xffffffff) ^ (id >> 32));
+        if (legacy_unstable_byteorder)
+                /* for historical reasons (a bug), the bits were swapped and thus
+                 * the result was endianness dependent. Preserve that behavior. */
+                id32 = __bswap_32(id32);
+        else
+                /* the fixed behavior returns a stable byte order. Since LE is expected
+                 * to be more common, swap the bytes on LE to give the same as legacy
+                 * behavior. */
+                id32 = be32toh(id32);
 
+        unaligned_write_ne32(_id, id32);
         return 0;
+#else /* NM_IGNORED */
+        /* for NetworkManager, we don't use this function and we should never call here.
+         * This got replaced by nm_utils_create_dhcp_iaid(). */
+        g_return_val_if_reached (-EINVAL);
+#endif /* NM_IGNORED */
 }
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.h b/src/systemd/src/libsystemd-network/dhcp-identifier.h
index e6834039..b3115125 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.h
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.h
@@ -57,4 +57,4 @@ int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *add
 int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
 int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len);
 int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len);
-int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_id);
+int dhcp_identifier_set_iaid(int ifindex, const uint8_t *mac, size_t mac_len, bool legacy_unstable_byteorder, void *_id);
diff --git a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
index 9d245a90..122042ab 100644
--- a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
@@ -41,7 +41,6 @@ struct sd_dhcp_lease {
         /* each 0 if unset */
         be32_t address;
         be32_t server_address;
-        be32_t router;
         be32_t next_server;
 
         bool have_subnet_mask;
@@ -50,6 +49,9 @@ struct sd_dhcp_lease {
         bool have_broadcast;
         be32_t broadcast;
 
+        struct in_addr *router;
+        size_t router_size;
+
         struct in_addr *dns;
         size_t dns_size;
 
diff --git a/src/systemd/src/libsystemd-network/dhcp-network.c b/src/systemd/src/libsystemd-network/dhcp-network.c
index 80e9577c..810a2633 100644
--- a/src/systemd/src/libsystemd-network/dhcp-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp-network.c
@@ -3,9 +3,8 @@
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <net/ethernet.h>
 #include <net/if.h>
 #include <net/if_arp.h>
@@ -52,12 +51,16 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
                 BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.htype)),  /* A <- DHCP header type */
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arp_type, 1, 0),                   /* header type == arp_type ? */
                 BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
-                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)),   /* A <- MAC address length */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, dhcp_hlen, 1, 0),                  /* address length == dhcp_hlen ? */
-                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
                 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_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)),   /* A <- MAC address length */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, dhcp_hlen, 1, 0),                  /* address length == dhcp_hlen ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+
+                /* We only support MAC address length to be either 0 or 6 (ETH_ALEN). Optionally
+                 * compare chaddr for ETH_ALEN bytes. */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 12),                                  /* A (the MAC address length) == ETH_ALEN ? */
                 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 */
@@ -70,6 +73,7 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
                 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_W + BPF_ABS, offsetof(DHCPPacket, dhcp.magic)),  /* A <- DHCP magic cookie */
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0),          /* cookie == DHCP magic cookie ? */
                 BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
@@ -80,7 +84,7 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
                 .filter = filter
         };
         _cleanup_close_ int s = -1;
-        int r, on = 1;
+        int r;
 
         assert(ifindex > 0);
         assert(link);
@@ -89,9 +93,9 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
         if (s < 0)
                 return -errno;
 
-        r = setsockopt(s, SOL_PACKET, PACKET_AUXDATA, &on, sizeof(on));
+        r = setsockopt_int(s, SOL_PACKET, PACKET_AUXDATA, true);
         if (r < 0)
-                return -errno;
+                return r;
 
         r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
         if (r < 0)
@@ -151,19 +155,19 @@ int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port) {
         };
         _cleanup_close_ int s = -1;
         char ifname[IF_NAMESIZE] = "";
-        int r, on = 1, tos = IPTOS_CLASS_CS6;
+        int r;
 
         s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
         if (s < 0)
                 return -errno;
 
-        r = setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+        r = setsockopt_int(s, IPPROTO_IP, IP_TOS, IPTOS_CLASS_CS6);
         if (r < 0)
-                return -errno;
+                return r;
 
-        r = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+        r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
         if (r < 0)
-                return -errno;
+                return r;
 
         if (ifindex > 0) {
                 if (if_indextoname(ifindex, ifname) == 0)
@@ -175,18 +179,18 @@ int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port) {
         }
 
         if (address == INADDR_ANY) {
-                r = setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
+                r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true);
                 if (r < 0)
-                        return -errno;
+                        return r;
 
-                r = setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
+                r = setsockopt_int(s, SOL_SOCKET, SO_BROADCAST, true);
                 if (r < 0)
-                        return -errno;
+                        return r;
 
         } else {
-                r = setsockopt(s, IPPROTO_IP, IP_FREEBIND, &on, sizeof(on));
+                r = setsockopt_int(s, IPPROTO_IP, IP_FREEBIND, true);
                 if (r < 0)
-                        return -errno;
+                        return r;
         }
 
         r = bind(s, &src.sa, sizeof(src.in));
diff --git a/src/systemd/src/libsystemd-network/dhcp-option.c b/src/systemd/src/libsystemd-network/dhcp-option.c
index 4a4f7de0..c5fbe749 100644
--- a/src/systemd/src/libsystemd-network/dhcp-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp-option.c
@@ -3,9 +3,8 @@
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp-packet.c b/src/systemd/src/libsystemd-network/dhcp-packet.c
index 8f8acb0d..91e8a54e 100644
--- a/src/systemd/src/libsystemd-network/dhcp-packet.c
+++ b/src/systemd/src/libsystemd-network/dhcp-packet.c
@@ -3,9 +3,8 @@
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <net/ethernet.h>
 #include <net/if_arp.h>
 #include <string.h>
@@ -108,70 +107,62 @@ int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, ui
 
         /* IP */
 
-        if (packet->ip.version != IPVERSION) {
-                log_debug("ignoring packet: not IPv4");
-                return -EINVAL;
-        }
+        if (packet->ip.version != IPVERSION)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: not IPv4");
 
-        if (packet->ip.ihl < 5) {
-                log_debug("ignoring packet: IPv4 IHL (%u words) invalid",
-                          packet->ip.ihl);
-                return -EINVAL;
-        }
+        if (packet->ip.ihl < 5)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: IPv4 IHL (%u words) invalid",
+                                       packet->ip.ihl);
 
         hdrlen = packet->ip.ihl * 4;
-        if (hdrlen < 20) {
-                log_debug("ignoring packet: IPv4 IHL (%zu bytes) "
-                          "smaller than minimum (20 bytes)", hdrlen);
-                return -EINVAL;
-        }
-
-        if (len < hdrlen) {
-                log_debug("ignoring packet: packet (%zu bytes) "
-                          "smaller than expected (%zu) by IP header", len,
-                          hdrlen);
-                return -EINVAL;
-        }
+        if (hdrlen < 20)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: IPv4 IHL (%zu bytes) "
+                                       "smaller than minimum (20 bytes)",
+                                       hdrlen);
+
+        if (len < hdrlen)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: packet (%zu bytes) "
+                                       "smaller than expected (%zu) by IP header",
+                                       len, hdrlen);
 
         /* UDP */
 
-        if (packet->ip.protocol != IPPROTO_UDP) {
-                log_debug("ignoring packet: not UDP");
-                return -EINVAL;
-        }
+        if (packet->ip.protocol != IPPROTO_UDP)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: not UDP");
 
-        if (len < hdrlen + be16toh(packet->udp.len)) {
-                log_debug("ignoring packet: packet (%zu bytes) "
-                          "smaller than expected (%zu) by UDP header", len,
-                          hdrlen + be16toh(packet->udp.len));
-                return -EINVAL;
-        }
+        if (len < hdrlen + be16toh(packet->udp.len))
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: packet (%zu bytes) "
+                                       "smaller than expected (%zu) by UDP header",
+                                       len, hdrlen + be16toh(packet->udp.len));
 
-        if (be16toh(packet->udp.dest) != port) {
-                log_debug("ignoring packet: to port %u, which "
-                          "is not the DHCP client port (%u)",
-                          be16toh(packet->udp.dest), port);
-                return -EINVAL;
-        }
+        if (be16toh(packet->udp.dest) != port)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: to port %u, which "
+                                       "is not the DHCP client port (%u)",
+                                       be16toh(packet->udp.dest), port);
 
         /* checksums - computing these is relatively expensive, so only do it
            if all the other checks have passed
          */
 
-        if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen)) {
-                log_debug("ignoring packet: invalid IP checksum");
-                return -EINVAL;
-        }
+        if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen))
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "ignoring packet: invalid IP checksum");
 
         if (checksum && packet->udp.check) {
                 packet->ip.check = packet->udp.len;
                 packet->ip.ttl = 0;
 
                 if (dhcp_packet_checksum((uint8_t*)&packet->ip.ttl,
-                                  be16toh(packet->udp.len) + 12)) {
-                        log_debug("ignoring packet: invalid UDP checksum");
-                        return -EINVAL;
-                }
+                                  be16toh(packet->udp.len) + 12))
+                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "ignoring packet: invalid UDP checksum");
         }
 
         return 0;
diff --git a/src/systemd/src/libsystemd-network/dhcp6-internal.h b/src/systemd/src/libsystemd-network/dhcp6-internal.h
index 06e2e532..157fc0aa 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-internal.h
@@ -73,8 +73,6 @@ struct DHCP6IA {
                 struct ia_pd ia_pd;
                 struct ia_ta ia_ta;
         };
-        sd_event_source *timeout_t1;
-        sd_event_source *timeout_t2;
 
         LIST_HEAD(DHCP6Address, addresses);
 };
@@ -86,8 +84,8 @@ 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_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia);
+int dhcp6_option_append_pd(uint8_t *buf, size_t len, const 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);
diff --git a/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
index ff0b0f00..e004f48b 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
@@ -37,7 +37,6 @@ struct sd_dhcp6_lease {
         size_t ntp_fqdn_count;
 };
 
-int dhcp6_lease_clear_timers(DHCP6IA *ia);
 int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire);
 DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia);
 
@@ -50,6 +49,7 @@ int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease);
 int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit);
 
 int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid);
+int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid);
 
 int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen);
 int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval,
diff --git a/src/systemd/src/libsystemd-network/dhcp6-network.c b/src/systemd/src/libsystemd-network/dhcp6-network.c
index 98aa6261..d786756e 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-network.c
@@ -3,9 +3,8 @@
   Copyright © 2014 Intel Corporation. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <netinet/in.h>
 #include <netinet/ip6.h>
 #include <stdio.h>
@@ -27,7 +26,7 @@ int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) {
                 .in6.sin6_scope_id = index,
         };
         _cleanup_close_ int s = -1;
-        int r, off = 0, on = 1;
+        int r;
 
         assert(index > 0);
         assert(local_address);
@@ -38,17 +37,17 @@ int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) {
         if (s < 0)
                 return -errno;
 
-        r = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
+        r = setsockopt_int(s, IPPROTO_IPV6, IPV6_V6ONLY, true);
         if (r < 0)
-                return -errno;
+                return r;
 
-        r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, sizeof(off));
+        r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, false);
         if (r < 0)
-                return -errno;
+                return r;
 
-        r = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+        r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true);
         if (r < 0)
-                return -errno;
+                return r;
 
         r = bind(s, &src.sa, sizeof(src.in6));
         if (r < 0)
diff --git a/src/systemd/src/libsystemd-network/dhcp6-option.c b/src/systemd/src/libsystemd-network/dhcp6-option.c
index 22970443..3d5abe64 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-option.c
@@ -3,9 +3,8 @@
   Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <netinet/in.h>
 #include <string.h>
 
@@ -39,9 +38,9 @@ typedef struct DHCP6PDPrefixOption {
         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))
+#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) {
@@ -51,14 +50,14 @@ static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
         assert_return(*buf, -EINVAL);
         assert_return(buflen, -EINVAL);
 
-        if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option))
+        if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data))
                 return -ENOBUFS;
 
         option->code = htobe16(optcode);
         option->len = htobe16(optlen);
 
-        *buf += sizeof(DHCP6Option);
-        *buflen -= sizeof(DHCP6Option);
+        *buf += offsetof(DHCP6Option, data);
+        *buflen -= offsetof(DHCP6Option, data);
 
         return 0;
 }
@@ -81,14 +80,17 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
         return 0;
 }
 
-int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
+int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
         uint16_t len;
         uint8_t *ia_hdr;
         size_t iaid_offset, ia_buflen, ia_addrlen = 0;
         DHCP6Address *addr;
         int r;
 
-        assert_return(buf && *buf && buflen && ia, -EINVAL);
+        assert_return(buf, -EINVAL);
+        assert_return(*buf, -EINVAL);
+        assert_return(buflen, -EINVAL);
+        assert_return(ia, -EINVAL);
 
         switch (ia->type) {
         case SD_DHCP6_OPTION_IA_NA:
@@ -111,8 +113,8 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
         ia_hdr = *buf;
         ia_buflen = *buflen;
 
-        *buf += sizeof(DHCP6Option);
-        *buflen -= sizeof(DHCP6Option);
+        *buf += offsetof(DHCP6Option, data);
+        *buflen -= offsetof(DHCP6Option, data);
 
         memcpy(*buf, (char*) ia + iaid_offset, len);
 
@@ -130,7 +132,7 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
                 *buf += sizeof(addr->iaaddr);
                 *buflen -= sizeof(addr->iaaddr);
 
-                ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
+                ia_addrlen += offsetof(DHCP6Option, data) + sizeof(addr->iaaddr);
         }
 
         r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
@@ -167,7 +169,7 @@ int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
         return r;
 }
 
-int dhcp6_option_append_pd(uint8_t *buf, size_t len, DHCP6IA *pd) {
+int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd) {
         DHCP6Option *option = (DHCP6Option *)buf;
         size_t i = sizeof(*option) + sizeof(pd->ia_pd);
         DHCP6Address *prefix;
@@ -212,7 +214,7 @@ static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, si
         assert_return(optcode, -EINVAL);
         assert_return(optlen, -EINVAL);
 
-        if (*buflen < sizeof(DHCP6Option))
+        if (*buflen < offsetof(DHCP6Option, data))
                 return -ENOMSG;
 
         len = be16toh(option->len);
@@ -253,7 +255,7 @@ int dhcp6_option_parse_status(DHCP6Option *option, size_t len) {
         DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
 
         if (len < sizeof(DHCP6StatusOption) ||
-            be16toh(option->len) + sizeof(DHCP6Option) < sizeof(DHCP6StatusOption))
+            be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption))
                 return -ENOBUFS;
 
         return be16toh(statusopt->status);
@@ -266,7 +268,7 @@ static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia,
         uint32_t lt_valid, lt_pref;
         int r;
 
-        if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*addr_option))
+        if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option))
                 return -ENOBUFS;
 
         lt_valid = be32toh(addr_option->iaaddr.lifetime_valid);
@@ -279,8 +281,8 @@ static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia,
                 return 0;
         }
 
-        if (be16toh(option->len) + sizeof(DHCP6Option) > sizeof(*addr_option)) {
-                r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + sizeof(DHCP6Option) - sizeof(*addr_option));
+        if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) {
+                r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option));
                 if (r != 0)
                         return r < 0 ? r: 0;
         }
@@ -306,7 +308,7 @@ static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia,
         uint32_t lt_valid, lt_pref;
         int r;
 
-        if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*pdprefix_option))
+        if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option))
                 return -ENOBUFS;
 
         lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid);
@@ -319,8 +321,8 @@ static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia,
                 return 0;
         }
 
-        if (be16toh(option->len) + sizeof(DHCP6Option) > sizeof(*pdprefix_option)) {
-                r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + sizeof(DHCP6Option) - sizeof(*pdprefix_option));
+        if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) {
+                r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option));
                 if (r != 0)
                         return r < 0 ? r: 0;
         }
@@ -356,10 +358,8 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
         switch (iatype) {
         case SD_DHCP6_OPTION_IA_NA:
 
-                if (len < DHCP6_OPTION_IA_NA_LEN) {
-                        r = -ENOBUFS;
-                        goto error;
-                }
+                if (len < DHCP6_OPTION_IA_NA_LEN)
+                        return -ENOBUFS;
 
                 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
                 memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na));
@@ -370,18 +370,15 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
                 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;
+                        return -EINVAL;
                 }
 
                 break;
 
         case SD_DHCP6_OPTION_IA_PD:
 
-                if (len < sizeof(ia->ia_pd)) {
-                        r = -ENOBUFS;
-                        goto error;
-                }
+                if (len < sizeof(ia->ia_pd))
+                        return -ENOBUFS;
 
                 iaaddr_offset = sizeof(ia->ia_pd);
                 memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd));
@@ -392,17 +389,14 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
                 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
                         log_dhcp6_client(client, "IA PD T1 %ds > T2 %ds",
                                          lt_t1, lt_t2);
-                        r = -EINVAL;
-                        goto error;
+                        return -EINVAL;
                 }
 
                 break;
 
         case SD_DHCP6_OPTION_IA_TA:
-                if (len < DHCP6_OPTION_IA_TA_LEN) {
-                        r = -ENOBUFS;
-                        goto error;
-                }
+                if (len < DHCP6_OPTION_IA_TA_LEN)
+                        return -ENOBUFS;
 
                 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
                 memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta));
@@ -410,8 +404,7 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
                 break;
 
         default:
-                r = -ENOMSG;
-                goto error;
+                return -ENOMSG;
         }
 
         ia->type = iatype;
@@ -420,10 +413,8 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
         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;
-                }
+                if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len))
+                        return -ENOBUFS;
 
                 opt = be16toh(option->code);
                 optlen = be16toh(option->len);
@@ -433,13 +424,12 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
 
                         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;
+                                return -EINVAL;
                         }
 
                         r = dhcp6_option_parse_address(option, ia, &lt_valid);
                         if (r < 0)
-                                goto error;
+                                return r;
 
                         if (lt_valid < lt_min)
                                 lt_min = lt_valid;
@@ -450,13 +440,12 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
 
                         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;
+                                return -EINVAL;
                         }
 
                         r = dhcp6_option_parse_pdprefix(option, ia, &lt_valid);
                         if (r < 0)
-                                goto error;
+                                return r;
 
                         if (lt_valid < lt_min)
                                 lt_min = lt_valid;
@@ -465,17 +454,14 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
 
                 case SD_DHCP6_OPTION_STATUS_CODE:
 
-                        status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option));
-                        if (status < 0) {
-                                r = status;
-                                goto error;
-                        }
+                        status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data));
+                        if (status < 0)
+                                return status;
                         if (status > 0) {
                                 log_dhcp6_client(client, "IA status %d",
                                                  status);
 
-                                r = -EINVAL;
-                                goto error;
+                                return -EINVAL;
                         }
 
                         break;
@@ -519,8 +505,7 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
                 break;
         }
 
-error:
-        return r;
+        return 0;
 }
 
 int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
diff --git a/src/systemd/src/libsystemd-network/lldp-internal.h b/src/systemd/src/libsystemd-network/lldp-internal.h
index 328d51f8..88b54933 100644
--- a/src/systemd/src/libsystemd-network/lldp-internal.h
+++ b/src/systemd/src/libsystemd-network/lldp-internal.h
@@ -34,3 +34,6 @@ struct sd_lldp {
 
 #define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__)
 #define log_lldp(fmt, ...) log_lldp_errno(0, fmt, ##__VA_ARGS__)
+
+const char* lldp_event_to_string(sd_lldp_event e) _const_;
+sd_lldp_event lldp_event_from_string(const char *s) _pure_;
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.c b/src/systemd/src/libsystemd-network/lldp-neighbor.c
index cd9358a4..bbe7bf9d 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.c
+++ b/src/systemd/src/libsystemd-network/lldp-neighbor.c
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #include "alloc-util.h"
 #include "escape.h"
@@ -9,40 +9,24 @@
 #include "in-addr-util.h"
 #include "lldp-internal.h"
 #include "lldp-neighbor.h"
+#include "missing.h"
 #include "unaligned.h"
+#include "util.h"
 
-static void lldp_neighbor_id_hash_func(const void *p, struct siphash *state) {
-        const LLDPNeighborID *id = p;
-
+static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash *state) {
         siphash24_compress(id->chassis_id, id->chassis_id_size, state);
         siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state);
         siphash24_compress(id->port_id, id->port_id_size, state);
         siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state);
 }
 
-static int lldp_neighbor_id_compare_func(const void *a, const void *b) {
-        const LLDPNeighborID *x = a, *y = b;
-        int r;
-
-        r = memcmp(x->chassis_id, y->chassis_id, MIN(x->chassis_id_size, y->chassis_id_size));
-        if (r != 0)
-                return r;
-
-        r = CMP(x->chassis_id_size, y->chassis_id_size);
-        if (r != 0)
-                return r;
-
-        r = memcmp(x->port_id, y->port_id, MIN(x->port_id_size, y->port_id_size));
-        if (r != 0)
-                return r;
-
-        return CMP(x->port_id_size, y->port_id_size);
+int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y) {
+        return memcmp_nn(x->chassis_id, x->chassis_id_size, y->chassis_id, y->chassis_id_size)
+            ?: memcmp_nn(x->port_id, x->port_id_size, y->port_id, y->port_id_size);
 }
 
-const struct hash_ops lldp_neighbor_id_hash_ops = {
-        .hash = lldp_neighbor_id_hash_func,
-        .compare = lldp_neighbor_id_compare_func
-};
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(lldp_neighbor_hash_ops, LLDPNeighborID, lldp_neighbor_id_hash_func, lldp_neighbor_id_compare_func,
+                                      sd_lldp_neighbor, lldp_neighbor_unlink);
 
 int lldp_neighbor_prioq_compare_func(const void *a, const void *b) {
         const sd_lldp_neighbor *x = a, *y = b;
@@ -100,7 +84,12 @@ sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) {
         if (!n->lldp)
                 return NULL;
 
-        assert_se(hashmap_remove(n->lldp->neighbor_by_id, &n->id) == n);
+        /* Only remove the neighbor object from the hash table if it's in there, don't complain if it isn't. This is
+         * because we are used as destructor call for hashmap_clear() and thus sometimes are called to de-register
+         * ourselves from the hashtable and sometimes are called after we already are de-registered. */
+
+        (void) hashmap_remove_value(n->lldp->neighbor_by_id, &n->id, n);
+
         assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0);
 
         n->lldp = NULL;
@@ -704,7 +693,7 @@ _public_ int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) {
         return type == k;
 }
 
-_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], uint8_t *subtype) {
+_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype) {
         const uint8_t *d;
         size_t length;
         int r;
@@ -733,7 +722,7 @@ _public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], u
         return 0;
 }
 
-_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[3], uint8_t subtype) {
+_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype) {
         uint8_t k[3], st;
         int r;
 
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.h b/src/systemd/src/libsystemd-network/lldp-neighbor.h
index 494bc517..62dbff42 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.h
+++ b/src/systemd/src/libsystemd-network/lldp-neighbor.h
@@ -80,7 +80,8 @@ static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) {
         return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2;
 }
 
-extern const struct hash_ops lldp_neighbor_id_hash_ops;
+extern const struct hash_ops lldp_neighbor_hash_ops;
+int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y);
 int lldp_neighbor_prioq_compare_func(const void *a, const void *b);
 
 sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n);
diff --git a/src/systemd/src/libsystemd-network/lldp-network.c b/src/systemd/src/libsystemd-network/lldp-network.c
index db779aaa..5ba9f081 100644
--- a/src/systemd/src/libsystemd-network/lldp-network.c
+++ b/src/systemd/src/libsystemd-network/lldp-network.c
@@ -1,12 +1,13 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #include <linux/filter.h>
 #include <netinet/if_ether.h>
 
 #include "fd-util.h"
 #include "lldp-network.h"
+#include "missing.h"
 #include "socket-util.h"
 
 int lldp_network_bind_raw_socket(int ifindex) {
diff --git a/src/systemd/src/libsystemd-network/network-internal.c b/src/systemd/src/libsystemd-network/network-internal.c
index 57a59030..f85db47b 100644
--- a/src/systemd/src/libsystemd-network/network-internal.c
+++ b/src/systemd/src/libsystemd-network/network-internal.c
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #include <arpa/inet.h>
 #include <linux/if.h>
@@ -12,6 +12,7 @@
 #include "alloc-util.h"
 #include "condition.h"
 #include "conf-parser.h"
+#include "device-util.h"
 #include "dhcp-lease-internal.h"
 #include "ether-addr-util.h"
 #include "hexdecoct.h"
@@ -43,31 +44,35 @@ const char *net_get_name(sd_device *device) {
 
 int net_get_unique_predictable_data(sd_device *device, uint64_t *result) {
         size_t l, sz = 0;
-        const char *name = NULL;
+        const char *name;
         int r;
         uint8_t *v;
 
         assert(device);
 
+        /* net_get_name() will return one of the device names based on stable information about the
+         * device. If this is not available, we fall back to using the device name. */
         name = net_get_name(device);
         if (!name)
-                return -ENOENT;
+                (void) sd_device_get_sysname(device, &name);
+        if (!name)
+                return log_device_debug_errno(device, SYNTHETIC_ERRNO(ENODATA),
+                                              "No stable identifying information found");
 
+        log_device_debug(device, "Using \"%s\" as stable identifying information", name);
         l = strlen(name);
         sz = sizeof(sd_id128_t) + l;
-        v = alloca(sz);
+        v = newa(uint8_t, sz);
 
-        /* fetch some persistent data unique to this machine */
+        /* Fetch some persistent data unique to this machine */
         r = sd_id128_get_machine((sd_id128_t*) v);
         if (r < 0)
                  return r;
         memcpy(v + sizeof(sd_id128_t), name, l);
 
-        /* Let's hash the machine ID plus the device name. We
-        * use a fixed, but originally randomly created hash
-        * key here. */
+        /* Let's hash the machine ID plus the device name. We use
+         * a fixed, but originally randomly created hash key here. */
         *result = htole64(siphash24(v, sz, HASH_KEY.bytes));
-
         return 0;
 }
 
@@ -105,7 +110,6 @@ bool net_match_config(Set *match_mac,
                       Condition *match_arch,
                       const struct ether_addr *dev_mac,
                       const char *dev_path,
-                      const char *dev_parent_driver,
                       const char *dev_driver,
                       const char *dev_type,
                       const char *dev_name) {
@@ -258,11 +262,10 @@ int config_parse_ifalias(const char *unit,
                 return 0;
         }
 
-        free(*s);
-        if (*n)
-                *s = TAKE_PTR(n);
+        if (isempty(n))
+                *s = mfree(*s);
         else
-                *s = NULL;
+                free_and_replace(*s, n);
 
         return 0;
 }
@@ -297,7 +300,7 @@ int config_parse_hwaddr(const char *unit,
                 return 0;
         }
 
-        *hwaddr = TAKE_PTR(n);
+        free_and_replace(*hwaddr, n);
 
         return 0;
 }
@@ -375,36 +378,6 @@ int config_parse_hwaddrs(const char *unit,
         return 0;
 }
 
-int config_parse_iaid(const char *unit,
-                      const char *filename,
-                      unsigned line,
-                      const char *section,
-                      unsigned section_line,
-                      const char *lvalue,
-                      int ltype,
-                      const char *rvalue,
-                      void *data,
-                      void *userdata) {
-        uint32_t iaid;
-        int r;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        r = safe_atou32(rvalue, &iaid);
-        if (r < 0) {
-                log_syntax(unit, LOG_ERR, filename, line, r,
-                           "Unable to read IAID, ignoring assignment: %s", rvalue);
-                return 0;
-        }
-
-        *((uint32_t *)data) = iaid;
-
-        return 0;
-}
-
 int config_parse_bridge_port_priority(
                 const char *unit,
                 const char *filename,
@@ -444,16 +417,33 @@ int config_parse_bridge_port_priority(
 }
 #endif /* NM_IGNORED */
 
-void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size) {
-        unsigned i;
+size_t serialize_in_addrs(FILE *f,
+                          const struct in_addr *addresses,
+                          size_t size,
+                          bool with_leading_space,
+                          bool (*predicate)(const struct in_addr *addr)) {
+        size_t count;
+        size_t i;
 
         assert(f);
         assert(addresses);
-        assert(size);
 
-        for (i = 0; i < size; i++)
-                fprintf(f, "%s%s", inet_ntoa(addresses[i]),
-                        (i < (size - 1)) ? " ": "");
+        count = 0;
+
+        for (i = 0; i < size; i++) {
+                char sbuf[INET_ADDRSTRLEN];
+
+                if (predicate && !predicate(&addresses[i]))
+                        continue;
+                if (with_leading_space)
+                        fputc(' ', f);
+                else
+                        with_leading_space = true;
+                fputs(inet_ntop(AF_INET, &addresses[i], sbuf, sizeof(sbuf)), f);
+                count++;
+        }
+
+        return count;
 }
 
 int deserialize_in_addrs(struct in_addr **ret, const char *string) {
@@ -487,7 +477,7 @@ int deserialize_in_addrs(struct in_addr **ret, const char *string) {
                 size++;
         }
 
-        *ret = TAKE_PTR(addresses);
+        *ret = size > 0 ? TAKE_PTR(addresses) : NULL;
 
         return size;
 }
@@ -556,6 +546,7 @@ void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, siz
         fprintf(f, "%s=", key);
 
         for (i = 0; i < size; i++) {
+                char sbuf[INET_ADDRSTRLEN];
                 struct in_addr dest, gw;
                 uint8_t length;
 
@@ -563,8 +554,8 @@ void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, siz
                 assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0);
                 assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0);
 
-                fprintf(f, "%s/%" PRIu8, inet_ntoa(dest), length);
-                fprintf(f, ",%s%s", inet_ntoa(gw), (i < (size - 1)) ? " ": "");
+                fprintf(f, "%s/%" PRIu8, inet_ntop(AF_INET, &dest, sbuf, sizeof(sbuf)), length);
+                fprintf(f, ",%s%s", inet_ntop(AF_INET, &gw, sbuf, sizeof(sbuf)), (i < (size - 1)) ? " ": "");
         }
 
         fputs("\n", f);
diff --git a/src/systemd/src/libsystemd-network/network-internal.h b/src/systemd/src/libsystemd-network/network-internal.h
index 06d61184..9119d9a4 100644
--- a/src/systemd/src/libsystemd-network/network-internal.h
+++ b/src/systemd/src/libsystemd-network/network-internal.h
@@ -8,7 +8,9 @@
 
 #include "condition.h"
 #include "conf-parser.h"
+#include "def.h"
 #include "set.h"
+#include "strv.h"
 
 #define LINK_BRIDGE_PORT_PRIORITY_INVALID 128
 #define LINK_BRIDGE_PORT_PRIORITY_MAX 63
@@ -25,7 +27,6 @@ bool net_match_config(Set *match_mac,
                       Condition *match_arch,
                       const struct ether_addr *dev_mac,
                       const char *dev_path,
-                      const char *dev_parent_driver,
                       const char *dev_driver,
                       const char *dev_type,
                       const char *dev_name);
@@ -36,14 +37,17 @@ CONFIG_PARSER_PROTOTYPE(config_parse_hwaddr);
 CONFIG_PARSER_PROTOTYPE(config_parse_hwaddrs);
 CONFIG_PARSER_PROTOTYPE(config_parse_ifnames);
 CONFIG_PARSER_PROTOTYPE(config_parse_ifalias);
-CONFIG_PARSER_PROTOTYPE(config_parse_iaid);
 CONFIG_PARSER_PROTOTYPE(config_parse_bridge_port_priority);
 
 int net_get_unique_predictable_data(sd_device *device, uint64_t *result);
 const char *net_get_name(sd_device *device);
 #endif /* NM_IGNORED */
 
-void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size);
+size_t serialize_in_addrs(FILE *f,
+                          const struct in_addr *addresses,
+                          size_t size,
+                          bool with_leading_space,
+                          bool (*predicate)(const struct in_addr *addr));
 int deserialize_in_addrs(struct in_addr **addresses, const char *string);
 void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses,
                          size_t size);
@@ -57,3 +61,5 @@ int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t
 
 /* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */
 int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size);
+
+#define NETWORK_DIRS ((const char* const*) CONF_PATHS_STRV("systemd/network"))
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index 0c385a84..27f79638 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -3,9 +3,8 @@
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <net/ethernet.h>
 #include <net/if_arp.h>
 #include <stdio.h>
@@ -23,11 +22,13 @@
 #include "dhcp-lease-internal.h"
 #include "dhcp-protocol.h"
 #include "dns-domain.h"
+#include "event-util.h"
 #include "hostname-util.h"
+#include "io-util.h"
 #include "random-util.h"
 #include "string-util.h"
-#include "util.h"
 #include "strv.h"
+#include "util.h"
 
 #define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN)  /* Arbitrary limit */
 #define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN)
@@ -88,7 +89,7 @@ struct sd_dhcp_client {
         uint32_t mtu;
         uint32_t xid;
         usec_t start_time;
-        unsigned int attempt;
+        unsigned attempt;
         usec_t request_sent;
         sd_event_source *timeout_t1;
         sd_event_source *timeout_t2;
@@ -118,19 +119,19 @@ static const uint8_t default_req_opts[] = {
 */
 /* NOTE: using PRL options that Windows 10 RFC7844 implementation uses */
 static const uint8_t default_req_opts_anonymize[] = {
-       SD_DHCP_OPTION_SUBNET_MASK,                     /* 1 */
-       SD_DHCP_OPTION_ROUTER,                          /* 3 */
-       SD_DHCP_OPTION_DOMAIN_NAME_SERVER,              /* 6 */
-       SD_DHCP_OPTION_DOMAIN_NAME,                     /* 15 */
-       SD_DHCP_OPTION_ROUTER_DISCOVER,                 /* 31 */
-       SD_DHCP_OPTION_STATIC_ROUTE,                    /* 33 */
-       SD_DHCP_OPTION_VENDOR_SPECIFIC,                 /* 43 */
-       SD_DHCP_OPTION_NETBIOS_NAMESERVER,              /* 44 */
-       SD_DHCP_OPTION_NETBIOS_NODETYPE,                /* 46 */
-       SD_DHCP_OPTION_NETBIOS_SCOPE,                   /* 47 */
-       SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,          /* 121 */
-       SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE,  /* 249 */
-       SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,     /* 252 */
+        SD_DHCP_OPTION_SUBNET_MASK,                     /* 1 */
+        SD_DHCP_OPTION_ROUTER,                          /* 3 */
+        SD_DHCP_OPTION_DOMAIN_NAME_SERVER,              /* 6 */
+        SD_DHCP_OPTION_DOMAIN_NAME,                     /* 15 */
+        SD_DHCP_OPTION_ROUTER_DISCOVER,                 /* 31 */
+        SD_DHCP_OPTION_STATIC_ROUTE,                    /* 33 */
+        SD_DHCP_OPTION_VENDOR_SPECIFIC,                 /* 43 */
+        SD_DHCP_OPTION_NETBIOS_NAMESERVER,              /* 44 */
+        SD_DHCP_OPTION_NETBIOS_NODETYPE,                /* 46 */
+        SD_DHCP_OPTION_NETBIOS_SCOPE,                   /* 47 */
+        SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,          /* 121 */
+        SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE,  /* 249 */
+        SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,     /* 252 */
 };
 
 static int client_receive_message_raw(
@@ -342,8 +343,9 @@ int sd_dhcp_client_set_client_id(
  */
 static int dhcp_client_set_iaid_duid_internal(
                 sd_dhcp_client *client,
+                bool iaid_append,
+                bool iaid_set,
                 uint32_t iaid,
-                bool append_iaid,
                 uint16_t duid_type,
                 const void *duid,
                 size_t duid_len,
@@ -354,9 +356,9 @@ static int dhcp_client_set_iaid_duid_internal(
         size_t len;
 
         assert_return(client, -EINVAL);
-        assert_return(duid_len == 0 || duid != NULL, -EINVAL);
+        assert_return(duid_len == 0 || duid, -EINVAL);
 
-        if (duid != NULL) {
+        if (duid) {
                 r = dhcp_validate_duid_len(duid_type, duid_len, true);
                 if (r < 0)
                         return r;
@@ -365,26 +367,27 @@ static int dhcp_client_set_iaid_duid_internal(
         zero(client->client_id);
         client->client_id.type = 255;
 
-        if (append_iaid) {
-                /* If IAID is not configured, generate it. */
-                if (iaid == 0) {
+        if (iaid_append) {
+                if (iaid_set)
+                        client->client_id.ns.iaid = htobe32(iaid);
+                else {
                         r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr,
                                                      client->mac_addr_len,
+                                                     true,
                                                      &client->client_id.ns.iaid);
                         if (r < 0)
                                 return r;
-                } else
-                        client->client_id.ns.iaid = htobe32(iaid);
+                }
         }
 
-        if (duid != NULL) {
+        if (duid) {
                 client->client_id.ns.duid.type = htobe16(duid_type);
                 memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len);
                 len = sizeof(client->client_id.ns.duid.type) + duid_len;
         } else
                 switch (duid_type) {
                 case DUID_TYPE_LLT:
-                        if (!client->mac_addr || client->mac_addr_len == 0)
+                        if (client->mac_addr_len == 0)
                                 return -EOPNOTSUPP;
 
                         r = dhcp_identifier_set_duid_llt(&client->client_id.ns.duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
@@ -397,7 +400,7 @@ static int dhcp_client_set_iaid_duid_internal(
                                 return r;
                         break;
                 case DUID_TYPE_LL:
-                        if (!client->mac_addr || client->mac_addr_len == 0)
+                        if (client->mac_addr_len == 0)
                                 return -EOPNOTSUPP;
 
                         r = dhcp_identifier_set_duid_ll(&client->client_id.ns.duid, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
@@ -414,10 +417,10 @@ static int dhcp_client_set_iaid_duid_internal(
                 }
 
         client->client_id_len = sizeof(client->client_id.type) + len +
-                                (append_iaid ? sizeof(client->client_id.ns.iaid) : 0);
+                                (iaid_append ? sizeof(client->client_id.ns.iaid) : 0);
 
         if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
-                log_dhcp_client(client, "Configured %sDUID, restarting.", append_iaid ? "IAID+" : "");
+                log_dhcp_client(client, "Configured %sDUID, restarting.", iaid_append ? "IAID+" : "");
                 client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
                 sd_dhcp_client_start(client);
         }
@@ -427,18 +430,20 @@ static int dhcp_client_set_iaid_duid_internal(
 
 int sd_dhcp_client_set_iaid_duid(
                 sd_dhcp_client *client,
+                bool iaid_set,
                 uint32_t iaid,
                 uint16_t duid_type,
                 const void *duid,
                 size_t duid_len) {
-        return dhcp_client_set_iaid_duid_internal(client, iaid, true, duid_type, duid, duid_len, 0);
+        return dhcp_client_set_iaid_duid_internal(client, true, iaid_set, iaid, duid_type, duid, duid_len, 0);
 }
 
 int sd_dhcp_client_set_iaid_duid_llt(
                 sd_dhcp_client *client,
+                bool iaid_set,
                 uint32_t iaid,
                 usec_t llt_time) {
-        return dhcp_client_set_iaid_duid_internal(client, iaid, true, DUID_TYPE_LLT, NULL, 0, llt_time);
+        return dhcp_client_set_iaid_duid_internal(client, true, iaid_set, iaid, DUID_TYPE_LLT, NULL, 0, llt_time);
 }
 
 int sd_dhcp_client_set_duid(
@@ -446,13 +451,13 @@ int sd_dhcp_client_set_duid(
                 uint16_t duid_type,
                 const void *duid,
                 size_t duid_len) {
-        return dhcp_client_set_iaid_duid_internal(client, 0, false, duid_type, duid, duid_len, 0);
+        return dhcp_client_set_iaid_duid_internal(client, false, false, 0, duid_type, duid, duid_len, 0);
 }
 
 int sd_dhcp_client_set_duid_llt(
                 sd_dhcp_client *client,
                 usec_t llt_time) {
-        return dhcp_client_set_iaid_duid_internal(client, 0, false, DUID_TYPE_LLT, NULL, 0, llt_time);
+        return dhcp_client_set_iaid_duid_internal(client, false, false, 0, DUID_TYPE_LLT, NULL, 0, llt_time);
 }
 #endif /* NM_IGNORED */
 
@@ -545,11 +550,10 @@ static int client_initialize(sd_dhcp_client *client) {
 
         client->fd = asynchronous_close(client->fd);
 
-        client->timeout_resend = sd_event_source_unref(client->timeout_resend);
-
-        client->timeout_t1 = sd_event_source_unref(client->timeout_t1);
-        client->timeout_t2 = sd_event_source_unref(client->timeout_t2);
-        client->timeout_expire = sd_event_source_unref(client->timeout_expire);
+        (void) event_source_disable(client->timeout_resend);
+        (void) event_source_disable(client->timeout_t1);
+        (void) event_source_disable(client->timeout_t2);
+        (void) event_source_disable(client->timeout_expire);
 
         client->attempt = 1;
 
@@ -650,7 +654,8 @@ static int client_message_init(
 
                 client->client_id.type = 255;
 
-                r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, &client->client_id.ns.iaid);
+                r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len,
+                                             true, &client->client_id.ns.iaid);
                 if (r < 0)
                         return r;
 
@@ -698,7 +703,7 @@ static int client_message_init(
            let the server know how large the server may make its DHCP messages.
 
            Note (from ConnMan): Some DHCP servers will send bigger DHCP packets
-           than the defined default size unless the Maximum Messge Size option
+           than the defined default size unless the Maximum Message Size option
            is explicitly set
 
            RFC3442 "Requirements to Avoid Sizing Constraints":
@@ -1063,22 +1068,11 @@ static int client_timeout_resend(
 
         next_timeout += (random_u32() & 0x1fffff);
 
-        client->timeout_resend = sd_event_source_unref(client->timeout_resend);
-
-        r = sd_event_add_time(client->event,
-                              &client->timeout_resend,
-                              clock_boottime_or_monotonic(),
-                              next_timeout, 10 * USEC_PER_MSEC,
-                              client_timeout_resend, client);
-        if (r < 0)
-                goto error;
-
-        r = sd_event_source_set_priority(client->timeout_resend,
-                                         client->event_priority);
-        if (r < 0)
-                goto error;
-
-        r = sd_event_source_set_description(client->timeout_resend, "dhcp4-resend-timer");
+        r = event_reset_time(client->event, &client->timeout_resend,
+                             clock_boottime_or_monotonic(),
+                             next_timeout, 10 * USEC_PER_MSEC,
+                             client_timeout_resend, client,
+                             client->event_priority, "dhcp4-resend-timer", true);
         if (r < 0)
                 goto error;
 
@@ -1175,31 +1169,16 @@ static int client_initialize_time_events(sd_dhcp_client *client) {
         assert(client);
         assert(client->event);
 
-        client->timeout_resend = sd_event_source_unref(client->timeout_resend);
-
         if (client->start_delay) {
                 assert_se(sd_event_now(client->event, clock_boottime_or_monotonic(), &usec) >= 0);
                 usec += client->start_delay;
         }
 
-        r = sd_event_add_time(client->event,
-                              &client->timeout_resend,
-                              clock_boottime_or_monotonic(),
-                              usec, 0,
-                              client_timeout_resend, client);
-        if (r < 0)
-                goto error;
-
-        r = sd_event_source_set_priority(client->timeout_resend,
-                                         client->event_priority);
-        if (r < 0)
-                goto error;
-
-        r = sd_event_source_set_description(client->timeout_resend, "dhcp4-resend-timer");
-        if (r < 0)
-                goto error;
-
-error:
+        r = event_reset_time(client->event, &client->timeout_resend,
+                             clock_boottime_or_monotonic(),
+                             usec, 0,
+                             client_timeout_resend, client,
+                             client->event_priority, "dhcp4-resend-timer", true);
         if (r < 0)
                 client_stop(client, r);
 
@@ -1457,13 +1436,14 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
         assert(client->lease);
         assert(client->lease->lifetime);
 
-        client->timeout_t1 = sd_event_source_unref(client->timeout_t1);
-        client->timeout_t2 = sd_event_source_unref(client->timeout_t2);
-        client->timeout_expire = sd_event_source_unref(client->timeout_expire);
-
         /* don't set timers for infinite leases */
-        if (client->lease->lifetime == 0xffffffff)
+        if (client->lease->lifetime == 0xffffffff) {
+                (void) event_source_disable(client->timeout_t1);
+                (void) event_source_disable(client->timeout_t2);
+                (void) event_source_disable(client->timeout_expire);
+
                 return 0;
+        }
 
         r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
         if (r < 0)
@@ -1515,19 +1495,11 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
         }
 
         /* arm lifetime timeout */
-        r = sd_event_add_time(client->event, &client->timeout_expire,
-                              clock_boottime_or_monotonic(),
-                              lifetime_timeout, 10 * USEC_PER_MSEC,
-                              client_timeout_expire, client);
-        if (r < 0)
-                return r;
-
-        r = sd_event_source_set_priority(client->timeout_expire,
-                                         client->event_priority);
-        if (r < 0)
-                return r;
-
-        r = sd_event_source_set_description(client->timeout_expire, "dhcp4-lifetime");
+        r = event_reset_time(client->event, &client->timeout_expire,
+                             clock_boottime_or_monotonic(),
+                             lifetime_timeout, 10 * USEC_PER_MSEC,
+                             client_timeout_expire, client,
+                             client->event_priority, "dhcp4-lifetime", true);
         if (r < 0)
                 return r;
 
@@ -1539,21 +1511,11 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
                 return 0;
 
         /* arm T2 timeout */
-        r = sd_event_add_time(client->event,
-                              &client->timeout_t2,
-                              clock_boottime_or_monotonic(),
-                              t2_timeout,
-                              10 * USEC_PER_MSEC,
-                              client_timeout_t2, client);
-        if (r < 0)
-                return r;
-
-        r = sd_event_source_set_priority(client->timeout_t2,
-                                         client->event_priority);
-        if (r < 0)
-                return r;
-
-        r = sd_event_source_set_description(client->timeout_t2, "dhcp4-t2-timeout");
+        r = event_reset_time(client->event, &client->timeout_t2,
+                             clock_boottime_or_monotonic(),
+                             t2_timeout, 10 * USEC_PER_MSEC,
+                             client_timeout_t2, client,
+                             client->event_priority, "dhcp4-t2-timeout", true);
         if (r < 0)
                 return r;
 
@@ -1565,20 +1527,11 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
                 return 0;
 
         /* arm T1 timeout */
-        r = sd_event_add_time(client->event,
-                              &client->timeout_t1,
-                              clock_boottime_or_monotonic(),
-                              t1_timeout, 10 * USEC_PER_MSEC,
-                              client_timeout_t1, client);
-        if (r < 0)
-                return r;
-
-        r = sd_event_source_set_priority(client->timeout_t1,
-                                         client->event_priority);
-        if (r < 0)
-                return r;
-
-        r = sd_event_source_set_description(client->timeout_t1, "dhcp4-t1-timer");
+        r = event_reset_time(client->event, &client->timeout_t1,
+                             clock_boottime_or_monotonic(),
+                             t1_timeout, 10 * USEC_PER_MSEC,
+                             client_timeout_t1, client,
+                             client->event_priority, "dhcp4-t1-timer", true);
         if (r < 0)
                 return r;
 
@@ -1603,26 +1556,14 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
                 r = client_handle_offer(client, message, len);
                 if (r >= 0) {
 
-                        client->timeout_resend =
-                                sd_event_source_unref(client->timeout_resend);
-
                         client->state = DHCP_STATE_REQUESTING;
                         client->attempt = 1;
 
-                        r = sd_event_add_time(client->event,
-                                              &client->timeout_resend,
-                                              clock_boottime_or_monotonic(),
-                                              0, 0,
-                                              client_timeout_resend, client);
-                        if (r < 0)
-                                goto error;
-
-                        r = sd_event_source_set_priority(client->timeout_resend,
-                                                         client->event_priority);
-                        if (r < 0)
-                                goto error;
-
-                        r = sd_event_source_set_description(client->timeout_resend, "dhcp4-resend-timer");
+                        r = event_reset_time(client->event, &client->timeout_resend,
+                                             clock_boottime_or_monotonic(),
+                                             0, 0,
+                                             client_timeout_resend, client,
+                                             client->event_priority, "dhcp4-resend-timer", true);
                         if (r < 0)
                                 goto error;
                 } else if (r == -ENOMSG)
@@ -1639,8 +1580,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
                 r = client_handle_ack(client, message, len);
                 if (r >= 0) {
                         client->start_delay = 0;
-                        client->timeout_resend =
-                                sd_event_source_unref(client->timeout_resend);
+                        (void) event_source_disable(client->timeout_resend);
                         client->receive_message =
                                 sd_event_source_unref(client->receive_message);
                         client->fd = asynchronous_close(client->fd);
@@ -1680,9 +1620,6 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
 
                 } else if (r == -EADDRNOTAVAIL) {
                         /* got a NAK, let's restart the client */
-                        client->timeout_resend =
-                                sd_event_source_unref(client->timeout_resend);
-
                         client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
 
                         r = client_initialize(client);
@@ -1743,8 +1680,7 @@ static int client_receive_message_udp(
 
         sd_dhcp_client *client = userdata;
         _cleanup_free_ DHCPMessage *message = NULL;
-        const struct ether_addr zero_mac = {};
-        const struct ether_addr *expected_chaddr = NULL;
+        const uint8_t *expected_chaddr = NULL;
         uint8_t expected_hlen = 0;
         ssize_t len, buflen;
 
@@ -1752,6 +1688,12 @@ static int client_receive_message_udp(
         assert(client);
 
         buflen = next_datagram_size_fd(fd);
+        if (buflen == -ENETDOWN) {
+                /* the link is down. Don't return an error or the I/O event
+                   source will be disconnected and we won't be able to receive
+                   packets again when the link comes back. */
+                return 0;
+        }
         if (buflen < 0)
                 return buflen;
 
@@ -1761,7 +1703,8 @@ static int client_receive_message_udp(
 
         len = recv(fd, message, buflen, 0);
         if (len < 0) {
-                if (IN_SET(errno, EAGAIN, EINTR))
+                /* see comment above for why we shouldn't error out on ENETDOWN. */
+                if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
                         return 0;
 
                 return log_dhcp_client_errno(client, errno,
@@ -1789,11 +1732,7 @@ static int client_receive_message_udp(
 
         if (client->arp_type == ARPHRD_ETHER) {
                 expected_hlen = ETH_ALEN;
-                expected_chaddr = (const struct ether_addr *) &client->mac_addr;
-        } else {
-               /* Non-Ethernet links expect zero chaddr */
-               expected_hlen = 0;
-               expected_chaddr = &zero_mac;
+                expected_chaddr = &client->mac_addr[0];
         }
 
         if (message->hlen != expected_hlen) {
@@ -1801,7 +1740,7 @@ static int client_receive_message_udp(
                 return 0;
         }
 
-        if (memcmp(&message->chaddr[0], expected_chaddr, ETH_ALEN)) {
+        if (expected_hlen > 0 && memcmp(&message->chaddr[0], expected_chaddr, expected_hlen)) {
                 log_dhcp_client(client, "Received chaddr does not match expected: ignoring");
                 return 0;
         }
@@ -1843,6 +1782,8 @@ static int client_receive_message_raw(
         assert(client);
 
         buflen = next_datagram_size_fd(fd);
+        if (buflen == -ENETDOWN)
+                return 0;
         if (buflen < 0)
                 return buflen;
 
@@ -1850,12 +1791,11 @@ static int client_receive_message_raw(
         if (!packet)
                 return -ENOMEM;
 
-        iov.iov_base = packet;
-        iov.iov_len = buflen;
+        iov = IOVEC_MAKE(packet, buflen);
 
         len = recvmsg(fd, &msg, 0);
         if (len < 0) {
-                if (IN_SET(errno, EAGAIN, EINTR))
+                if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
                         return 0;
 
                 return log_dhcp_client_errno(client, errno,
@@ -1863,7 +1803,7 @@ static int client_receive_message_raw(
         } else if ((size_t)len < sizeof(DHCPPacket))
                 return 0;
 
-        CMSG_FOREACH(cmsg, &msg) {
+        CMSG_FOREACH(cmsg, &msg)
                 if (cmsg->cmsg_level == SOL_PACKET &&
                     cmsg->cmsg_type == PACKET_AUXDATA &&
                     cmsg->cmsg_len == CMSG_LEN(sizeof(struct tpacket_auxdata))) {
@@ -1872,7 +1812,6 @@ static int client_receive_message_raw(
                         checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
                         break;
                 }
-        }
 
         r = dhcp_packet_verify_headers(packet, len, checksum, client->port);
         if (r < 0)
@@ -1953,33 +1892,17 @@ sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) {
         return client->event;
 }
 
-sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client) {
-
-        if (!client)
-                return NULL;
-
-        assert(client->n_ref >= 1);
-        client->n_ref++;
-
-        return client;
-}
-
-sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client) {
-
-        if (!client)
-                return NULL;
-
-        assert(client->n_ref >= 1);
-        client->n_ref--;
-
-        if (client->n_ref > 0)
-                return NULL;
+static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) {
+        assert(client);
 
         log_dhcp_client(client, "FREE");
 
-        client_initialize(client);
+        client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+        client->timeout_t1 = sd_event_source_unref(client->timeout_t1);
+        client->timeout_t2 = sd_event_source_unref(client->timeout_t2);
+        client->timeout_expire = sd_event_source_unref(client->timeout_expire);
 
-        client->receive_message = sd_event_source_unref(client->receive_message);
+        client_initialize(client);
 
         sd_dhcp_client_detach_event(client);
 
@@ -1992,24 +1915,27 @@ sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client) {
         return mfree(client);
 }
 
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client, sd_dhcp_client, dhcp_client_free);
+
 int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
         _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL;
 
         assert_return(ret, -EINVAL);
 
-        client = new0(sd_dhcp_client, 1);
+        client = new(sd_dhcp_client, 1);
         if (!client)
                 return -ENOMEM;
 
-        client->n_ref = 1;
-        client->state = DHCP_STATE_INIT;
-        client->ifindex = -1;
-        client->fd = -1;
-        client->attempt = 1;
-        client->mtu = DHCP_DEFAULT_MIN_SIZE;
-        client->port = DHCP_PORT_CLIENT;
-
-        client->anonymize = !!anonymize;
+        *client = (sd_dhcp_client) {
+                .n_ref = 1,
+                .state = DHCP_STATE_INIT,
+                .ifindex = -1,
+                .fd = -1,
+                .attempt = 1,
+                .mtu = DHCP_DEFAULT_MIN_SIZE,
+                .port = DHCP_PORT_CLIENT,
+                .anonymize = !!anonymize,
+        };
         /* NOTE: this could be moved to a function. */
         if (anonymize) {
                 client->req_opts_size = ELEMENTSOF(default_req_opts_anonymize);
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index cac07d3e..39d2a6d0 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -3,10 +3,9 @@
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #include <arpa/inet.h>
-#include <errno.h>
 #include <stdio.h>
 #include <stdio_ext.h>
 #include <stdlib.h>
@@ -18,6 +17,7 @@
 #include "dhcp-lease-internal.h"
 #include "dhcp-protocol.h"
 #include "dns-domain.h"
+#include "env-file.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "hexdecoct.h"
@@ -28,6 +28,7 @@
 #include "stdio-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "tmpfile-util.h"
 #include "unaligned.h"
 
 int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr) {
@@ -151,15 +152,15 @@ int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path) {
         return 0;
 }
 
-int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, struct in_addr *addr) {
+int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) {
         assert_return(lease, -EINVAL);
         assert_return(addr, -EINVAL);
 
-        if (lease->router == 0)
+        if (lease->router_size <= 0)
                 return -ENODATA;
 
-        addr->s_addr = lease->router;
-        return 0;
+        *addr = lease->router;
+        return (int) lease->router_size;
 }
 
 int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) {
@@ -248,27 +249,8 @@ int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, s
         return 0;
 }
 
-sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease) {
-
-        if (!lease)
-                return NULL;
-
-        assert(lease->n_ref >= 1);
-        lease->n_ref++;
-
-        return lease;
-}
-
-sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease) {
-
-        if (!lease)
-                return NULL;
-
-        assert(lease->n_ref >= 1);
-        lease->n_ref--;
-
-        if (lease->n_ref > 0)
-                return NULL;
+static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) {
+        assert(lease);
 
         while (lease->private_options) {
                 struct sd_dhcp_raw_option *option = lease->private_options;
@@ -280,6 +262,7 @@ sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease) {
         }
 
         free(lease->root_path);
+        free(lease->router);
         free(lease->timezone);
         free(lease->hostname);
         free(lease->domainname);
@@ -292,6 +275,8 @@ sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease) {
         return mfree(lease);
 }
 
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_lease, sd_dhcp_lease, dhcp_lease_free);
+
 static int lease_parse_u32(const uint8_t *option, size_t len, uint32_t *ret, uint32_t min) {
         assert(option);
         assert(ret);
@@ -351,8 +336,7 @@ static int lease_parse_string(const uint8_t *option, size_t len, char **ret) {
                 if (!string)
                         return -ENOMEM;
 
-                free(*ret);
-                *ret = string;
+                free_and_replace(*ret, string);
         }
 
         return 0;
@@ -373,7 +357,7 @@ static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) {
                 return 0;
         }
 
-        r = dns_name_normalize(name, &normalized);
+        r = dns_name_normalize(name, 0, &normalized);
         if (r < 0)
                 return r;
 
@@ -388,23 +372,6 @@ static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) {
         return 0;
 }
 
-static void filter_bogus_addresses(struct in_addr *addresses, size_t *n) {
-        size_t i, j;
-
-        /* Silently filter DNS/NTP servers supplied to us that do not make outside of the local scope. */
-
-        for (i = 0, j = 0; i < *n; i ++) {
-
-                if (in4_addr_is_null(addresses+i) ||
-                    in4_addr_is_localhost(addresses+i))
-                        continue;
-
-                addresses[j++] = addresses[i];
-        }
-
-        *n = j;
-}
-
 static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) {
         assert(option);
         assert(ret);
@@ -426,8 +393,6 @@ static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_add
                 if (!addresses)
                         return -ENOMEM;
 
-                filter_bogus_addresses(addresses, &n_addresses);
-
                 free(*ret);
                 *ret = addresses;
                 *n_ret = n_addresses;
@@ -572,11 +537,9 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
                 break;
 
         case SD_DHCP_OPTION_ROUTER:
-                if (len >= 4) {
-                        r = lease_parse_be32(option, 4, &lease->router);
-                        if (r < 0)
-                                log_debug_errno(r, "Failed to parse router address, ignoring: %m");
-                }
+                r = lease_parse_in_addrs(option, len, &lease->router, &lease->router_size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse router addresses, ignoring: %m");
                 break;
 
         case SD_DHCP_OPTION_DOMAIN_NAME_SERVER:
@@ -838,7 +801,6 @@ int dhcp_lease_new(sd_dhcp_lease **ret) {
         if (!lease)
                 return -ENOMEM;
 
-        lease->router = INADDR_ANY;
         lease->n_ref = 1;
 
         *ret = lease;
@@ -853,6 +815,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         const struct in_addr *addresses;
         const void *client_id, *data;
         size_t client_id_len, data_len;
+        char sbuf[INET_ADDRSTRLEN];
         const char *string;
         uint16_t mtu;
         _cleanup_free_ sd_dhcp_route **routes = NULL;
@@ -875,27 +838,30 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
 
         r = sd_dhcp_lease_get_address(lease, &address);
         if (r >= 0)
-                fprintf(f, "ADDRESS=%s\n", inet_ntoa(address));
+                fprintf(f, "ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
 
         r = sd_dhcp_lease_get_netmask(lease, &address);
         if (r >= 0)
-                fprintf(f, "NETMASK=%s\n", inet_ntoa(address));
+                fprintf(f, "NETMASK=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
 
-        r = sd_dhcp_lease_get_router(lease, &address);
-        if (r >= 0)
-                fprintf(f, "ROUTER=%s\n", inet_ntoa(address));
+        r = sd_dhcp_lease_get_router(lease, &addresses);
+        if (r > 0) {
+                fputs("ROUTER=", f);
+                serialize_in_addrs(f, addresses, r, false, NULL);
+                fputc('\n', f);
+        }
 
         r = sd_dhcp_lease_get_server_identifier(lease, &address);
         if (r >= 0)
-                fprintf(f, "SERVER_ADDRESS=%s\n", inet_ntoa(address));
+                fprintf(f, "SERVER_ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
 
         r = sd_dhcp_lease_get_next_server(lease, &address);
         if (r >= 0)
-                fprintf(f, "NEXT_SERVER=%s\n", inet_ntoa(address));
+                fprintf(f, "NEXT_SERVER=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
 
         r = sd_dhcp_lease_get_broadcast(lease, &address);
         if (r >= 0)
-                fprintf(f, "BROADCAST=%s\n", inet_ntoa(address));
+                fprintf(f, "BROADCAST=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
 
         r = sd_dhcp_lease_get_mtu(lease, &mtu);
         if (r >= 0)
@@ -916,15 +882,15 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         r = sd_dhcp_lease_get_dns(lease, &addresses);
         if (r > 0) {
                 fputs("DNS=", f);
-                serialize_in_addrs(f, addresses, r);
-                fputs("\n", f);
+                serialize_in_addrs(f, addresses, r, false, NULL);
+                fputc('\n', f);
         }
 
         r = sd_dhcp_lease_get_ntp(lease, &addresses);
         if (r > 0) {
                 fputs("NTP=", f);
-                serialize_in_addrs(f, addresses, r);
-                fputs("\n", f);
+                serialize_in_addrs(f, addresses, r, false, NULL);
+                fputc('\n', f);
         }
 
         r = sd_dhcp_lease_get_domainname(lease, &string);
@@ -935,7 +901,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         if (r > 0) {
                 fputs("DOMAIN_SEARCH_LIST=", f);
                 fputstrv(f, search_domains, NULL, NULL);
-                fputs("\n", f);
+                fputc('\n', f);
         }
 
         r = sd_dhcp_lease_get_hostname(lease, &string);
@@ -1036,7 +1002,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
         if (r < 0)
                 return r;
 
-        r = parse_env_file(NULL, lease_file, NEWLINE,
+        r = parse_env_file(NULL, lease_file,
                            "ADDRESS", &address,
                            "ROUTER", &router,
                            "NETMASK", &netmask,
@@ -1087,8 +1053,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                            "OPTION_251", &options[27],
                            "OPTION_252", &options[28],
                            "OPTION_253", &options[29],
-                           "OPTION_254", &options[30],
-                           NULL);
+                           "OPTION_254", &options[30]);
         if (r < 0)
                 return r;
 
@@ -1099,9 +1064,11 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
         }
 
         if (router) {
-                r = inet_pton(AF_INET, router, &lease->router);
-                if (r <= 0)
-                        log_debug("Failed to parse router %s, ignoring.", router);
+                r = deserialize_in_addrs(&lease->router, router);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize router addresses %s, ignoring: %m", router);
+                else
+                        lease->router_size = r;
         }
 
         if (netmask) {
@@ -1320,3 +1287,9 @@ int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) {
         *gateway = route->gw_addr;
         return 0;
 }
+
+int sd_dhcp_route_get_option(sd_dhcp_route *route) {
+        assert_return(route, -EINVAL);
+
+        return route->option;
+}
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
index f0cd2f97..b72cd82d 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -3,9 +3,8 @@
   Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <linux/if_infiniband.h>
@@ -18,6 +17,7 @@
 #include "dhcp6-lease-internal.h"
 #include "dhcp6-protocol.h"
 #include "dns-domain.h"
+#include "event-util.h"
 #include "fd-util.h"
 #include "hostname-util.h"
 #include "in-addr-util.h"
@@ -29,6 +29,13 @@
 
 #define MAX_MAC_ADDR_LEN INFINIBAND_ALEN
 
+/* what to request from the server, addresses (IA_NA) and/or prefixes (IA_PD) */
+enum {
+        DHCP6_REQUEST_IA_NA                     = 1,
+        DHCP6_REQUEST_IA_TA                     = 2, /* currently not used */
+        DHCP6_REQUEST_IA_PD                     = 4,
+};
+
 struct sd_dhcp6_client {
         unsigned n_ref;
 
@@ -42,12 +49,15 @@ struct sd_dhcp6_client {
         uint16_t arp_type;
         DHCP6IA ia_na;
         DHCP6IA ia_pd;
-        bool prefix_delegation;
+        sd_event_source *timeout_t1;
+        sd_event_source *timeout_t2;
+        unsigned request;
         be32_t transaction_id;
         usec_t transaction_start;
         struct sd_dhcp6_lease *lease;
         int fd;
         bool information_request;
+        bool iaid_set;
         be16_t *req_opts;
         size_t req_opts_allocated;
         size_t req_opts_len;
@@ -212,10 +222,10 @@ static int dhcp6_client_set_duid_internal(
 #if 0 /* NM_IGNORED */
                 switch (duid_type) {
                 case DUID_TYPE_LLT:
-                        if (!client->mac_addr || client->mac_addr_len == 0)
+                        if (client->mac_addr_len == 0)
                                 return -EOPNOTSUPP;
 
-                        r = dhcp_identifier_set_duid_llt(&client->duid, 0, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
+                        r = dhcp_identifier_set_duid_llt(&client->duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
                         if (r < 0)
                                 return r;
                         break;
@@ -225,7 +235,7 @@ static int dhcp6_client_set_duid_internal(
                                 return r;
                         break;
                 case DUID_TYPE_LL:
-                        if (!client->mac_addr || client->mac_addr_len == 0)
+                        if (client->mac_addr_len == 0)
                                 return -EOPNOTSUPP;
 
                         r = dhcp_identifier_set_duid_ll(&client->duid, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
@@ -255,7 +265,6 @@ int sd_dhcp6_client_set_duid(
         return dhcp6_client_set_duid_internal(client, duid_type, duid, duid_len, 0);
 }
 
-#if 0 /* NM_IGNORED */
 int sd_dhcp6_client_set_duid_llt(
                 sd_dhcp6_client *client,
                 usec_t llt_time) {
@@ -268,10 +277,10 @@ int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
 
         client->ia_na.ia_na.id = htobe32(iaid);
         client->ia_pd.ia_pd.id = htobe32(iaid);
+        client->iaid_set = true;
 
         return 0;
 }
-#endif /* NM_IGNORED */
 
 int sd_dhcp6_client_set_fqdn(
                 sd_dhcp6_client *client,
@@ -337,10 +346,44 @@ 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) {
+int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegation) {
+        assert_return(client, -EINVAL);
+        assert_return(delegation, -EINVAL);
+
+        *delegation = FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD);
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, int delegation) {
+        assert_return(client, -EINVAL);
+
+        SET_FLAG(client->request, DHCP6_REQUEST_IA_PD, delegation);
+
+        return 0;
+}
+
+int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request) {
+        assert_return(client, -EINVAL);
+        assert_return(request, -EINVAL);
+
+        *request = FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA);
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request) {
         assert_return(client, -EINVAL);
 
-        client->prefix_delegation = delegation;
+        SET_FLAG(client->request, DHCP6_REQUEST_IA_NA, request);
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id) {
+        assert_return(client, -EINVAL);
+
+        client->transaction_id = transaction_id;
 
         return 0;
 }
@@ -364,21 +407,10 @@ static void client_notify(sd_dhcp6_client *client, int event) {
                 client->callback(client, event, client->userdata);
 }
 
-static void client_set_lease(sd_dhcp6_client *client, sd_dhcp6_lease *lease) {
-        assert(client);
-
-        if (client->lease) {
-                dhcp6_lease_clear_timers(&client->lease->ia);
-                sd_dhcp6_lease_unref(client->lease);
-        }
-
-        client->lease = lease;
-}
-
 static int client_reset(sd_dhcp6_client *client) {
         assert(client);
 
-        client_set_lease(client, NULL);
+        client->lease = sd_dhcp6_lease_unref(client->lease);
 
         client->receive_message =
                 sd_event_source_unref(client->receive_message);
@@ -386,16 +418,13 @@ static int client_reset(sd_dhcp6_client *client) {
         client->transaction_id = 0;
         client->transaction_start = 0;
 
-        client->ia_na.timeout_t1 =
-                sd_event_source_unref(client->ia_na.timeout_t1);
-        client->ia_na.timeout_t2 =
-                sd_event_source_unref(client->ia_na.timeout_t2);
-
         client->retransmit_time = 0;
         client->retransmit_count = 0;
-        client->timeout_resend = sd_event_source_unref(client->timeout_resend);
-        client->timeout_resend_expire =
-                sd_event_source_unref(client->timeout_resend_expire);
+
+        (void) event_source_disable(client->timeout_resend);
+        (void) event_source_disable(client->timeout_resend_expire);
+        (void) event_source_disable(client->timeout_t1);
+        (void) event_source_disable(client->timeout_t2);
 
         client->state = DHCP6_STATE_STOPPED;
 
@@ -448,9 +477,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                 if (r < 0)
                         return r;
 
-                r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
-                if (r < 0)
-                        return r;
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) {
+                        r = dhcp6_option_append_ia(&opt, &optlen,
+                                                   &client->ia_na);
+                        if (r < 0)
+                                return r;
+                }
 
                 if (client->fqdn) {
                         r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
@@ -458,7 +490,7 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                                 return r;
                 }
 
-                if (client->prefix_delegation) {
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
                         r = dhcp6_option_append_pd(opt, optlen, &client->ia_pd);
                         if (r < 0)
                                 return r;
@@ -483,9 +515,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                 if (r < 0)
                         return r;
 
-                r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
-                if (r < 0)
-                        return r;
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) {
+                        r = dhcp6_option_append_ia(&opt, &optlen,
+                                                   &client->lease->ia);
+                        if (r < 0)
+                                return r;
+                }
 
                 if (client->fqdn) {
                         r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
@@ -493,7 +528,7 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                                 return r;
                 }
 
-                if (client->prefix_delegation) {
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
                         r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd);
                         if (r < 0)
                                 return r;
@@ -507,9 +542,11 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
         case DHCP6_STATE_REBIND:
                 message->type = DHCP6_REBIND;
 
-                r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
-                if (r < 0)
-                        return r;
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA)) {
+                        r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
+                        if (r < 0)
+                                return r;
+                }
 
                 if (client->fqdn) {
                         r = dhcp6_option_append_fqdn(&opt, &optlen, client->fqdn);
@@ -517,7 +554,7 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                                 return r;
                 }
 
-                if (client->prefix_delegation) {
+                if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
                         r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd);
                         if (r < 0)
                                 return r;
@@ -574,8 +611,7 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata)
         assert(client);
         assert(client->lease);
 
-        client->lease->ia.timeout_t2 =
-                sd_event_source_unref(client->lease->ia.timeout_t2);
+        (void) event_source_disable(client->timeout_t2);
 
         log_dhcp6_client(client, "Timeout T2");
 
@@ -591,8 +627,7 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata)
         assert(client);
         assert(client->lease);
 
-        client->lease->ia.timeout_t1 =
-                sd_event_source_unref(client->lease->ia.timeout_t1);
+        (void) event_source_disable(client->timeout_t1);
 
         log_dhcp6_client(client, "Timeout T1");
 
@@ -640,7 +675,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
         assert(client);
         assert(client->event);
 
-        client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+        (void) event_source_disable(client->timeout_resend);
 
         switch (client->state) {
         case DHCP6_STATE_INFORMATION_REQUEST:
@@ -682,7 +717,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
                 init_retransmit_time = DHCP6_REB_TIMEOUT;
                 max_retransmit_time = DHCP6_REB_MAX_RT;
 
-                if (!client->timeout_resend_expire) {
+                if (event_source_is_enabled(client->timeout_resend_expire) <= 0) {
                         r = dhcp6_lease_ia_rebind_expire(&client->lease->ia,
                                                          &expire);
                         if (r < 0) {
@@ -731,43 +766,24 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
         log_dhcp6_client(client, "Next retransmission in %s",
                          format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->retransmit_time, USEC_PER_SEC));
 
-        r = sd_event_add_time(client->event, &client->timeout_resend,
-                              clock_boottime_or_monotonic(),
-                              time_now + client->retransmit_time,
-                              10 * USEC_PER_MSEC, client_timeout_resend,
-                              client);
-        if (r < 0)
-                goto error;
-
-        r = sd_event_source_set_priority(client->timeout_resend,
-                                         client->event_priority);
-        if (r < 0)
-                goto error;
-
-        r = sd_event_source_set_description(client->timeout_resend, "dhcp6-resend-timer");
+        r = event_reset_time(client->event, &client->timeout_resend,
+                             clock_boottime_or_monotonic(),
+                             time_now + client->retransmit_time, 10 * USEC_PER_MSEC,
+                             client_timeout_resend, client,
+                             client->event_priority, "dhcp6-resend-timer", true);
         if (r < 0)
                 goto error;
 
-        if (max_retransmit_duration && !client->timeout_resend_expire) {
+        if (max_retransmit_duration && event_source_is_enabled(client->timeout_resend_expire) <= 0) {
 
                 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
                                  max_retransmit_duration / USEC_PER_SEC);
 
-                r = sd_event_add_time(client->event,
-                                      &client->timeout_resend_expire,
-                                      clock_boottime_or_monotonic(),
-                                      time_now + max_retransmit_duration,
-                                      USEC_PER_SEC,
-                                      client_timeout_resend_expire, client);
-                if (r < 0)
-                        goto error;
-
-                r = sd_event_source_set_priority(client->timeout_resend_expire,
-                                                 client->event_priority);
-                if (r < 0)
-                        goto error;
-
-                r = sd_event_source_set_description(client->timeout_resend_expire, "dhcp6-resend-expire-timer");
+                r = event_reset_time(client->event, &client->timeout_resend_expire,
+                                     clock_boottime_or_monotonic(),
+                                     time_now + max_retransmit_duration, USEC_PER_SEC,
+                                     client_timeout_resend_expire, client,
+                                     client->event_priority, "dhcp6-resend-expire-timer", true);
                 if (r < 0)
                         goto error;
         }
@@ -781,19 +797,20 @@ error:
 
 static int client_ensure_iaid(sd_dhcp6_client *client) {
         int r;
-        be32_t iaid;
+        uint32_t iaid;
 
         assert(client);
 
-        if (client->ia_na.ia_na.id)
+        if (client->iaid_set)
                 return 0;
 
-        r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, &iaid);
+        r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, true, &iaid);
         if (r < 0)
                 return r;
 
         client->ia_na.ia_na.id = iaid;
         client->ia_pd.ia_pd.id = iaid;
+        client->iaid_set = true;
 
         return 0;
 }
@@ -803,10 +820,11 @@ static int client_parse_message(
                 DHCP6Message *message,
                 size_t len,
                 sd_dhcp6_lease *lease) {
+
+        uint32_t lt_t1 = ~0, lt_t2 = ~0;
+        bool clientid = false;
         size_t pos = 0;
         int r;
-        bool clientid = false;
-        uint32_t lt_t1 = ~0, lt_t2 = ~0;
 
         assert(client);
         assert(message);
@@ -816,20 +834,22 @@ static int client_parse_message(
         len -= sizeof(DHCP6Message);
 
         while (pos < len) {
-                DHCP6Option *option = (DHCP6Option *)&message->options[pos];
+                DHCP6Option *option = (DHCP6Option *) &message->options[pos];
                 uint16_t optcode, optlen;
-                int status;
-                uint8_t *optval;
                 be32_t iaid_lease;
+                uint8_t *optval;
+                int status;
 
-                if (len < pos + offsetof(DHCP6Option, data) ||
-                    len < pos + offsetof(DHCP6Option, data) + be16toh(option->len))
+                if (len < pos + offsetof(DHCP6Option, data))
                         return -ENOBUFS;
 
                 optcode = be16toh(option->code);
                 optlen = be16toh(option->len);
                 optval = option->data;
 
+                if (len < pos + offsetof(DHCP6Option, data) + optlen)
+                        return -ENOBUFS;
+
                 switch (optcode) {
                 case SD_DHCP6_OPTION_CLIENTID:
                         if (clientid) {
@@ -927,7 +947,7 @@ static int client_parse_message(
                         if (r < 0 && r != -ENOMSG)
                                 return r;
 
-                        r = dhcp6_lease_get_iaid(lease, &iaid_lease);
+                        r = dhcp6_lease_get_pd_iaid(lease, &iaid_lease);
                         if (r < 0)
                                 return r;
 
@@ -980,7 +1000,7 @@ static int client_parse_message(
                         break;
                 }
 
-                pos += sizeof(*option) + optlen;
+                pos += offsetof(DHCP6Option, data) + optlen;
         }
 
         if (!clientid) {
@@ -1040,8 +1060,8 @@ static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, si
                         return 0;
         }
 
-        client_set_lease(client, lease);
-        lease = NULL;
+        sd_dhcp6_lease_unref(client->lease);
+        client->lease = TAKE_PTR(lease);
 
         return DHCP6_STATE_BOUND;
 }
@@ -1069,8 +1089,8 @@ static int client_receive_advertise(sd_dhcp6_client *client, DHCP6Message *adver
         r = dhcp6_lease_get_preference(client->lease, &pref_lease);
 
         if (r < 0 || pref_advertise > pref_lease) {
-                client_set_lease(client, lease);
-                lease = NULL;
+                sd_dhcp6_lease_unref(client->lease);
+                client->lease = TAKE_PTR(lease);
                 r = 0;
         }
 
@@ -1097,6 +1117,12 @@ static int client_receive_message(
         assert(client->event);
 
         buflen = next_datagram_size_fd(fd);
+        if (buflen == -ENETDOWN) {
+                /* the link is down. Don't return an error or the I/O event
+                   source will be disconnected and we won't be able to receive
+                   packets again when the link comes back. */
+                return 0;
+        }
         if (buflen < 0)
                 return buflen;
 
@@ -1106,7 +1132,8 @@ static int client_receive_message(
 
         len = recv(fd, message, buflen, 0);
         if (len < 0) {
-                if (IN_SET(errno, EAGAIN, EINTR))
+                /* see comment above for why we shouldn't error out on ENETDOWN. */
+                if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
                         return 0;
 
                 return log_dhcp6_client_errno(client, errno, "Could not receive message from UDP socket: %m");
@@ -1201,19 +1228,41 @@ static int client_receive_message(
         return 0;
 }
 
+static int client_get_lifetime(sd_dhcp6_client *client, uint32_t *lifetime_t1,
+                               uint32_t *lifetime_t2) {
+        assert_return(client, -EINVAL);
+        assert_return(client->lease, -EINVAL);
+
+        if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_NA) && client->lease->ia.addresses) {
+                *lifetime_t1 = be32toh(client->lease->ia.ia_na.lifetime_t1);
+                *lifetime_t2 = be32toh(client->lease->ia.ia_na.lifetime_t2);
+
+                return 0;
+        }
+
+        if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD) && client->lease->pd.addresses) {
+                *lifetime_t1 = be32toh(client->lease->pd.ia_pd.lifetime_t1);
+                *lifetime_t2 = be32toh(client->lease->pd.ia_pd.lifetime_t2);
+
+                return 0;
+        }
+
+        return -ENOMSG;
+}
+
 static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
         int r;
         usec_t timeout, time_now;
         char time_string[FORMAT_TIMESPAN_MAX];
+        uint32_t lifetime_t1, lifetime_t2;
 
         assert_return(client, -EINVAL);
         assert_return(client->event, -EINVAL);
         assert_return(client->ifindex > 0, -EINVAL);
         assert_return(client->state != state, -EINVAL);
 
-        client->timeout_resend_expire =
-                sd_event_source_unref(client->timeout_resend_expire);
-        client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+        (void) event_source_disable(client->timeout_resend_expire);
+        (void) event_source_disable(client->timeout_resend);
         client->retransmit_time = 0;
         client->retransmit_count = 0;
 
@@ -1264,59 +1313,40 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
 
         case DHCP6_STATE_BOUND:
 
-                if (client->lease->ia.ia_na.lifetime_t1 == 0xffffffff ||
-                    client->lease->ia.ia_na.lifetime_t2 == 0xffffffff) {
+                r = client_get_lifetime(client, &lifetime_t1, &lifetime_t2);
+                if (r < 0)
+                        goto error;
 
+                if (lifetime_t1 == 0xffffffff || lifetime_t2 == 0xffffffff) {
                         log_dhcp6_client(client, "Infinite T1 0x%08x or T2 0x%08x",
-                                         be32toh(client->lease->ia.ia_na.lifetime_t1),
-                                         be32toh(client->lease->ia.ia_na.lifetime_t2));
+                                         lifetime_t1, lifetime_t2);
 
                         return 0;
                 }
 
-                timeout = client_timeout_compute_random(be32toh(client->lease->ia.ia_na.lifetime_t1) * USEC_PER_SEC);
+                timeout = client_timeout_compute_random(lifetime_t1 * USEC_PER_SEC);
 
                 log_dhcp6_client(client, "T1 expires in %s",
                                  format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
 
-                client->lease->ia.timeout_t1 = sd_event_source_unref(client->lease->ia.timeout_t1);
-                r = sd_event_add_time(client->event,
-                                      &client->lease->ia.timeout_t1,
-                                      clock_boottime_or_monotonic(), time_now + timeout,
-                                      10 * USEC_PER_SEC, client_timeout_t1,
-                                      client);
+                r = event_reset_time(client->event, &client->timeout_t1,
+                                     clock_boottime_or_monotonic(),
+                                     time_now + timeout, 10 * USEC_PER_SEC,
+                                     client_timeout_t1, client,
+                                     client->event_priority, "dhcp6-t1-timeout", true);
                 if (r < 0)
                         goto error;
 
-                r = sd_event_source_set_priority(client->lease->ia.timeout_t1,
-                                                 client->event_priority);
-                if (r < 0)
-                        goto error;
-
-                r = sd_event_source_set_description(client->lease->ia.timeout_t1, "dhcp6-t1-timeout");
-                if (r < 0)
-                        goto error;
-
-                timeout = client_timeout_compute_random(be32toh(client->lease->ia.ia_na.lifetime_t2) * USEC_PER_SEC);
+                timeout = client_timeout_compute_random(lifetime_t2 * USEC_PER_SEC);
 
                 log_dhcp6_client(client, "T2 expires in %s",
                                  format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
 
-                client->lease->ia.timeout_t2 = sd_event_source_unref(client->lease->ia.timeout_t2);
-                r = sd_event_add_time(client->event,
-                                      &client->lease->ia.timeout_t2,
-                                      clock_boottime_or_monotonic(), time_now + timeout,
-                                      10 * USEC_PER_SEC, client_timeout_t2,
-                                      client);
-                if (r < 0)
-                        goto error;
-
-                r = sd_event_source_set_priority(client->lease->ia.timeout_t2,
-                                                 client->event_priority);
-                if (r < 0)
-                        goto error;
-
-                r = sd_event_source_set_description(client->lease->ia.timeout_t2, "dhcp6-t2-timeout");
+                r = event_reset_time(client->event, &client->timeout_t2,
+                                     clock_boottime_or_monotonic(),
+                                     time_now + timeout, 10 * USEC_PER_SEC,
+                                     client_timeout_t2, client,
+                                     client->event_priority, "dhcp6-t2-timeout", true);
                 if (r < 0)
                         goto error;
 
@@ -1328,18 +1358,11 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
         client->transaction_id = random_u32() & htobe32(0x00ffffff);
         client->transaction_start = time_now;
 
-        r = sd_event_add_time(client->event, &client->timeout_resend,
-                              clock_boottime_or_monotonic(), 0, 0, client_timeout_resend,
-                              client);
-        if (r < 0)
-                goto error;
-
-        r = sd_event_source_set_priority(client->timeout_resend,
-                                         client->event_priority);
-        if (r < 0)
-                goto error;
-
-        r = sd_event_source_set_description(client->timeout_resend, "dhcp6-resend-timeout");
+        r = event_reset_time(client->event, &client->timeout_resend,
+                             clock_boottime_or_monotonic(),
+                             0, 0,
+                             client_timeout_resend, client,
+                             client->event_priority, "dhcp6-resend-timeout", true);
         if (r < 0)
                 goto error;
 
@@ -1378,6 +1401,9 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) {
         if (!IN_SET(client->state, DHCP6_STATE_STOPPED))
                 return -EBUSY;
 
+        if (!client->information_request && !client->request)
+                return -EINVAL;
+
         r = client_reset(client);
         if (r < 0)
                 return r;
@@ -1446,27 +1472,13 @@ sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
         return client->event;
 }
 
-sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
-
-        if (!client)
-                return NULL;
-
-        assert(client->n_ref >= 1);
-        client->n_ref++;
-
-        return client;
-}
-
-sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
-
-        if (!client)
-                return NULL;
-
-        assert(client->n_ref >= 1);
-        client->n_ref--;
+static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
+        assert(client);
 
-        if (client->n_ref > 0)
-                return NULL;
+        client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+        client->timeout_resend_expire = sd_event_source_unref(client->timeout_resend_expire);
+        client->timeout_t1 = sd_event_source_unref(client->timeout_t1);
+        client->timeout_t2 = sd_event_source_unref(client->timeout_t2);
 
         client_reset(client);
 
@@ -1479,29 +1491,36 @@ sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
         return mfree(client);
 }
 
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_client, sd_dhcp6_client, dhcp6_client_free);
+
 int sd_dhcp6_client_new(sd_dhcp6_client **ret) {
         _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+        _cleanup_free_ be16_t *req_opts = NULL;
         size_t t;
 
         assert_return(ret, -EINVAL);
 
-        client = new0(sd_dhcp6_client, 1);
-        if (!client)
+        req_opts = new(be16_t, ELEMENTSOF(default_req_opts));
+        if (!req_opts)
                 return -ENOMEM;
 
-        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;
+        for (t = 0; t < ELEMENTSOF(default_req_opts); t++)
+                req_opts[t] = htobe16(default_req_opts[t]);
 
-        client->req_opts_len = ELEMENTSOF(default_req_opts);
-        client->req_opts = new0(be16_t, client->req_opts_len);
-        if (!client->req_opts)
+        client = new(sd_dhcp6_client, 1);
+        if (!client)
                 return -ENOMEM;
 
-        for (t = 0; t < client->req_opts_len; t++)
-                client->req_opts[t] = htobe16(default_req_opts[t]);
+        *client = (sd_dhcp6_client) {
+                .n_ref = 1,
+                .ia_na.type = SD_DHCP6_OPTION_IA_NA,
+                .ia_pd.type = SD_DHCP6_OPTION_IA_PD,
+                .ifindex = -1,
+                .request = DHCP6_REQUEST_IA_NA,
+                .fd = -1,
+                .req_opts_len = ELEMENTSOF(default_req_opts),
+                .req_opts = TAKE_PTR(req_opts),
+        };
 
         *ret = TAKE_PTR(client);
 
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
index cca0b500..7263c96f 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
@@ -3,9 +3,8 @@
   Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 
 #include "alloc-util.h"
 #include "dhcp6-lease-internal.h"
@@ -13,15 +12,6 @@
 #include "strv.h"
 #include "util.h"
 
-int dhcp6_lease_clear_timers(DHCP6IA *ia) {
-        assert_return(ia, -EINVAL);
-
-        ia->timeout_t1 = sd_event_source_unref(ia->timeout_t1);
-        ia->timeout_t2 = sd_event_source_unref(ia->timeout_t2);
-
-        return 0;
-}
-
 int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) {
         DHCP6Address *addr;
         uint32_t valid = 0, t;
@@ -50,8 +40,6 @@ DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) {
         if (!ia)
                 return NULL;
 
-        dhcp6_lease_clear_timers(ia);
-
         while (ia->addresses) {
                 address = ia->addresses;
 
@@ -65,15 +53,16 @@ DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) {
 
 int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id,
                              size_t len) {
+        uint8_t *serverid;
+
         assert_return(lease, -EINVAL);
         assert_return(id, -EINVAL);
 
-        free(lease->serverid);
-
-        lease->serverid = memdup(id, len);
-        if (!lease->serverid)
-                return -EINVAL;
+        serverid = memdup(id, len);
+        if (!serverid)
+                return -ENOMEM;
 
+        free_and_replace(lease->serverid, serverid);
         lease->serverid_len = len;
 
         return 0;
@@ -138,6 +127,15 @@ int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid) {
         return 0;
 }
 
+int dhcp6_lease_get_pd_iaid(sd_dhcp6_lease *lease, be32_t *iaid) {
+        assert_return(lease, -EINVAL);
+        assert_return(iaid, -EINVAL);
+
+        *iaid = lease->pd.ia_pd.id;
+
+        return 0;
+}
+
 int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *addr,
                                uint32_t *lifetime_preferred,
                                uint32_t *lifetime_valid) {
@@ -376,27 +374,8 @@ int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn) {
         return -ENOENT;
 }
 
-sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease) {
-
-        if (!lease)
-                return NULL;
-
-        assert(lease->n_ref >= 1);
-        lease->n_ref++;
-
-        return lease;
-}
-
-sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease) {
-
-        if (!lease)
-                return NULL;
-
-        assert(lease->n_ref >= 1);
-        lease->n_ref--;
-
-        if (lease->n_ref > 0)
-                return NULL;
+static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
+        assert(lease);
 
         free(lease->serverid);
         dhcp6_lease_free_ia(&lease->ia);
@@ -412,6 +391,8 @@ sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease) {
         return mfree(lease);
 }
 
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_lease, sd_dhcp6_lease, dhcp6_lease_free);
+
 int dhcp6_lease_new(sd_dhcp6_lease **ret) {
         sd_dhcp6_lease *lease;
 
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
index a39a865a..6de4adb5 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
@@ -3,10 +3,9 @@
   Copyright © 2014 Axis Communications AB. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #include <arpa/inet.h>
-#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -16,6 +15,7 @@
 #include "alloc-util.h"
 #include "arp-util.h"
 #include "ether-addr-util.h"
+#include "event-util.h"
 #include "fd-util.h"
 #include "in-addr-util.h"
 #include "list.h"
@@ -91,7 +91,7 @@ static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_count
 static void ipv4acd_reset(sd_ipv4acd *acd) {
         assert(acd);
 
-        acd->timer_event_source = sd_event_source_unref(acd->timer_event_source);
+        (void) event_source_disable(acd->timer_event_source);
         acd->receive_message_event_source = sd_event_source_unref(acd->receive_message_event_source);
 
         acd->fd = safe_close(acd->fd);
@@ -99,25 +99,10 @@ static void ipv4acd_reset(sd_ipv4acd *acd) {
         ipv4acd_set_state(acd, IPV4ACD_STATE_INIT, true);
 }
 
-sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *acd) {
-        if (!acd)
-                return NULL;
-
-        assert_se(acd->n_ref >= 1);
-        acd->n_ref++;
-
-        return acd;
-}
-
-sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd) {
-        if (!acd)
-                return NULL;
-
-        assert_se(acd->n_ref >= 1);
-        acd->n_ref--;
+static sd_ipv4acd *ipv4acd_free(sd_ipv4acd *acd) {
+        assert(acd);
 
-        if (acd->n_ref > 0)
-                return NULL;
+        acd->timer_event_source = sd_event_source_unref(acd->timer_event_source);
 
         ipv4acd_reset(acd);
         sd_ipv4acd_detach_event(acd);
@@ -125,19 +110,23 @@ sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd) {
         return mfree(acd);
 }
 
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4acd, sd_ipv4acd, ipv4acd_free);
+
 int sd_ipv4acd_new(sd_ipv4acd **ret) {
         _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL;
 
         assert_return(ret, -EINVAL);
 
-        acd = new0(sd_ipv4acd, 1);
+        acd = new(sd_ipv4acd, 1);
         if (!acd)
                 return -ENOMEM;
 
-        acd->n_ref = 1;
-        acd->state = IPV4ACD_STATE_INIT;
-        acd->ifindex = -1;
-        acd->fd = -1;
+        *acd = (sd_ipv4acd) {
+                .n_ref = 1,
+                .state = IPV4ACD_STATE_INIT,
+                .ifindex = -1,
+                .fd = -1,
+        };
 
         *ret = TAKE_PTR(acd);
 
@@ -168,9 +157,7 @@ int sd_ipv4acd_stop(sd_ipv4acd *acd) {
 static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata);
 
 static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_usec) {
-        _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL;
         usec_t next_timeout, time_now;
-        int r;
 
         assert(acd);
 
@@ -181,20 +168,11 @@ static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_u
 
         assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
 
-        r = sd_event_add_time(acd->event, &timer, clock_boottime_or_monotonic(), time_now + next_timeout, 0, ipv4acd_on_timeout, acd);
-        if (r < 0)
-                return r;
-
-        r = sd_event_source_set_priority(timer, acd->event_priority);
-        if (r < 0)
-                return r;
-
-        (void) sd_event_source_set_description(timer, "ipv4acd-timer");
-
-        sd_event_source_unref(acd->timer_event_source);
-        acd->timer_event_source = TAKE_PTR(timer);
-
-        return 0;
+        return event_reset_time(acd->event, &acd->timer_event_source,
+                                clock_boottime_or_monotonic(),
+                                time_now + next_timeout, 0,
+                                ipv4acd_on_timeout, acd,
+                                acd->event_priority, "ipv4acd-timer", true);
 }
 
 static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) {
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
index 69fa60e2..8b1e9665 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
@@ -3,10 +3,9 @@
   Copyright © 2014 Axis Communications AB. All rights reserved.
 ***/
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #include <arpa/inet.h>
-#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -57,30 +56,15 @@ struct sd_ipv4ll {
 
 static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
 
-sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll) {
-        if (!ll)
-                return NULL;
-
-        assert(ll->n_ref >= 1);
-        ll->n_ref++;
-
-        return ll;
-}
-
-sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) {
-        if (!ll)
-                return NULL;
-
-        assert(ll->n_ref >= 1);
-        ll->n_ref--;
-
-        if (ll->n_ref > 0)
-                return NULL;
+static sd_ipv4ll *ipv4ll_free(sd_ipv4ll *ll) {
+        assert(ll);
 
         sd_ipv4acd_unref(ll->acd);
         return mfree(ll);
 }
 
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_ipv4ll, sd_ipv4ll, ipv4ll_free);
+
 int sd_ipv4ll_new(sd_ipv4ll **ret) {
         _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL;
         int r;
diff --git a/src/systemd/src/libsystemd-network/sd-lldp.c b/src/systemd/src/libsystemd-network/sd-lldp.c
index 74ba8236..741128e1 100644
--- a/src/systemd/src/libsystemd-network/sd-lldp.c
+++ b/src/systemd/src/libsystemd-network/sd-lldp.c
@@ -1,39 +1,50 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #include <arpa/inet.h>
 #include <linux/sockios.h>
+#include <sys/ioctl.h>
 
 #include "sd-lldp.h"
 
 #include "alloc-util.h"
+#include "ether-addr-util.h"
+#include "event-util.h"
 #include "fd-util.h"
 #include "lldp-internal.h"
 #include "lldp-neighbor.h"
 #include "lldp-network.h"
 #include "socket-util.h"
-#include "ether-addr-util.h"
+#include "string-table.h"
 
 #define LLDP_DEFAULT_NEIGHBORS_MAX 128U
 
-static void lldp_flush_neighbors(sd_lldp *lldp) {
-        sd_lldp_neighbor *n;
+static const char * const lldp_event_table[_SD_LLDP_EVENT_MAX] = {
+        [SD_LLDP_EVENT_ADDED]   = "added",
+        [SD_LLDP_EVENT_REMOVED] = "removed",
+        [SD_LLDP_EVENT_UPDATED]   = "updated",
+        [SD_LLDP_EVENT_REFRESHED] = "refreshed",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(lldp_event, sd_lldp_event);
 
+static void lldp_flush_neighbors(sd_lldp *lldp) {
         assert(lldp);
 
-        while ((n = hashmap_first(lldp->neighbor_by_id)))
-                lldp_neighbor_unlink(n);
+        hashmap_clear(lldp->neighbor_by_id);
 }
 
 static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) {
         assert(lldp);
+        assert(event >= 0 && event < _SD_LLDP_EVENT_MAX);
 
-        log_lldp("Invoking callback for '%c'.", event);
-
-        if (!lldp->callback)
+        if (!lldp->callback) {
+                log_lldp("Received '%s' event.", lldp_event_to_string(event));
                 return;
+        }
 
+        log_lldp("Invoking callback for '%s' event.", lldp_event_to_string(event));
         lldp->callback(lldp, event, n, lldp->userdata);
 }
 
@@ -121,7 +132,7 @@ static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
                 }
 
                 if (lldp_neighbor_equal(n, old)) {
-                        /* Is this equal, then restart the TTL counter, but don't do anyting else. */
+                        /* Is this equal, then restart the TTL counter, but don't do anything else. */
                         old->timestamp = n->timestamp;
                         lldp_start_timer(lldp, old);
                         lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
@@ -225,7 +236,7 @@ static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, v
 static void lldp_reset(sd_lldp *lldp) {
         assert(lldp);
 
-        lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
+        (void) event_source_disable(lldp->timer_event_source);
         lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
         lldp->fd = safe_close(lldp->fd);
 }
@@ -331,27 +342,10 @@ _public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) {
         return 0;
 }
 
-_public_ sd_lldp* sd_lldp_ref(sd_lldp *lldp) {
-
-        if (!lldp)
-                return NULL;
-
-        assert(lldp->n_ref > 0);
-        lldp->n_ref++;
-
-        return lldp;
-}
-
-_public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) {
-
-        if (!lldp)
-                return NULL;
-
-        assert(lldp->n_ref > 0);
-        lldp->n_ref --;
+static sd_lldp* lldp_free(sd_lldp *lldp) {
+        assert(lldp);
 
-        if (lldp->n_ref > 0)
-                return NULL;
+        lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
 
         lldp_reset(lldp);
         sd_lldp_detach_event(lldp);
@@ -362,22 +356,26 @@ _public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) {
         return mfree(lldp);
 }
 
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp, sd_lldp, lldp_free);
+
 _public_ int sd_lldp_new(sd_lldp **ret) {
         _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
         int r;
 
         assert_return(ret, -EINVAL);
 
-        lldp = new0(sd_lldp, 1);
+        lldp = new(sd_lldp, 1);
         if (!lldp)
                 return -ENOMEM;
 
-        lldp->n_ref = 1;
-        lldp->fd = -1;
-        lldp->neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX;
-        lldp->capability_mask = (uint16_t) -1;
+        *lldp = (sd_lldp) {
+                .n_ref = 1,
+                .fd = -1,
+                .neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX,
+                .capability_mask = (uint16_t) -1,
+        };
 
-        lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_id_hash_ops);
+        lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_hash_ops);
         if (!lldp->neighbor_by_id)
                 return -ENOMEM;
 
@@ -390,10 +388,8 @@ _public_ int sd_lldp_new(sd_lldp **ret) {
         return 0;
 }
 
-static int neighbor_compare_func(const void *a, const void *b) {
-        const sd_lldp_neighbor * const*x = a, * const *y = b;
-
-        return lldp_neighbor_id_hash_ops.compare(&(*x)->id, &(*y)->id);
+static int neighbor_compare_func(sd_lldp_neighbor * const *a, sd_lldp_neighbor * const *b) {
+        return lldp_neighbor_id_compare_func(&(*a)->id, &(*b)->id);
 }
 
 static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
@@ -413,7 +409,6 @@ static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
 
 static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) {
         sd_lldp_neighbor *n;
-        int r;
 
         assert(lldp);
 
@@ -421,35 +416,17 @@ static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) {
                 lldp_neighbor_start_ttl(neighbor);
 
         n = prioq_peek(lldp->neighbor_by_expiry);
-        if (!n) {
-
-                if (lldp->timer_event_source)
-                        return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_OFF);
-
-                return 0;
-        }
-
-        if (lldp->timer_event_source) {
-                r = sd_event_source_set_time(lldp->timer_event_source, n->until);
-                if (r < 0)
-                        return r;
-
-                return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_ONESHOT);
-        }
+        if (!n)
+                return event_source_disable(lldp->timer_event_source);
 
         if (!lldp->event)
                 return 0;
 
-        r = sd_event_add_time(lldp->event, &lldp->timer_event_source, clock_boottime_or_monotonic(), n->until, 0, on_timer_event, lldp);
-        if (r < 0)
-                return r;
-
-        r = sd_event_source_set_priority(lldp->timer_event_source, lldp->event_priority);
-        if (r < 0)
-                return r;
-
-        (void) sd_event_source_set_description(lldp->timer_event_source, "lldp-timer");
-        return 0;
+        return event_reset_time(lldp->event, &lldp->timer_event_source,
+                                clock_boottime_or_monotonic(),
+                                n->until, 0,
+                                on_timer_event, lldp,
+                                lldp->event_priority, "lldp-timer", true);
 }
 
 _public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
@@ -481,7 +458,7 @@ _public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
         assert((size_t) k == hashmap_size(lldp->neighbor_by_id));
 
         /* Return things in a stable order */
-        qsort(l, k, sizeof(sd_lldp_neighbor*), neighbor_compare_func);
+        typesafe_qsort(l, k, neighbor_compare_func);
         *ret = l;
 
         return k;
diff --git a/src/systemd/src/libsystemd/sd-event/event-source.h b/src/systemd/src/libsystemd/sd-event/event-source.h
new file mode 100644
index 00000000..99ab8fc1
--- /dev/null
+++ b/src/systemd/src/libsystemd/sd-event/event-source.h
@@ -0,0 +1,206 @@
+#pragma once
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <sys/wait.h>
+
+#include "sd-event.h"
+
+#include "fs-util.h"
+#include "hashmap.h"
+#include "list.h"
+#include "prioq.h"
+
+typedef enum EventSourceType {
+        SOURCE_IO,
+        SOURCE_TIME_REALTIME,
+        SOURCE_TIME_BOOTTIME,
+        SOURCE_TIME_MONOTONIC,
+        SOURCE_TIME_REALTIME_ALARM,
+        SOURCE_TIME_BOOTTIME_ALARM,
+        SOURCE_SIGNAL,
+        SOURCE_CHILD,
+        SOURCE_DEFER,
+        SOURCE_POST,
+        SOURCE_EXIT,
+        SOURCE_WATCHDOG,
+        SOURCE_INOTIFY,
+        _SOURCE_EVENT_SOURCE_TYPE_MAX,
+        _SOURCE_EVENT_SOURCE_TYPE_INVALID = -1
+} EventSourceType;
+
+/* All objects we use in epoll events start with this value, so that
+ * we know how to dispatch it */
+typedef enum WakeupType {
+        WAKEUP_NONE,
+        WAKEUP_EVENT_SOURCE,
+        WAKEUP_CLOCK_DATA,
+        WAKEUP_SIGNAL_DATA,
+        WAKEUP_INOTIFY_DATA,
+        _WAKEUP_TYPE_MAX,
+        _WAKEUP_TYPE_INVALID = -1,
+} WakeupType;
+
+struct inode_data;
+
+struct sd_event_source {
+        WakeupType wakeup;
+
+        unsigned n_ref;
+
+        sd_event *event;
+        void *userdata;
+        sd_event_handler_t prepare;
+
+        char *description;
+
+        EventSourceType type:5;
+        signed int enabled:3;
+        bool pending:1;
+        bool dispatching:1;
+        bool floating:1;
+
+        int64_t priority;
+        unsigned pending_index;
+        unsigned prepare_index;
+        uint64_t pending_iteration;
+        uint64_t prepare_iteration;
+
+        sd_event_destroy_t destroy_callback;
+
+        LIST_FIELDS(sd_event_source, sources);
+
+        union {
+                struct {
+                        sd_event_io_handler_t callback;
+                        int fd;
+                        uint32_t events;
+                        uint32_t revents;
+                        bool registered:1;
+                        bool owned:1;
+                } io;
+                struct {
+                        sd_event_time_handler_t callback;
+                        usec_t next, accuracy;
+                        unsigned earliest_index;
+                        unsigned latest_index;
+                } time;
+                struct {
+                        sd_event_signal_handler_t callback;
+                        struct signalfd_siginfo siginfo;
+                        int sig;
+                } signal;
+                struct {
+                        sd_event_child_handler_t callback;
+                        siginfo_t siginfo;
+                        pid_t pid;
+                        int options;
+                } child;
+                struct {
+                        sd_event_handler_t callback;
+                } defer;
+                struct {
+                        sd_event_handler_t callback;
+                } post;
+                struct {
+                        sd_event_handler_t callback;
+                        unsigned prioq_index;
+                } exit;
+                struct {
+                        sd_event_inotify_handler_t callback;
+                        uint32_t mask;
+                        struct inode_data *inode_data;
+                        LIST_FIELDS(sd_event_source, by_inode_data);
+                } inotify;
+        };
+};
+
+struct clock_data {
+        WakeupType wakeup;
+        int fd;
+
+        /* For all clocks we maintain two priority queues each, one
+         * ordered for the earliest times the events may be
+         * dispatched, and one ordered by the latest times they must
+         * have been dispatched. The range between the top entries in
+         * the two prioqs is the time window we can freely schedule
+         * wakeups in */
+
+        Prioq *earliest;
+        Prioq *latest;
+        usec_t next;
+
+        bool needs_rearm:1;
+};
+
+struct signal_data {
+        WakeupType wakeup;
+
+        /* For each priority we maintain one signal fd, so that we
+         * only have to dequeue a single event per priority at a
+         * time. */
+
+        int fd;
+        int64_t priority;
+        sigset_t sigset;
+        sd_event_source *current;
+};
+
+/* A structure listing all event sources currently watching a specific inode */
+struct inode_data {
+        /* The identifier for the inode, the combination of the .st_dev + .st_ino fields of the file */
+        ino_t ino;
+        dev_t dev;
+
+        /* An fd of the inode to watch. The fd is kept open until the next iteration of the loop, so that we can
+         * rearrange the priority still until then, as we need the original inode to change the priority as we need to
+         * add a watch descriptor to the right inotify for the priority which we can only do if we have a handle to the
+         * original inode. We keep a list of all inode_data objects with an open fd in the to_close list (see below) of
+         * the sd-event object, so that it is efficient to close everything, before entering the next event loop
+         * iteration. */
+        int fd;
+
+        /* The inotify "watch descriptor" */
+        int wd;
+
+        /* The combination of the mask of all inotify watches on this inode we manage. This is also the mask that has
+         * most recently been set on the watch descriptor. */
+        uint32_t combined_mask;
+
+        /* All event sources subscribed to this inode */
+        LIST_HEAD(sd_event_source, event_sources);
+
+        /* The inotify object we watch this inode with */
+        struct inotify_data *inotify_data;
+
+        /* A linked list of all inode data objects with fds to close (see above) */
+        LIST_FIELDS(struct inode_data, to_close);
+};
+
+/* A structure encapsulating an inotify fd */
+struct inotify_data {
+        WakeupType wakeup;
+
+        /* For each priority we maintain one inotify fd, so that we only have to dequeue a single event per priority at
+         * a time */
+
+        int fd;
+        int64_t priority;
+
+        Hashmap *inodes; /* The inode_data structures keyed by dev+ino */
+        Hashmap *wd;     /* The inode_data structures keyed by the watch descriptor for each */
+
+        /* The buffer we read inotify events into */
+        union inotify_event_buffer buffer;
+        size_t buffer_filled; /* fill level of the buffer */
+
+        /* How many event sources are currently marked pending for this inotify. We won't read new events off the
+         * inotify fd as long as there are still pending events on the inotify (because we have no strategy of queuing
+         * the events locally if they can't be coalesced). */
+        unsigned n_pending;
+
+        /* A linked list of all inotify objects with data already read, that still need processing. We keep this list
+         * to make it efficient to figure out what inotify objects to process data on next. */
+        LIST_FIELDS(struct inotify_data, buffered);
+};
diff --git a/src/systemd/src/libsystemd/sd-event/event-util.c b/src/systemd/src/libsystemd/sd-event/event-util.c
new file mode 100644
index 00000000..2afacfe6
--- /dev/null
+++ b/src/systemd/src/libsystemd/sd-event/event-util.c
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-core.h"
+
+
+#include "event-source.h"
+#include "event-util.h"
+#include "log.h"
+#include "string-util.h"
+
+int event_reset_time(
+                sd_event *e,
+                sd_event_source **s,
+                clockid_t clock,
+                uint64_t usec,
+                uint64_t accuracy,
+                sd_event_time_handler_t callback,
+                void *userdata,
+                int64_t priority,
+                const char *description,
+                bool force_reset) {
+
+        bool created = false;
+        int enabled, r;
+        clockid_t c;
+
+        assert(e);
+        assert(s);
+
+        if (*s) {
+                if (!force_reset) {
+                        r = sd_event_source_get_enabled(*s, &enabled);
+                        if (r < 0)
+                                return log_debug_errno(r, "sd-event: Failed to query whether event source \"%s\" is enabled or not: %m",
+                                                       strna((*s)->description ?: description));
+
+                        if (enabled != SD_EVENT_OFF)
+                                return 0;
+                }
+
+                r = sd_event_source_get_time_clock(*s, &c);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to get clock id of event source \"%s\": %m", strna((*s)->description ?: description));
+
+                if (c != clock)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "sd-event: Current clock id %i of event source \"%s\" is different from specified one %i.",
+                                               (int)c,
+                                               strna((*s)->description ? : description),
+                                               (int)clock);
+
+                r = sd_event_source_set_time(*s, usec);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to set time for event source \"%s\": %m", strna((*s)->description ?: description));
+
+                r = sd_event_source_set_time_accuracy(*s, accuracy);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to set accuracy for event source \"%s\": %m", strna((*s)->description ?: description));
+
+                /* callback function is not updated, as we do not have sd_event_source_set_time_callback(). */
+
+                (void) sd_event_source_set_userdata(*s, userdata);
+
+                r = sd_event_source_set_enabled(*s, SD_EVENT_ONESHOT);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to enable event source \"%s\": %m", strna((*s)->description ?: description));
+        } else {
+                r = sd_event_add_time(e, s, clock, usec, accuracy, callback, userdata);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to create timer event \"%s\": %m", strna(description));
+
+                created = true;
+        }
+
+        r = sd_event_source_set_priority(*s, priority);
+        if (r < 0)
+                return log_debug_errno(r, "sd-event: Failed to set priority for event source \"%s\": %m", strna((*s)->description ?: description));
+
+        if (description) {
+                r = sd_event_source_set_description(*s, description);
+                if (r < 0)
+                        return log_debug_errno(r, "sd-event: Failed to set description for event source \"%s\": %m", description);
+        }
+
+        return created;
+}
+
+int event_source_disable(sd_event_source *s) {
+        if (!s)
+                return 0;
+
+        return sd_event_source_set_enabled(s, SD_EVENT_OFF);
+}
+
+int event_source_is_enabled(sd_event_source *s) {
+        if (!s)
+                return false;
+
+        return sd_event_source_get_enabled(s, NULL);
+}
diff --git a/src/systemd/src/libsystemd/sd-event/event-util.h b/src/systemd/src/libsystemd/sd-event/event-util.h
new file mode 100644
index 00000000..00180955
--- /dev/null
+++ b/src/systemd/src/libsystemd/sd-event/event-util.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+
+#include "sd-event.h"
+
+int event_reset_time(sd_event *e, sd_event_source **s,
+                     clockid_t clock, uint64_t usec, uint64_t accuracy,
+                     sd_event_time_handler_t callback, void *userdata,
+                     int64_t priority, const char *description, bool force_reset);
+int event_source_disable(sd_event_source *s);
+int event_source_is_enabled(sd_event_source *s);
diff --git a/src/systemd/src/libsystemd/sd-event/sd-event.c b/src/systemd/src/libsystemd/sd-event/sd-event.c
index 2af3572d..6f77421b 100644
--- a/src/systemd/src/libsystemd/sd-event/sd-event.c
+++ b/src/systemd/src/libsystemd/sd-event/sd-event.c
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #include <sys/epoll.h>
 #include <sys/timerfd.h>
@@ -11,6 +11,7 @@
 #include "sd-id128.h"
 
 #include "alloc-util.h"
+#include "event-source.h"
 #include "fd-util.h"
 #include "fs-util.h"
 #include "hashmap.h"
@@ -28,24 +29,6 @@
 
 #define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC)
 
-typedef enum EventSourceType {
-        SOURCE_IO,
-        SOURCE_TIME_REALTIME,
-        SOURCE_TIME_BOOTTIME,
-        SOURCE_TIME_MONOTONIC,
-        SOURCE_TIME_REALTIME_ALARM,
-        SOURCE_TIME_BOOTTIME_ALARM,
-        SOURCE_SIGNAL,
-        SOURCE_CHILD,
-        SOURCE_DEFER,
-        SOURCE_POST,
-        SOURCE_EXIT,
-        SOURCE_WATCHDOG,
-        SOURCE_INOTIFY,
-        _SOURCE_EVENT_SOURCE_TYPE_MAX,
-        _SOURCE_EVENT_SOURCE_TYPE_INVALID = -1
-} EventSourceType;
-
 static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = {
         [SOURCE_IO] = "io",
         [SOURCE_TIME_REALTIME] = "realtime",
@@ -64,183 +47,8 @@ static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX]
 
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int);
 
-/* All objects we use in epoll events start with this value, so that
- * we know how to dispatch it */
-typedef enum WakeupType {
-        WAKEUP_NONE,
-        WAKEUP_EVENT_SOURCE,
-        WAKEUP_CLOCK_DATA,
-        WAKEUP_SIGNAL_DATA,
-        WAKEUP_INOTIFY_DATA,
-        _WAKEUP_TYPE_MAX,
-        _WAKEUP_TYPE_INVALID = -1,
-} WakeupType;
-
 #define EVENT_SOURCE_IS_TIME(t) IN_SET((t), SOURCE_TIME_REALTIME, SOURCE_TIME_BOOTTIME, SOURCE_TIME_MONOTONIC, SOURCE_TIME_REALTIME_ALARM, SOURCE_TIME_BOOTTIME_ALARM)
 
-struct inode_data;
-
-struct sd_event_source {
-        WakeupType wakeup;
-
-        unsigned n_ref;
-
-        sd_event *event;
-        void *userdata;
-        sd_event_handler_t prepare;
-
-        char *description;
-
-        EventSourceType type:5;
-        signed int enabled:3;
-        bool pending:1;
-        bool dispatching:1;
-        bool floating:1;
-
-        int64_t priority;
-        unsigned pending_index;
-        unsigned prepare_index;
-        uint64_t pending_iteration;
-        uint64_t prepare_iteration;
-
-        sd_event_destroy_t destroy_callback;
-
-        LIST_FIELDS(sd_event_source, sources);
-
-        union {
-                struct {
-                        sd_event_io_handler_t callback;
-                        int fd;
-                        uint32_t events;
-                        uint32_t revents;
-                        bool registered:1;
-                        bool owned:1;
-                } io;
-                struct {
-                        sd_event_time_handler_t callback;
-                        usec_t next, accuracy;
-                        unsigned earliest_index;
-                        unsigned latest_index;
-                } time;
-                struct {
-                        sd_event_signal_handler_t callback;
-                        struct signalfd_siginfo siginfo;
-                        int sig;
-                } signal;
-                struct {
-                        sd_event_child_handler_t callback;
-                        siginfo_t siginfo;
-                        pid_t pid;
-                        int options;
-                } child;
-                struct {
-                        sd_event_handler_t callback;
-                } defer;
-                struct {
-                        sd_event_handler_t callback;
-                } post;
-                struct {
-                        sd_event_handler_t callback;
-                        unsigned prioq_index;
-                } exit;
-                struct {
-                        sd_event_inotify_handler_t callback;
-                        uint32_t mask;
-                        struct inode_data *inode_data;
-                        LIST_FIELDS(sd_event_source, by_inode_data);
-                } inotify;
-        };
-};
-
-struct clock_data {
-        WakeupType wakeup;
-        int fd;
-
-        /* For all clocks we maintain two priority queues each, one
-         * ordered for the earliest times the events may be
-         * dispatched, and one ordered by the latest times they must
-         * have been dispatched. The range between the top entries in
-         * the two prioqs is the time window we can freely schedule
-         * wakeups in */
-
-        Prioq *earliest;
-        Prioq *latest;
-        usec_t next;
-
-        bool needs_rearm:1;
-};
-
-struct signal_data {
-        WakeupType wakeup;
-
-        /* For each priority we maintain one signal fd, so that we
-         * only have to dequeue a single event per priority at a
-         * time. */
-
-        int fd;
-        int64_t priority;
-        sigset_t sigset;
-        sd_event_source *current;
-};
-
-/* A structure listing all event sources currently watching a specific inode */
-struct inode_data {
-        /* The identifier for the inode, the combination of the .st_dev + .st_ino fields of the file */
-        ino_t ino;
-        dev_t dev;
-
-        /* An fd of the inode to watch. The fd is kept open until the next iteration of the loop, so that we can
-         * rearrange the priority still until then, as we need the original inode to change the priority as we need to
-         * add a watch descriptor to the right inotify for the priority which we can only do if we have a handle to the
-         * original inode. We keep a list of all inode_data objects with an open fd in the to_close list (see below) of
-         * the sd-event object, so that it is efficient to close everything, before entering the next event loop
-         * iteration. */
-        int fd;
-
-        /* The inotify "watch descriptor" */
-        int wd;
-
-        /* The combination of the mask of all inotify watches on this inode we manage. This is also the mask that has
-         * most recently been set on the watch descriptor. */
-        uint32_t combined_mask;
-
-        /* All event sources subscribed to this inode */
-        LIST_HEAD(sd_event_source, event_sources);
-
-        /* The inotify object we watch this inode with */
-        struct inotify_data *inotify_data;
-
-        /* A linked list of all inode data objects with fds to close (see above) */
-        LIST_FIELDS(struct inode_data, to_close);
-};
-
-/* A structure encapsulating an inotify fd */
-struct inotify_data {
-        WakeupType wakeup;
-
-        /* For each priority we maintain one inotify fd, so that we only have to dequeue a single event per priority at
-         * a time */
-
-        int fd;
-        int64_t priority;
-
-        Hashmap *inodes; /* The inode_data structures keyed by dev+ino */
-        Hashmap *wd;     /* The inode_data structures keyed by the watch descriptor for each */
-
-        /* The buffer we read inotify events into */
-        union inotify_event_buffer buffer;
-        size_t buffer_filled; /* fill level of the buffer */
-
-        /* How many event sources are currently marked pending for this inotify. We won't read new events off the
-         * inotify fd as long as there are still pending events on the inotify (because we have no strategy of queuing
-         * the events locally if they can't be coalesced). */
-        unsigned n_pending;
-
-        /* A linked list of all inotify objects with data already read, that still need processing. We keep this list
-         * to make it efficient to figure out what inotify objects to process data on next. */
-        LIST_FIELDS(struct inotify_data, buffered);
-};
-
 struct sd_event {
         unsigned n_ref;
 
@@ -316,6 +124,7 @@ static sd_event *event_resolve(sd_event *e) {
 
 static int pending_prioq_compare(const void *a, const void *b) {
         const sd_event_source *x = a, *y = b;
+        int r;
 
         assert(x->pending);
         assert(y->pending);
@@ -327,22 +136,17 @@ static int pending_prioq_compare(const void *a, const void *b) {
                 return 1;
 
         /* Lower priority values first */
-        if (x->priority < y->priority)
-                return -1;
-        if (x->priority > y->priority)
-                return 1;
+        r = CMP(x->priority, y->priority);
+        if (r != 0)
+                return r;
 
         /* Older entries first */
-        if (x->pending_iteration < y->pending_iteration)
-                return -1;
-        if (x->pending_iteration > y->pending_iteration)
-                return 1;
-
-        return 0;
+        return CMP(x->pending_iteration, y->pending_iteration);
 }
 
 static int prepare_prioq_compare(const void *a, const void *b) {
         const sd_event_source *x = a, *y = b;
+        int r;
 
         assert(x->prepare);
         assert(y->prepare);
@@ -356,18 +160,12 @@ static int prepare_prioq_compare(const void *a, const void *b) {
         /* Move most recently prepared ones last, so that we can stop
          * preparing as soon as we hit one that has already been
          * prepared in the current iteration */
-        if (x->prepare_iteration < y->prepare_iteration)
-                return -1;
-        if (x->prepare_iteration > y->prepare_iteration)
-                return 1;
+        r = CMP(x->prepare_iteration, y->prepare_iteration);
+        if (r != 0)
+                return r;
 
         /* Lower priority values first */
-        if (x->priority < y->priority)
-                return -1;
-        if (x->priority > y->priority)
-                return 1;
-
-        return 0;
+        return CMP(x->priority, y->priority);
 }
 
 static int earliest_time_prioq_compare(const void *a, const void *b) {
@@ -389,12 +187,7 @@ static int earliest_time_prioq_compare(const void *a, const void *b) {
                 return 1;
 
         /* Order by time */
-        if (x->time.next < y->time.next)
-                return -1;
-        if (x->time.next > y->time.next)
-                return 1;
-
-        return 0;
+        return CMP(x->time.next, y->time.next);
 }
 
 static usec_t time_event_source_latest(const sd_event_source *s) {
@@ -420,12 +213,7 @@ static int latest_time_prioq_compare(const void *a, const void *b) {
                 return 1;
 
         /* Order by time */
-        if (time_event_source_latest(x) < time_event_source_latest(y))
-                return -1;
-        if (time_event_source_latest(x) > time_event_source_latest(y))
-                return 1;
-
-        return 0;
+        return CMP(time_event_source_latest(x), time_event_source_latest(y));
 }
 
 static int exit_prioq_compare(const void *a, const void *b) {
@@ -441,12 +229,7 @@ static int exit_prioq_compare(const void *a, const void *b) {
                 return 1;
 
         /* Lower priority values first */
-        if (x->priority < y->priority)
-                return -1;
-        if (x->priority > y->priority)
-                return 1;
-
-        return 0;
+        return CMP(x->priority, y->priority);
 }
 
 static void free_clock_data(struct clock_data *d) {
@@ -458,7 +241,7 @@ static void free_clock_data(struct clock_data *d) {
         prioq_free(d->latest);
 }
 
-static void event_free(sd_event *e) {
+static sd_event *event_free(sd_event *e) {
         sd_event_source *s;
 
         assert(e);
@@ -494,7 +277,8 @@ static void event_free(sd_event *e) {
 
         hashmap_free(e->child_sources);
         set_free(e->post_sources);
-        free(e);
+
+        return mfree(e);
 }
 
 _public_ int sd_event_new(sd_event** ret) {
@@ -555,30 +339,7 @@ fail:
         return r;
 }
 
-_public_ sd_event* sd_event_ref(sd_event *e) {
-
-        if (!e)
-                return NULL;
-
-        assert(e->n_ref >= 1);
-        e->n_ref++;
-
-        return e;
-}
-
-_public_ sd_event* sd_event_unref(sd_event *e) {
-
-        if (!e)
-                return NULL;
-
-        assert(e->n_ref >= 1);
-        e->n_ref--;
-
-        if (e->n_ref <= 0)
-                event_free(e);
-
-        return NULL;
-}
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event, sd_event, event_free);
 
 static bool event_pid_changed(sd_event *e) {
         assert(e);
@@ -711,6 +472,17 @@ static struct clock_data* event_get_clock_data(sd_event *e, EventSourceType t) {
         }
 }
 
+static void event_free_signal_data(sd_event *e, struct signal_data *d) {
+        assert(e);
+
+        if (!d)
+                return;
+
+        hashmap_remove(e->signal_data, &d->priority);
+        safe_close(d->fd);
+        free(d);
+}
+
 static int event_make_signal_data(
                 sd_event *e,
                 int sig,
@@ -800,11 +572,8 @@ static int event_make_signal_data(
         return 0;
 
 fail:
-        if (added) {
-                d->fd = safe_close(d->fd);
-                hashmap_remove(e->signal_data, &d->priority);
-                free(d);
-        }
+        if (added)
+                event_free_signal_data(e, d);
 
         return r;
 }
@@ -823,11 +592,8 @@ static void event_unmask_signal_data(sd_event *e, struct signal_data *d, int sig
         assert_se(sigdelset(&d->sigset, sig) >= 0);
 
         if (sigisemptyset(&d->sigset)) {
-
                 /* If all the mask is all-zero we can get rid of the structure */
-                hashmap_remove(e->signal_data, &d->priority);
-                safe_close(d->fd);
-                free(d);
+                event_free_signal_data(e, d);
                 return;
         }
 
@@ -974,7 +740,7 @@ static void source_disconnect(sd_event_source *s) {
                          * continued to being watched. That's because inotify doesn't really have an API for that: we
                          * can only change watch masks with access to the original inode either by fd or by path. But
                          * paths aren't stable, and keeping an O_PATH fd open all the time would mean wasting an fd
-                         * continously and keeping the mount busy which we can't really do. We could reconstruct the
+                         * continuously and keeping the mount busy which we can't really do. We could reconstruct the
                          * original inode from /proc/self/fdinfo/$INOTIFY_FD (as all watch descriptors are listed
                          * there), but given the need for open_by_handle_at() which is privileged and not universally
                          * available this would be quite an incomplete solution. Hence we go the other way, leave the
@@ -1023,6 +789,7 @@ static void source_free(sd_event_source *s) {
         free(s->description);
         free(s);
 }
+DEFINE_TRIVIAL_CLEANUP_FUNC(sd_event_source*, source_free);
 
 static int source_set_pending(sd_event_source *s, bool b) {
         int r;
@@ -1116,7 +883,7 @@ _public_ int sd_event_add_io(
                 sd_event_io_handler_t callback,
                 void *userdata) {
 
-        sd_event_source *s;
+        _cleanup_(source_freep) sd_event_source *s = NULL;
         int r;
 
         assert_return(e, -EINVAL);
@@ -1139,13 +906,12 @@ _public_ int sd_event_add_io(
         s->enabled = SD_EVENT_ON;
 
         r = source_io_register(s, s->enabled, events);
-        if (r < 0) {
-                source_free(s);
+        if (r < 0)
                 return r;
-        }
 
         if (ret)
                 *ret = s;
+        TAKE_PTR(s);
 
         return 0;
 }
@@ -1154,7 +920,7 @@ static void initialize_perturb(sd_event *e) {
         sd_id128_t bootid = {};
 
         /* When we sleep for longer, we try to realign the wakeup to
-           the same time wihtin each minute/second/250ms, so that
+           the same time within each minute/second/250ms, so that
            events all across the system can be coalesced into a single
            CPU wakeup. However, let's take some system-specific
            randomness for this value, so that in a network of systems
@@ -1220,7 +986,7 @@ _public_ int sd_event_add_time(
                 void *userdata) {
 
         EventSourceType type;
-        sd_event_source *s;
+        _cleanup_(source_freep) sd_event_source *s = NULL;
         struct clock_data *d;
         int r;
 
@@ -1272,20 +1038,17 @@ _public_ int sd_event_add_time(
 
         r = prioq_put(d->earliest, s, &s->time.earliest_index);
         if (r < 0)
-                goto fail;
+                return r;
 
         r = prioq_put(d->latest, s, &s->time.latest_index);
         if (r < 0)
-                goto fail;
+                return r;
 
         if (ret)
                 *ret = s;
+        TAKE_PTR(s);
 
         return 0;
-
-fail:
-        source_free(s);
-        return r;
 }
 
 #if 0 /* NM_IGNORED */
@@ -1302,7 +1065,7 @@ _public_ int sd_event_add_signal(
                 sd_event_signal_handler_t callback,
                 void *userdata) {
 
-        sd_event_source *s;
+        _cleanup_(source_freep) sd_event_source *s = NULL;
         struct signal_data *d;
         sigset_t ss;
         int r;
@@ -1342,16 +1105,15 @@ _public_ int sd_event_add_signal(
         e->signal_sources[sig] = s;
 
         r = event_make_signal_data(e, sig, &d);
-        if (r < 0) {
-                source_free(s);
+        if (r < 0)
                 return r;
-        }
 
         /* Use the signal name as description for the event source by default */
         (void) sd_event_source_set_description(s, signal_to_string(sig));
 
         if (ret)
                 *ret = s;
+        TAKE_PTR(s);
 
         return 0;
 }
@@ -1365,7 +1127,7 @@ _public_ int sd_event_add_child(
                 sd_event_child_handler_t callback,
                 void *userdata) {
 
-        sd_event_source *s;
+        _cleanup_(source_freep) sd_event_source *s = NULL;
         int r;
 
         assert_return(e, -EINVAL);
@@ -1395,17 +1157,14 @@ _public_ int sd_event_add_child(
         s->enabled = SD_EVENT_ONESHOT;
 
         r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s);
-        if (r < 0) {
-                source_free(s);
+        if (r < 0)
                 return r;
-        }
 
         e->n_enabled_child_sources++;
 
         r = event_make_signal_data(e, SIGCHLD, NULL);
         if (r < 0) {
                 e->n_enabled_child_sources--;
-                source_free(s);
                 return r;
         }
 
@@ -1413,6 +1172,7 @@ _public_ int sd_event_add_child(
 
         if (ret)
                 *ret = s;
+        TAKE_PTR(s);
 
         return 0;
 }
@@ -1423,7 +1183,7 @@ _public_ int sd_event_add_defer(
                 sd_event_handler_t callback,
                 void *userdata) {
 
-        sd_event_source *s;
+        _cleanup_(source_freep) sd_event_source *s = NULL;
         int r;
 
         assert_return(e, -EINVAL);
@@ -1441,13 +1201,12 @@ _public_ int sd_event_add_defer(
         s->enabled = SD_EVENT_ONESHOT;
 
         r = source_set_pending(s, true);
-        if (r < 0) {
-                source_free(s);
+        if (r < 0)
                 return r;
-        }
 
         if (ret)
                 *ret = s;
+        TAKE_PTR(s);
 
         return 0;
 }
@@ -1458,7 +1217,7 @@ _public_ int sd_event_add_post(
                 sd_event_handler_t callback,
                 void *userdata) {
 
-        sd_event_source *s;
+        _cleanup_(source_freep) sd_event_source *s = NULL;
         int r;
 
         assert_return(e, -EINVAL);
@@ -1480,13 +1239,12 @@ _public_ int sd_event_add_post(
         s->enabled = SD_EVENT_ON;
 
         r = set_put(e->post_sources, s);
-        if (r < 0) {
-                source_free(s);
+        if (r < 0)
                 return r;
-        }
 
         if (ret)
                 *ret = s;
+        TAKE_PTR(s);
 
         return 0;
 }
@@ -1497,7 +1255,7 @@ _public_ int sd_event_add_exit(
                 sd_event_handler_t callback,
                 void *userdata) {
 
-        sd_event_source *s;
+        _cleanup_(source_freep) sd_event_source *s = NULL;
         int r;
 
         assert_return(e, -EINVAL);
@@ -1520,13 +1278,12 @@ _public_ int sd_event_add_exit(
         s->enabled = SD_EVENT_ONESHOT;
 
         r = prioq_put(s->event->exit, s, &s->exit.prioq_index);
-        if (r < 0) {
-                source_free(s);
+        if (r < 0)
                 return r;
-        }
 
         if (ret)
                 *ret = s;
+        TAKE_PTR(s);
 
         return 0;
 }
@@ -1623,38 +1380,27 @@ static int event_make_inotify_data(
         return 1;
 }
 
-static int inode_data_compare(const void *a, const void *b) {
-        const struct inode_data *x = a, *y = b;
+static int inode_data_compare(const struct inode_data *x, const struct inode_data *y) {
+        int r;
 
         assert(x);
         assert(y);
 
-        if (x->dev < y->dev)
-                return -1;
-        if (x->dev > y->dev)
-                return 1;
-
-        if (x->ino < y->ino)
-                return -1;
-        if (x->ino > y->ino)
-                return 1;
+        r = CMP(x->dev, y->dev);
+        if (r != 0)
+                return r;
 
-        return 0;
+        return CMP(x->ino, y->ino);
 }
 
-static void inode_data_hash_func(const void *p, struct siphash *state) {
-        const struct inode_data *d = p;
-
-        assert(p);
+static void inode_data_hash_func(const struct inode_data *d, struct siphash *state) {
+        assert(d);
 
         siphash24_compress(&d->dev, sizeof(d->dev), state);
         siphash24_compress(&d->ino, sizeof(d->ino), state);
 }
 
-const struct hash_ops inode_data_hash_ops = {
-        .hash = inode_data_hash_func,
-        .compare = inode_data_compare
-};
+DEFINE_PRIVATE_HASH_OPS(inode_data_hash_ops, struct inode_data, inode_data_hash_func, inode_data_compare);
 
 static void event_free_inode_data(
                 sd_event *e,
@@ -1781,7 +1527,7 @@ static uint32_t inode_data_determine_mask(struct inode_data *d) {
          *
          * Note that we add all sources to the mask here, regardless whether enabled, disabled or oneshot. That's
          * because we cannot change the mask anymore after the event source was created once, since the kernel has no
-         * API for that. Hence we need to subscribe to the maximum mask we ever might be interested in, and supress
+         * API for that. Hence we need to subscribe to the maximum mask we ever might be interested in, and suppress
          * events we don't care for client-side. */
 
         LIST_FOREACH(inotify.by_inode_data, s, d->event_sources) {
@@ -1843,11 +1589,10 @@ _public_ int sd_event_add_inotify(
                 sd_event_inotify_handler_t callback,
                 void *userdata) {
 
-        bool rm_inotify = false, rm_inode = false;
         struct inotify_data *inotify_data = NULL;
         struct inode_data *inode_data = NULL;
         _cleanup_close_ int fd = -1;
-        sd_event_source *s;
+        _cleanup_(source_freep) sd_event_source *s = NULL;
         struct stat st;
         int r;
 
@@ -1885,13 +1630,13 @@ _public_ int sd_event_add_inotify(
         /* Allocate an inotify object for this priority, and an inode object within it */
         r = event_make_inotify_data(e, SD_EVENT_PRIORITY_NORMAL, &inotify_data);
         if (r < 0)
-                goto fail;
-        rm_inotify = r > 0;
+                return r;
 
         r = event_make_inode_data(e, inotify_data, st.st_dev, st.st_ino, &inode_data);
-        if (r < 0)
-                goto fail;
-        rm_inode = r > 0;
+        if (r < 0) {
+                event_free_inotify_data(e, inotify_data);
+                return r;
+        }
 
         /* Keep the O_PATH fd around until the first iteration of the loop, so that we can still change the priority of
          * the event source, until then, for which we need the original inode. */
@@ -1904,72 +1649,45 @@ _public_ int sd_event_add_inotify(
         LIST_PREPEND(inotify.by_inode_data, inode_data->event_sources, s);
         s->inotify.inode_data = inode_data;
 
-        rm_inode = rm_inotify = false;
-
         /* Actually realize the watch now */
         r = inode_data_realize_watch(e, inode_data);
         if (r < 0)
-                goto fail;
+                return r;
 
         (void) sd_event_source_set_description(s, path);
 
         if (ret)
                 *ret = s;
+        TAKE_PTR(s);
 
         return 0;
-
-fail:
-        source_free(s);
-
-        if (rm_inode)
-                event_free_inode_data(e, inode_data);
-
-        if (rm_inotify)
-                event_free_inotify_data(e, inotify_data);
-
-        return r;
 }
 
-_public_ sd_event_source* sd_event_source_ref(sd_event_source *s) {
-
+static sd_event_source* event_source_free(sd_event_source *s) {
         if (!s)
                 return NULL;
 
-        assert(s->n_ref >= 1);
-        s->n_ref++;
-
-        return s;
-}
-
-_public_ sd_event_source* sd_event_source_unref(sd_event_source *s) {
+        /* Here's a special hack: when we are called from a
+         * dispatch handler we won't free the event source
+         * immediately, but we will detach the fd from the
+         * epoll. This way it is safe for the caller to unref
+         * the event source and immediately close the fd, but
+         * we still retain a valid event source object after
+         * the callback. */
 
-        if (!s)
-                return NULL;
-
-        assert(s->n_ref >= 1);
-        s->n_ref--;
-
-        if (s->n_ref <= 0) {
-                /* Here's a special hack: when we are called from a
-                 * dispatch handler we won't free the event source
-                 * immediately, but we will detach the fd from the
-                 * epoll. This way it is safe for the caller to unref
-                 * the event source and immediately close the fd, but
-                 * we still retain a valid event source object after
-                 * the callback. */
-
-                if (s->dispatching) {
-                        if (s->type == SOURCE_IO)
-                                source_io_unregister(s);
+        if (s->dispatching) {
+                if (s->type == SOURCE_IO)
+                        source_io_unregister(s);
 
-                        source_disconnect(s);
-                } else
-                        source_free(s);
-        }
+                source_disconnect(s);
+        } else
+                source_free(s);
 
         return NULL;
 }
 
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event_source, sd_event_source, event_source_free);
+
 _public_ int sd_event_source_set_description(sd_event_source *s, const char *description) {
         assert_return(s, -EINVAL);
         assert_return(!event_pid_changed(s->event), -ECHILD);
@@ -1980,9 +1698,11 @@ _public_ int sd_event_source_set_description(sd_event_source *s, const char *des
 _public_ int sd_event_source_get_description(sd_event_source *s, const char **description) {
         assert_return(s, -EINVAL);
         assert_return(description, -EINVAL);
-        assert_return(s->description, -ENXIO);
         assert_return(!event_pid_changed(s->event), -ECHILD);
 
+        if (!s->description)
+                return -ENXIO;
+
         *description = s->description;
         return 0;
 }
@@ -2234,11 +1954,11 @@ fail:
 
 _public_ int sd_event_source_get_enabled(sd_event_source *s, int *m) {
         assert_return(s, -EINVAL);
-        assert_return(m, -EINVAL);
         assert_return(!event_pid_changed(s->event), -ECHILD);
 
-        *m = s->enabled;
-        return 0;
+        if (m)
+                *m = s->enabled;
+        return s->enabled != SD_EVENT_OFF;
 }
 
 _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
@@ -3796,4 +3516,32 @@ _public_ int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_d
 
         return !!s->destroy_callback;
 }
+
+_public_ int sd_event_source_get_floating(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+
+        return s->floating;
+}
+
+_public_ int sd_event_source_set_floating(sd_event_source *s, int b) {
+        assert_return(s, -EINVAL);
+
+        if (s->floating == !!b)
+                return 0;
+
+        if (!s->event) /* Already disconnected */
+                return -ESTALE;
+
+        s->floating = b;
+
+        if (b) {
+                sd_event_source_ref(s);
+                sd_event_unref(s->event);
+        } else {
+                sd_event_ref(s->event);
+                sd_event_source_unref(s);
+        }
+
+        return 1;
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.c b/src/systemd/src/libsystemd/sd-id128/id128-util.c
index d4d668e8..6245e9df 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.c
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.c
@@ -1,8 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <fcntl.h>
 #include <unistd.h>
 
@@ -187,16 +186,13 @@ int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync) {
         return id128_write_fd(fd, f, id, do_sync);
 }
 
-void id128_hash_func(const void *p, struct siphash *state) {
-        siphash24_compress(p, 16, state);
+void id128_hash_func(const sd_id128_t *p, struct siphash *state) {
+        siphash24_compress(p, sizeof(sd_id128_t), state);
 }
 
-int id128_compare_func(const void *a, const void *b) {
+int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) {
         return memcmp(a, b, 16);
 }
 
-const struct hash_ops id128_hash_ops = {
-        .hash = id128_hash_func,
-        .compare = id128_compare_func,
-};
+DEFINE_HASH_OPS(id128_hash_ops, sd_id128_t, id128_hash_func, id128_compare_func);
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.h b/src/systemd/src/libsystemd/sd-id128/id128-util.h
index 44f159c0..65f14ab2 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.h
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.h
@@ -28,6 +28,6 @@ int id128_read(const char *p, Id128Format f, sd_id128_t *ret);
 int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync);
 int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync);
 
-void id128_hash_func(const void *p, struct siphash *state);
-int id128_compare_func(const void *a, const void *b) _pure_;
+void id128_hash_func(const sd_id128_t *p, struct siphash *state);
+int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_;
 extern const struct hash_ops id128_hash_ops;
diff --git a/src/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
index 483b2fe0..13a28291 100644
--- a/src/systemd/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
@@ -1,8 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <fcntl.h>
 #include <unistd.h>
 
@@ -21,7 +20,7 @@
 #include "util.h"
 
 #if 0 /* NM_IGNORED */
-_public_ char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]) {
+_public_ char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]) {
         unsigned n;
 
         assert_return(s, NULL);
@@ -277,7 +276,9 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
 
         assert_return(ret, -EINVAL);
 
-        r = acquire_random_bytes(&t, sizeof t, true);
+        /* We allow usage if x86-64 RDRAND here. It might not be trusted enough for keeping secrets, but it should be
+         * fine for UUIDS. */
+        r = genuine_random_bytes(&t, sizeof t, RANDOM_ALLOW_RDRAND);
         if (r < 0)
                 return r;
 
@@ -289,19 +290,15 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
         return 0;
 }
 
-_public_ int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
+static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) {
         _cleanup_(khash_unrefp) khash *h = NULL;
-        sd_id128_t m, result;
+        sd_id128_t result;
         const void *p;
         int r;
 
-        assert_return(ret, -EINVAL);
-
-        r = sd_id128_get_machine(&m);
-        if (r < 0)
-                return r;
+        assert(ret);
 
-        r = khash_new_with_key(&h, "hmac(sha256)", &m, sizeof(m));
+        r = khash_new_with_key(&h, "hmac(sha256)", &base, sizeof(base));
         if (r < 0)
                 return r;
 
@@ -319,4 +316,30 @@ _public_ int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *re
         *ret = make_v4_uuid(result);
         return 0;
 }
+
+_public_ int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
+        sd_id128_t id;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        r = sd_id128_get_machine(&id);
+        if (r < 0)
+                return r;
+
+        return get_app_specific(id, app_id, ret);
+}
+
+_public_ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
+        sd_id128_t id;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        r = sd_id128_get_boot(&id);
+        if (r < 0)
+                return r;
+
+        return get_app_specific(id, app_id, ret);
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/shared/dns-domain.c b/src/systemd/src/shared/dns-domain.c
index 048075f2..ebea861f 100644
--- a/src/systemd/src/shared/dns-domain.c
+++ b/src/systemd/src/shared/dns-domain.c
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include "nm-sd-adapt.h"
+#include "nm-sd-adapt-core.h"
 
 #if 0 /* NM_IGNORED */
 #if HAVE_LIBIDN2
@@ -21,6 +21,7 @@
 #include "dns-domain.h"
 #include "hashmap.h"
 #include "hexdecoct.h"
+#include "hostname-util.h"
 #include "in-addr-util.h"
 #include "macro.h"
 #include "parse-util.h"
@@ -28,9 +29,9 @@
 #include "strv.h"
 #include "utf8.h"
 
-int dns_label_unescape(const char **name, char *dest, size_t sz) {
+int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags) {
         const char *n;
-        char *d;
+        char *d, last_char = 0;
         int r = 0;
 
         assert(name);
@@ -40,13 +41,15 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
         d = dest;
 
         for (;;) {
-                if (*n == '.') {
-                        n++;
-                        break;
-                }
+                if (*n == 0 || *n == '.') {
+                        if (FLAGS_SET(flags, DNS_LABEL_LDH) && last_char == '-')
+                                /* Trailing dash */
+                                return -EINVAL;
 
-                if (*n == 0)
+                        if (*n == '.')
+                                n++;
                         break;
+                }
 
                 if (r >= DNS_LABEL_MAX)
                         return -EINVAL;
@@ -56,6 +59,8 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
 
                 if (*n == '\\') {
                         /* Escaped character */
+                        if (FLAGS_SET(flags, DNS_LABEL_NO_ESCAPES))
+                                return -EINVAL;
 
                         n++;
 
@@ -66,6 +71,10 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
                         else if (IN_SET(*n, '\\', '.')) {
                                 /* Escaped backslash or dot */
 
+                                if (FLAGS_SET(flags, DNS_LABEL_LDH))
+                                        return -EINVAL;
+
+                                last_char = *n;
                                 if (d)
                                         *(d++) = *n;
                                 sz--;
@@ -94,6 +103,11 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
                                 if (k > 255)
                                         return -EINVAL;
 
+                                if (FLAGS_SET(flags, DNS_LABEL_LDH) &&
+                                    !valid_ldh_char((char) k))
+                                        return -EINVAL;
+
+                                last_char = (char) k;
                                 if (d)
                                         *(d++) = (char) k;
                                 sz--;
@@ -107,6 +121,15 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
 
                         /* Normal character */
 
+                        if (FLAGS_SET(flags, DNS_LABEL_LDH)) {
+                                if (!valid_ldh_char(*n))
+                                        return -EINVAL;
+                                if (r == 0 && *n == '-')
+                                        /* Leading dash */
+                                        return -EINVAL;
+                        }
+
+                        last_char = *n;
                         if (d)
                                 *(d++) = *n;
                         sz--;
@@ -189,7 +212,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
                 terminal--;
         }
 
-        r = dns_label_unescape(&name, dest, sz);
+        r = dns_label_unescape(&name, dest, sz, 0);
         if (r < 0)
                 return r;
 
@@ -386,7 +409,7 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
 #endif
 #endif /* NM_IGNORED */
 
-int dns_name_concat(const char *a, const char *b, char **_ret) {
+int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_ret) {
         _cleanup_free_ char *ret = NULL;
         size_t n = 0, allocated = 0;
         const char *p;
@@ -403,7 +426,7 @@ int dns_name_concat(const char *a, const char *b, char **_ret) {
         for (;;) {
                 char label[DNS_LABEL_MAX];
 
-                r = dns_label_unescape(&p, label, sizeof(label));
+                r = dns_label_unescape(&p, label, sizeof label, flags);
                 if (r < 0)
                         return r;
                 if (r == 0) {
@@ -469,8 +492,7 @@ finish:
 }
 
 #if 0 /* NM_IGNORED */
-void dns_name_hash_func(const void *s, struct siphash *state) {
-        const char *p = s;
+void dns_name_hash_func(const char *p, struct siphash *state) {
         int r;
 
         assert(p);
@@ -478,7 +500,7 @@ void dns_name_hash_func(const void *s, struct siphash *state) {
         for (;;) {
                 char label[DNS_LABEL_MAX+1];
 
-                r = dns_label_unescape(&p, label, sizeof(label));
+                r = dns_label_unescape(&p, label, sizeof label, 0);
                 if (r < 0)
                         break;
                 if (r == 0)
@@ -493,15 +515,15 @@ void dns_name_hash_func(const void *s, struct siphash *state) {
         string_hash_func("", state);
 }
 
-int dns_name_compare_func(const void *a, const void *b) {
+int dns_name_compare_func(const char *a, const char *b) {
         const char *x, *y;
         int r, q;
 
         assert(a);
         assert(b);
 
-        x = (const char *) a + strlen(a);
-        y = (const char *) b + strlen(b);
+        x = a + strlen(a);
+        y = b + strlen(b);
 
         for (;;) {
                 char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
@@ -520,10 +542,7 @@ int dns_name_compare_func(const void *a, const void *b) {
         }
 }
 
-const struct hash_ops dns_name_hash_ops = {
-        .hash = dns_name_hash_func,
-        .compare = dns_name_compare_func
-};
+DEFINE_HASH_OPS(dns_name_hash_ops, char, dns_name_hash_func, dns_name_compare_func);
 
 int dns_name_equal(const char *x, const char *y) {
         int r, q;
@@ -534,11 +553,11 @@ int dns_name_equal(const char *x, const char *y) {
         for (;;) {
                 char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
 
-                r = dns_label_unescape(&x, la, sizeof(la));
+                r = dns_label_unescape(&x, la, sizeof la, 0);
                 if (r < 0)
                         return r;
 
-                q = dns_label_unescape(&y, lb, sizeof(lb));
+                q = dns_label_unescape(&y, lb, sizeof lb, 0);
                 if (q < 0)
                         return q;
 
@@ -565,14 +584,14 @@ int dns_name_endswith(const char *name, const char *suffix) {
         for (;;) {
                 char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
 
-                r = dns_label_unescape(&n, ln, sizeof(ln));
+                r = dns_label_unescape(&n, ln, sizeof ln, 0);
                 if (r < 0)
                         return r;
 
                 if (!saved_n)
                         saved_n = n;
 
-                q = dns_label_unescape(&s, ls, sizeof(ls));
+                q = dns_label_unescape(&s, ls, sizeof ls, 0);
                 if (q < 0)
                         return q;
 
@@ -603,13 +622,13 @@ int dns_name_startswith(const char *name, const char *prefix) {
         for (;;) {
                 char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX];
 
-                r = dns_label_unescape(&p, lp, sizeof(lp));
+                r = dns_label_unescape(&p, lp, sizeof lp, 0);
                 if (r < 0)
                         return r;
                 if (r == 0)
                         return true;
 
-                q = dns_label_unescape(&n, ln, sizeof(ln));
+                q = dns_label_unescape(&n, ln, sizeof ln, 0);
                 if (q < 0)
                         return q;
 
@@ -638,14 +657,14 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
                 if (!saved_before)
                         saved_before = n;
 
-                r = dns_label_unescape(&n, ln, sizeof(ln));
+                r = dns_label_unescape(&n, ln, sizeof ln, 0);
                 if (r < 0)
                         return r;
 
                 if (!saved_after)
                         saved_after = n;
 
-                q = dns_label_unescape(&s, ls, sizeof(ls));
+                q = dns_label_unescape(&s, ls, sizeof ls, 0);
                 if (q < 0)
                         return q;
 
@@ -668,7 +687,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
         /* Found it! Now generate the new name */
         prefix = strndupa(name, saved_before - name);
 
-        r = dns_name_concat(prefix, new_suffix, ret);
+        r = dns_name_concat(prefix, new_suffix, 0, ret);
         if (r < 0)
                 return r;
 
@@ -746,7 +765,7 @@ int dns_name_address(const char *p, int *family, union in_addr_union *address) {
                 for (i = 0; i < ELEMENTSOF(a); i++) {
                         char label[DNS_LABEL_MAX+1];
 
-                        r = dns_label_unescape(&p, label, sizeof(label));
+                        r = dns_label_unescape(&p, label, sizeof label, 0);
                         if (r < 0)
                                 return r;
                         if (r == 0)
@@ -783,7 +802,7 @@ int dns_name_address(const char *p, int *family, union in_addr_union *address) {
                         char label[DNS_LABEL_MAX+1];
                         int x, y;
 
-                        r = dns_label_unescape(&p, label, sizeof(label));
+                        r = dns_label_unescape(&p, label, sizeof label, 0);
                         if (r <= 0)
                                 return r;
                         if (r != 1)
@@ -792,7 +811,7 @@ int dns_name_address(const char *p, int *family, union in_addr_union *address) {
                         if (x < 0)
                                 return -EINVAL;
 
-                        r = dns_label_unescape(&p, label, sizeof(label));
+                        r = dns_label_unescape(&p, label, sizeof label, 0);
                         if (r <= 0)
                                 return r;
                         if (r != 1)
@@ -861,7 +880,7 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo
                  * dns_label_unescape() returns 0 when it hits the end
                  * of the domain name, which we rely on here to encode
                  * the trailing NUL byte. */
-                r = dns_label_unescape(&domain, (char *) out, len);
+                r = dns_label_unescape(&domain, (char *) out, len, 0);
                 if (r < 0)
                         return r;
 
@@ -929,7 +948,7 @@ bool dns_srv_type_is_valid(const char *name) {
 
                 /* This more or less implements RFC 6335, Section 5.1 */
 
-                r = dns_label_unescape(&name, label, sizeof(label));
+                r = dns_label_unescape(&name, label, sizeof label, 0);
                 if (r < 0)
                         return false;
                 if (r == 0)
@@ -989,7 +1008,7 @@ int dns_service_join(const char *name, const char *type, const char *domain, cha
                 return -EINVAL;
 
         if (!name)
-                return dns_name_concat(type, domain, ret);
+                return dns_name_concat(type, domain, 0, ret);
 
         if (!dns_service_name_is_valid(name))
                 return -EINVAL;
@@ -998,11 +1017,11 @@ int dns_service_join(const char *name, const char *type, const char *domain, cha
         if (r < 0)
                 return r;
 
-        r = dns_name_concat(type, domain, &n);
+        r = dns_name_concat(type, domain, 0, &n);
         if (r < 0)
                 return r;
 
-        return dns_name_concat(escaped, n, ret);
+        return dns_name_concat(escaped, n, 0, ret);
 }
 
 static bool dns_service_name_label_is_valid(const char *label, size_t n) {
@@ -1027,7 +1046,7 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
         assert(joined);
 
         /* Get first label from the full name */
-        an = dns_label_unescape(&p, a, sizeof(a));
+        an = dns_label_unescape(&p, a, sizeof(a), 0);
         if (an < 0)
                 return an;
 
@@ -1035,7 +1054,7 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
                 x++;
 
                 /* If there was a first label, try to get the second one */
-                bn = dns_label_unescape(&p, b, sizeof(b));
+                bn = dns_label_unescape(&p, b, sizeof(b), 0);
                 if (bn < 0)
                         return bn;
 
@@ -1044,7 +1063,7 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
 
                         /* If there was a second label, try to get the third one */
                         q = p;
-                        cn = dns_label_unescape(&p, c, sizeof(c));
+                        cn = dns_label_unescape(&p, c, sizeof(c), 0);
                         if (cn < 0)
                                 return cn;
 
@@ -1094,7 +1113,7 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
         d = joined;
 
 finish:
-        r = dns_name_normalize(d, &domain);
+        r = dns_name_normalize(d, 0, &domain);
         if (r < 0)
                 return r;
 
@@ -1110,7 +1129,7 @@ finish:
         return 0;
 }
 
-static int dns_name_build_suffix_table(const char *name, const char*table[]) {
+static int dns_name_build_suffix_table(const char *name, const char *table[]) {
         const char *p;
         unsigned n = 0;
         int r;
@@ -1239,12 +1258,12 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
                 }
 
                 x = a_labels[n - 1 - k];
-                r = dns_label_unescape(&x, la, sizeof(la));
+                r = dns_label_unescape(&x, la, sizeof la, 0);
                 if (r < 0)
                         return r;
 
                 y = b_labels[m - 1 - k];
-                q = dns_label_unescape(&y, lb, sizeof(lb));
+                q = dns_label_unescape(&y, lb, sizeof lb, 0);
                 if (q < 0)
                         return q;
 
@@ -1295,7 +1314,7 @@ int dns_name_apply_idna(const char *name, char **ret) {
 
         log_debug("idn2_lookup_u8(\"%s\") failed: %d/%s", name, r, idn2_strerror(r));
         if (r == IDN2_2HYPHEN)
-                /* The name has two hypens — forbidden by IDNA2008 in some cases */
+                /* The name has two hyphens — forbidden by IDNA2008 in some cases */
                 return 0;
         if (IN_SET(r, IDN2_TOO_BIG_DOMAIN, IDN2_TOO_BIG_LABEL))
                 return -ENOSPC;
@@ -1312,13 +1331,13 @@ int dns_name_apply_idna(const char *name, char **ret) {
         for (;;) {
                 char label[DNS_LABEL_MAX];
 
-                r = dns_label_unescape(&name, label, sizeof(label));
+                r = dns_label_unescape(&name, label, sizeof label, 0);
                 if (r < 0)
                         return r;
                 if (r == 0)
                         break;
 
-                q = dns_label_apply_idna(label, r, label, sizeof(label));
+                q = dns_label_apply_idna(label, r, label, sizeof label);
                 if (q < 0)
                         return q;
                 if (q > 0)
diff --git a/src/systemd/src/shared/dns-domain.h b/src/systemd/src/shared/dns-domain.h
index 95f4069c..17db7c52 100644
--- a/src/systemd/src/shared/dns-domain.h
+++ b/src/systemd/src/shared/dns-domain.h
@@ -1,7 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-#include <errno.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
@@ -24,13 +23,18 @@
 /* Maximum number of labels per valid hostname */
 #define DNS_N_LABELS_MAX 127
 
-int dns_label_unescape(const char **name, char *dest, size_t sz);
+typedef enum DNSLabelFlags {
+        DNS_LABEL_LDH        = 1 << 0, /* Follow the "LDH" rule — only letters, digits, and internal hyphens. */
+        DNS_LABEL_NO_ESCAPES = 1 << 1, /* Do not treat backslashes specially */
+} DNSLabelFlags;
+
+int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags);
 int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz);
 int dns_label_escape(const char *p, size_t l, char *dest, size_t sz);
 int dns_label_escape_new(const char *p, size_t l, char **ret);
 
 static inline int dns_name_parent(const char **name) {
-        return dns_label_unescape(name, NULL, DNS_LABEL_MAX);
+        return dns_label_unescape(name, NULL, DNS_LABEL_MAX, 0);
 }
 
 #if 0 /* NM_IGNORED */
@@ -40,18 +44,29 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
 #endif
 #endif /* NM_IGNORED */
 
-int dns_name_concat(const char *a, const char *b, char **ret);
+int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **ret);
 
-static inline int dns_name_normalize(const char *s, char **ret) {
+static inline int dns_name_normalize(const char *s, DNSLabelFlags flags, char **ret) {
         /* dns_name_concat() normalizes as a side-effect */
-        return dns_name_concat(s, NULL, ret);
+        return dns_name_concat(s, NULL, flags, ret);
 }
 
 static inline int dns_name_is_valid(const char *s) {
         int r;
 
         /* dns_name_normalize() verifies as a side effect */
-        r = dns_name_normalize(s, NULL);
+        r = dns_name_normalize(s, 0, NULL);
+        if (r == -EINVAL)
+                return 0;
+        if (r < 0)
+                return r;
+        return 1;
+}
+
+static inline int dns_name_is_valid_ldh(const char *s) {
+        int r;
+
+        r = dns_name_concat(s, NULL, DNS_LABEL_LDH|DNS_LABEL_NO_ESCAPES, NULL);
         if (r == -EINVAL)
                 return 0;
         if (r < 0)
@@ -59,8 +74,8 @@ static inline int dns_name_is_valid(const char *s) {
         return 1;
 }
 
-void dns_name_hash_func(const void *s, struct siphash *state);
-int dns_name_compare_func(const void *a, const void *b);
+void dns_name_hash_func(const char *s, struct siphash *state);
+int dns_name_compare_func(const char *a, const char *b);
 extern const struct hash_ops dns_name_hash_ops;
 
 int dns_name_between(const char *a, const char *b, const char *c);
diff --git a/src/systemd/src/systemd/_sd-common.h b/src/systemd/src/systemd/_sd-common.h
index 4742b2e7..b3ee7bbc 100644
--- a/src/systemd/src/systemd/_sd-common.h
+++ b/src/systemd/src/systemd/_sd-common.h
@@ -23,24 +23,26 @@
 #  error "Do not include _sd-common.h directly; it is a private header."
 #endif
 
+typedef void (*_sd_destroy_t)(void *userdata);
+
 #ifndef _sd_printf_
 #  if __GNUC__ >= 4
-#    define _sd_printf_(a,b) __attribute__ ((format (printf, a, b)))
+#    define _sd_printf_(a,b) __attribute__((__format__(printf, a, b)))
 #  else
 #    define _sd_printf_(a,b)
 #  endif
 #endif
 
 #ifndef _sd_sentinel_
-#  define _sd_sentinel_ __attribute__((sentinel))
+#  define _sd_sentinel_ __attribute__((__sentinel__))
 #endif
 
 #ifndef _sd_packed_
-#  define _sd_packed_ __attribute__((packed))
+#  define _sd_packed_ __attribute__((__packed__))
 #endif
 
 #ifndef _sd_pure_
-#  define _sd_pure_ __attribute__((pure))
+#  define _sd_pure_ __attribute__((__pure__))
 #endif
 
 #ifndef _SD_STRINGIFY
@@ -70,6 +72,14 @@
 #  endif
 #endif
 
+#ifndef _SD_ARRAY_STATIC
+#  if __STDC_VERSION__ >= 199901L
+#    define _SD_ARRAY_STATIC static
+#  else
+#    define _SD_ARRAY_STATIC
+#  endif
+#endif
+
 #define _SD_DEFINE_POINTER_CLEANUP_FUNC(type, func)             \
         static __inline__ void func##p(type **p) {              \
                 if (*p)                                         \
diff --git a/src/systemd/src/systemd/sd-dhcp-client.h b/src/systemd/src/systemd/sd-dhcp-client.h
index e3885520..bd0d429d 100644
--- a/src/systemd/src/systemd/sd-dhcp-client.h
+++ b/src/systemd/src/systemd/sd-dhcp-client.h
@@ -23,6 +23,7 @@
 #include <net/ethernet.h>
 #include <netinet/in.h>
 #include <sys/types.h>
+#include <stdbool.h>
 
 #include "sd-dhcp-lease.h"
 #include "sd-event.h"
@@ -127,12 +128,14 @@ int sd_dhcp_client_set_client_id(
                 size_t data_len);
 int sd_dhcp_client_set_iaid_duid(
                 sd_dhcp_client *client,
+                bool iaid_set,
                 uint32_t iaid,
                 uint16_t duid_type,
                 const void *duid,
                 size_t duid_len);
 int sd_dhcp_client_set_iaid_duid_llt(
                 sd_dhcp_client *client,
+                bool iaid_set,
                 uint32_t iaid,
                 uint64_t llt_time);
 int sd_dhcp_client_set_duid(
diff --git a/src/systemd/src/systemd/sd-dhcp-lease.h b/src/systemd/src/systemd/sd-dhcp-lease.h
index 2a60145f..d299c791 100644
--- a/src/systemd/src/systemd/sd-dhcp-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp-lease.h
@@ -39,7 +39,7 @@ int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1);
 int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2);
 int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr);
 int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr);
-int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, struct in_addr *addr);
+int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr);
 int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr);
 int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr);
@@ -57,6 +57,7 @@ int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone);
 int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination);
 int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length);
 int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway);
+int sd_dhcp_route_get_option(sd_dhcp_route *route);
 
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref);
 
diff --git a/src/systemd/src/systemd/sd-dhcp6-client.h b/src/systemd/src/systemd/sd-dhcp6-client.h
index fa36dca9..43d38f5c 100644
--- a/src/systemd/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/src/systemd/sd-dhcp6-client.h
@@ -21,7 +21,6 @@
 
 #include <inttypes.h>
 #include <net/ethernet.h>
-#include <stdbool.h>
 #include <sys/types.h>
 
 #include "sd-dhcp6-lease.h"
@@ -120,8 +119,15 @@ 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_get_prefix_delegation(sd_dhcp6_client *client,
+                                          int *delegation);
 int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client,
-                                          bool delegation);
+                                          int delegation);
+int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client,
+                                        int *request);
+int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client,
+                                        int request);
+int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id);
 
 int sd_dhcp6_client_get_lease(
                 sd_dhcp6_client *client,
diff --git a/src/systemd/src/systemd/sd-event.h b/src/systemd/src/systemd/sd-event.h
index eb35b834..787a12f2 100644
--- a/src/systemd/src/systemd/sd-event.h
+++ b/src/systemd/src/systemd/sd-event.h
@@ -33,7 +33,8 @@
   - Supports event source prioritization
   - Scales better with a large number of time events because it does not require one timerfd each
   - Automatically tries to coalesce timer events system-wide
-  - Handles signals and child PIDs
+  - Handles signals, child PIDs, inotify events
+  - Supports systemd-style automatic watchdog event generation
 */
 
 _SD_BEGIN_DECLARATIONS;
@@ -76,7 +77,7 @@ typedef int (*sd_event_child_handler_t)(sd_event_source *s, const siginfo_t *si,
 typedef void* sd_event_child_handler_t;
 #endif
 typedef int (*sd_event_inotify_handler_t)(sd_event_source *s, const struct inotify_event *event, void *userdata);
-typedef void (*sd_event_destroy_t)(void *userdata);
+typedef _sd_destroy_t sd_event_destroy_t;
 
 int sd_event_default(sd_event **e);
 
@@ -142,6 +143,8 @@ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid);
 int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret);
 int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback);
 int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret);
+int sd_event_source_get_floating(sd_event_source *s);
+int sd_event_source_set_floating(sd_event_source *s, int b);
 
 /* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref);
diff --git a/src/systemd/src/systemd/sd-id128.h b/src/systemd/src/systemd/sd-id128.h
index 143a0ffb..bdf88ed5 100644
--- a/src/systemd/src/systemd/sd-id128.h
+++ b/src/systemd/src/systemd/sd-id128.h
@@ -35,24 +35,26 @@ union sd_id128 {
 
 #define SD_ID128_STRING_MAX 33
 
-char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]);
+char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]);
 int sd_id128_from_string(const char *s, sd_id128_t *ret);
 
 int sd_id128_randomize(sd_id128_t *ret);
 
 int sd_id128_get_machine(sd_id128_t *ret);
-int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret);
 int sd_id128_get_boot(sd_id128_t *ret);
 int sd_id128_get_invocation(sd_id128_t *ret);
 
-#define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \
-        ((const sd_id128_t) { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \
-                                   0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }})
+int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret);
+int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret);
 
 #define SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \
         { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \
                      0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }}
 
+#define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \
+        ((const sd_id128_t) SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15))
+
+
 /* Note that SD_ID128_FORMAT_VAL will evaluate the passed argument 16
  * times. It is hence not a good idea to call this macro with an
  * expensive function as parameter or an expression with side
@@ -108,7 +110,12 @@ _sd_pure_ static __inline__ int sd_id128_is_null(sd_id128_t a) {
         return a.qwords[0] == 0 && a.qwords[1] == 0;
 }
 
+_sd_pure_ static __inline__ int sd_id128_is_allf(sd_id128_t a) {
+        return a.qwords[0] == UINT64_C(0xFFFFFFFFFFFFFFFF) && a.qwords[1] == UINT64_C(0xFFFFFFFFFFFFFFFF);
+}
+
 #define SD_ID128_NULL ((const sd_id128_t) { .qwords = { 0, 0 }})
+#define SD_ID128_ALLF ((const sd_id128_t) { .qwords = { UINT64_C(0xFFFFFFFFFFFFFFFF), UINT64_C(0xFFFFFFFFFFFFFFFF) }})
 
 _SD_END_DECLARATIONS;
 
diff --git a/src/systemd/src/systemd/sd-lldp.h b/src/systemd/src/systemd/sd-lldp.h
index d650794c..bf3afadc 100644
--- a/src/systemd/src/systemd/sd-lldp.h
+++ b/src/systemd/src/systemd/sd-lldp.h
@@ -27,7 +27,7 @@
 
 _SD_BEGIN_DECLARATIONS;
 
-/* IEEE 802.3AB Clause 9: TLV Types */
+/* IEEE 802.1AB-2009 Clause 8: TLV Types */
 enum {
         SD_LLDP_TYPE_END                 = 0,
         SD_LLDP_TYPE_CHASSIS_ID          = 1,
@@ -41,7 +41,7 @@ enum {
         SD_LLDP_TYPE_PRIVATE             = 127,
 };
 
-/* IEEE 802.3AB Clause 9.5.2: Chassis subtypes */
+/* IEEE 802.1AB-2009 Clause 8.5.2: Chassis subtypes */
 enum {
         SD_LLDP_CHASSIS_SUBTYPE_RESERVED            = 0,
         SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT   = 1,
@@ -53,7 +53,7 @@ enum {
         SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED    = 7,
 };
 
-/* IEEE 802.3AB Clause 9.5.3: Port subtype */
+/* IEEE 802.1AB-2009 Clause 8.5.3: Port subtype */
 enum {
         SD_LLDP_PORT_SUBTYPE_RESERVED         = 0,
         SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS  = 1,
@@ -65,6 +65,7 @@ enum {
         SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7,
 };
 
+/* IEEE 802.1AB-2009 Clause 8.5.8: System capabilities */
 enum {
         SD_LLDP_SYSTEM_CAPABILITIES_OTHER    = 1 << 0,
         SD_LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1,
@@ -95,6 +96,7 @@ enum {
 #define SD_LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 }
 #define SD_LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f }
 
+/* IEEE 802.1AB-2009 Annex E */
 enum {
         SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID          = 1,
         SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID = 2,
@@ -105,14 +107,24 @@ enum {
         SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION      = 7,
 };
 
+/* IEEE 802.1AB-2009 Annex F */
+enum {
+        SD_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS = 1,
+        SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI         = 2,
+        SD_LLDP_OUI_802_3_SUBTYPE_LINK_AGGREGATION      = 3,
+        SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE    = 4,
+};
+
 typedef struct sd_lldp sd_lldp;
 typedef struct sd_lldp_neighbor sd_lldp_neighbor;
 
 typedef enum sd_lldp_event {
-        SD_LLDP_EVENT_ADDED     = 'a',
-        SD_LLDP_EVENT_REMOVED   = 'r',
-        SD_LLDP_EVENT_UPDATED   = 'u',
-        SD_LLDP_EVENT_REFRESHED = 'f',
+        SD_LLDP_EVENT_ADDED,
+        SD_LLDP_EVENT_REMOVED,
+        SD_LLDP_EVENT_UPDATED,
+        SD_LLDP_EVENT_REFRESHED,
+        _SD_LLDP_EVENT_MAX,
+        _SD_LLDP_EVENT_INVALID = -1,
 } sd_lldp_event;
 
 typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata);
@@ -166,8 +178,8 @@ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n);
 int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n);
 int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type);
 int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type);
-int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], uint8_t *subtype);
-int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[3], uint8_t subtype);
+int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype);
+int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype);
 int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
 
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp, sd_lldp_unref);
diff --git a/src/systemd/src/systemd/sd-ndisc.h b/src/systemd/src/systemd/sd-ndisc.h
index 6b6249ca..d1bee343 100644
--- a/src/systemd/src/systemd/sd-ndisc.h
+++ b/src/systemd/src/systemd/sd-ndisc.h
@@ -55,8 +55,10 @@ typedef struct sd_ndisc sd_ndisc;
 typedef struct sd_ndisc_router sd_ndisc_router;
 
 typedef enum sd_ndisc_event {
-        SD_NDISC_EVENT_TIMEOUT = 't',
-        SD_NDISC_EVENT_ROUTER  = 'r',
+        SD_NDISC_EVENT_TIMEOUT,
+        SD_NDISC_EVENT_ROUTER,
+        _SD_NDISC_EVENT_MAX,
+        _SD_NDISC_EVENT_INVALID = -1,
 } sd_ndisc_event;
 
 typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata);
diff --git a/src/tests/config/NetworkManager-warn.conf b/src/tests/config/NetworkManager-warn.conf
new file mode 100644
index 00000000..80df7c52
--- /dev/null
+++ b/src/tests/config/NetworkManager-warn.conf
@@ -0,0 +1,26 @@
+[main]
+dhcp=dhclient
+plugin=foo,bar,baz
+no-auto-default=11:11:11:11:11:11
+rc-managed=unmanaged
+dns=none
+
+[logging]
+level=INFO
+
+[connectivity]
+uri=http://example.com
+interval=100
+response=Hello
+audit=true
+
+[connection]
+ipv4.route-metric=50
+ipv4.addresses=1.2.3.4
+ipv4.dad-timeout=100
+
+[connection-wifi]
+match-device=type:wifi
+wifi.powersave=2
+ipv6.ip6-privacy=1
+wifi.tx-power=99
diff --git a/src/tests/config/NetworkManager.conf b/src/tests/config/NetworkManager.conf
index da7b1fd4..a447b6d6 100644
--- a/src/tests/config/NetworkManager.conf
+++ b/src/tests/config/NetworkManager.conf
@@ -19,8 +19,8 @@ extra-key=some value
 [connection]
 ipv4.route-metric=50
 ipv6.ip6_privacy=0
-dummy.test1=no
-dummy.test2=no
+ethernet.mtu=1400
+ipv4.dns-priority=60
 
 ord.key00=A-0.0.00
 ord.key01=A-0.0.01
@@ -37,7 +37,7 @@ ord.key09=A-0.0.09
 match-device=mac:00:00:00:00:00:51
 stop-match=yes
 ipv4.route-metric=51
-dummy.test1=yes
+ethernet.mtu=9000
 
 [connection.dev52]
 match-device=mac:00:00:00:00:00:52
diff --git a/src/tests/config/meson.build b/src/tests/config/meson.build
index fd6c89b7..f65f90bb 100644
--- a/src/tests/config/meson.build
+++ b/src/tests/config/meson.build
@@ -2,7 +2,7 @@ test_unit = 'test-config'
 
 sources = files(
   'nm-test-device.c',
-  'test-config.c'
+  'test-config.c',
 )
 
 test_config_dir = meson.current_source_dir()
@@ -16,5 +16,6 @@ exe = executable(
 test(
   'config/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/tests/config/nm-test-device.c b/src/tests/config/nm-test-device.c
index 49631583..3a8ef266 100644
--- a/src/tests/config/nm-test-device.c
+++ b/src/tests/config/nm-test-device.c
@@ -22,8 +22,6 @@
 
 #include "nm-test-device.h"
 
-#include <string.h>
-
 #include "devices/nm-device-private.h"
 #include "nm-utils.h"
 
@@ -77,6 +75,7 @@ nm_test_device_new (const char *hwaddr)
 	return g_object_new (NM_TYPE_TEST_DEVICE,
 	                     NM_DEVICE_IFACE, "dummy",
 	                     NM_DEVICE_PERM_HW_ADDRESS, hwaddr,
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_ETHERNET,
 	                     NULL);
 }
 
diff --git a/src/tests/config/test-config.c b/src/tests/config/test-config.c
index 20a05df1..10084b7f 100644
--- a/src/tests/config/test-config.c
+++ b/src/tests/config/test-config.c
@@ -206,20 +206,20 @@ test_config_simple (void)
 	g_assert_cmpstr (value, ==, "52");
 	g_free (value);
 
-	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test1", dev51);
-	g_assert_cmpstr (value, ==, "yes");
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ethernet.mtu", dev51);
+	g_assert_cmpstr (value, ==, "9000");
 	g_free (value);
 
-	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test1", dev50);
-	g_assert_cmpstr (value, ==, "no");
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ethernet.mtu", dev50);
+	g_assert_cmpstr (value, ==, "1400");
 	g_free (value);
 
-	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test2", dev51);
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv4.dns-priority", dev51);
 	g_assert_cmpstr (value, ==, NULL);
 	g_free (value);
 
-	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "dummy.test2", dev50);
-	g_assert_cmpstr (value, ==, "no");
+	value = nm_config_data_get_connection_default (nm_config_get_data_orig (config), "ipv4.dns-priority", dev50);
+	g_assert_cmpstr (value, ==, "60");
 	g_free (value);
 }
 
@@ -506,17 +506,17 @@ test_config_confdir (void)
 		gs_free char *_value = nm_config_data_get_connection_default (nm_config_get_data_orig (xconfig), (xname), NULL); \
 		g_assert_cmpstr (_value, ==, (xvalue)); \
 	} G_STMT_END
-	ASSERT_GET_CONN_DEFAULT (config, "ord.key00", "A-0.0.00");
-	ASSERT_GET_CONN_DEFAULT (config, "ord.key01", "A-0.3.01");
-	ASSERT_GET_CONN_DEFAULT (config, "ord.key02", "A-0.2.02");
-	ASSERT_GET_CONN_DEFAULT (config, "ord.key03", "A-0.1.03");
-	ASSERT_GET_CONN_DEFAULT (config, "ord.key04", "B-1.3.04");
-	ASSERT_GET_CONN_DEFAULT (config, "ord.key05", "B-1.2.05");
-	ASSERT_GET_CONN_DEFAULT (config, "ord.key06", "B-1.1.06");
-	ASSERT_GET_CONN_DEFAULT (config, "ord.key07", "C-2.3.07");
-	ASSERT_GET_CONN_DEFAULT (config, "ord.key08", "C-2.2.08");
-	ASSERT_GET_CONN_DEFAULT (config, "ord.key09", "C-2.1.09");
-	ASSERT_GET_CONN_DEFAULT (config, "ord.ovw01", "C-0.1.ovw01");
+	ASSERT_GET_CONN_DEFAULT (config, NM_CON_DEFAULT ("ord.key00"), "A-0.0.00");
+	ASSERT_GET_CONN_DEFAULT (config, NM_CON_DEFAULT ("ord.key01"), "A-0.3.01");
+	ASSERT_GET_CONN_DEFAULT (config, NM_CON_DEFAULT ("ord.key02"), "A-0.2.02");
+	ASSERT_GET_CONN_DEFAULT (config, NM_CON_DEFAULT ("ord.key03"), "A-0.1.03");
+	ASSERT_GET_CONN_DEFAULT (config, NM_CON_DEFAULT ("ord.key04"), "B-1.3.04");
+	ASSERT_GET_CONN_DEFAULT (config, NM_CON_DEFAULT ("ord.key05"), "B-1.2.05");
+	ASSERT_GET_CONN_DEFAULT (config, NM_CON_DEFAULT ("ord.key06"), "B-1.1.06");
+	ASSERT_GET_CONN_DEFAULT (config, NM_CON_DEFAULT ("ord.key07"), "C-2.3.07");
+	ASSERT_GET_CONN_DEFAULT (config, NM_CON_DEFAULT ("ord.key08"), "C-2.2.08");
+	ASSERT_GET_CONN_DEFAULT (config, NM_CON_DEFAULT ("ord.key09"), "C-2.1.09");
+	ASSERT_GET_CONN_DEFAULT (config, NM_CON_DEFAULT ("ord.ovw01"), "C-0.1.ovw01");
 
 	value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".1", "val1", NM_CONFIG_GET_VALUE_NONE);
 	g_assert_cmpstr (value, ==, "a,c");
@@ -552,6 +552,36 @@ test_config_confdir_parse_error (void)
 	g_clear_error (&error);
 }
 
+static void
+test_config_warnings (void)
+{
+	gs_unref_object NMConfig *config = NULL;
+	const char *const *warnings;
+
+	config = setup_config (NULL, TEST_DIR "/NetworkManager-warn.conf", "", NULL, "/no/such/dir", "", NULL);
+
+	warnings = nm_config_get_warnings (config);
+
+#define check_warning(str, group, key) \
+	{ \
+		gs_free char *expected = NULL; \
+		\
+		expected = g_strdup_printf ("unknown key '%s' in section [%s] of file '" TEST_DIR "/NetworkManager-warn.conf'", \
+		                            key, group); \
+		g_assert_cmpstr (str, ==, expected); \
+	}
+
+	g_assert (warnings);
+	g_assert_cmpint (g_strv_length ((char **) warnings), ==, 5);
+	check_warning (warnings[0], "main", "plugin");
+	check_warning (warnings[1], "main", "rc-managed");
+	check_warning (warnings[2], "connectivity", "audit");
+	check_warning (warnings[3], "connection-wifi", "wifi.tx-power");
+	check_warning (warnings[4], "connection", "ipv4.addresses");
+
+#undef check_warning
+}
+
 /*****************************************************************************/
 
 typedef void (*TestSetValuesUserSetFcn) (NMConfig *config, gboolean is_user, GKeyFile *keyfile_user, NMConfigChangeFlags *out_expected_changes);
@@ -622,7 +652,7 @@ _set_values_user (NMConfig *config,
 	else
 		NMTST_EXPECT_NM_INFO ("config: signal: SIGHUP (no changes from disk)*");
 
-	nm_config_reload (config, NM_CONFIG_CHANGE_CAUSE_SIGHUP);
+	nm_config_reload (config, NM_CONFIG_CHANGE_CAUSE_SIGHUP, FALSE);
 
 	g_test_assert_expected_messages ();
 
@@ -926,15 +956,15 @@ test_config_signal (void)
 
 	expected = NM_CONFIG_CHANGE_CAUSE_SIGUSR1;
 	NMTST_EXPECT_NM_INFO ("config: signal: SIGUSR1");
-	nm_config_reload (config, expected);
+	nm_config_reload (config, expected, FALSE);
 
 	expected = NM_CONFIG_CHANGE_CAUSE_SIGUSR2;
 	NMTST_EXPECT_NM_INFO ("config: signal: SIGUSR2");
-	nm_config_reload (config, expected);
+	nm_config_reload (config, expected, FALSE);
 
 	expected = NM_CONFIG_CHANGE_CAUSE_SIGHUP;
 	NMTST_EXPECT_NM_INFO ("config: signal: SIGHUP (no changes from disk)*");
-	nm_config_reload (config, expected);
+	nm_config_reload (config, expected, FALSE);
 
 	/* test with subscribing two signals...
 	 *
@@ -946,7 +976,7 @@ test_config_signal (void)
 	                  &expected);
 	expected = NM_CONFIG_CHANGE_CAUSE_SIGUSR2;
 	NMTST_EXPECT_NM_INFO ("config: signal: SIGUSR2");
-	nm_config_reload (config, NM_CONFIG_CHANGE_CAUSE_SIGUSR2);
+	nm_config_reload (config, NM_CONFIG_CHANGE_CAUSE_SIGUSR2, FALSE);
 	g_signal_handlers_disconnect_by_func (config, _test_signal_config_changed_cb2, &expected);
 
 	g_signal_handlers_disconnect_by_func (config, _test_signal_config_changed_cb, &expected);
@@ -1064,6 +1094,7 @@ main (int argc, char **argv)
 	g_test_add_func ("/config/no-auto-default", test_config_no_auto_default);
 	g_test_add_func ("/config/confdir", test_config_confdir);
 	g_test_add_func ("/config/confdir-parse-error", test_config_confdir_parse_error);
+	g_test_add_func ("/config/warnings", test_config_warnings);
 
 	g_test_add_func ("/config/set-values", test_config_set_values);
 	g_test_add_func ("/config/global-dns", test_config_global_dns);
diff --git a/src/tests/meson.build b/src/tests/meson.build
index 430ffe43..153128f2 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -7,20 +7,21 @@ test_units = [
   'test-ip6-config',
   'test-dcb',
   'test-wired-defname',
-  'test-utils'
+  'test-utils',
 ]
 
 foreach test_unit: test_units
   exe = executable(
     test_unit,
     test_unit + '.c',
-    dependencies: test_nm_dep
+    dependencies: test_nm_dep,
   )
 
   test(
     'src/' + test_unit,
     test_script,
-    args: test_args + [exe.full_path()]
+    args: test_args + [exe.full_path()],
+    timeout: default_test_timeout,
   )
 endforeach
 
@@ -37,11 +38,14 @@ exe = executable(
   include_directories: src_inc,
   dependencies: nm_core_dep,
   c_args: cflags,
-  link_with: libsystemd_nm
+  link_with: [
+    libnm_systemd_core,
+    libnm_systemd_shared,
+  ],
 )
 
 test(
   'src/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
 )
diff --git a/src/tests/test-dcb.c b/src/tests/test-dcb.c
index 2ab0f890..6291f254 100644
--- a/src/tests/test-dcb.c
+++ b/src/tests/test-dcb.c
@@ -20,8 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "nm-dcb.h"
 
 #include "nm-test-utils-core.h"
diff --git a/src/tests/test-general-with-expect.c b/src/tests/test-general-with-expect.c
index ba8e3ce4..8339fa5d 100644
--- a/src/tests/test-general-with-expect.c
+++ b/src/tests/test-general-with-expect.c
@@ -20,8 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-#include <errno.h>
 #include <time.h>
 #include <netinet/ether.h>
 #include <sys/types.h>
@@ -42,7 +40,8 @@ test_nm_utils_monotonic_timestamp_as_boottime (void)
 	clockid_t clockid;
 	guint i;
 
-	if (clock_gettime (CLOCK_BOOTTIME, &tp) != 0 && errno == EINVAL)
+	if (   clock_gettime (CLOCK_BOOTTIME, &tp) != 0
+	    && errno == EINVAL)
 		clockid = CLOCK_MONOTONIC;
 	else
 		clockid = CLOCK_BOOTTIME;
diff --git a/src/tests/test-general.c b/src/tests/test-general.c
index 9dce9435..0dee566e 100644
--- a/src/tests/test-general.c
+++ b/src/tests/test-general.c
@@ -20,8 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-#include <errno.h>
 #include <net/if.h>
 #include <byteswap.h>
 
@@ -31,7 +29,7 @@
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
 #include "nm-core-utils.h"
-#include "systemd/nm-sd-utils.h"
+#include "systemd/nm-sd-utils-core.h"
 
 #include "dns/nm-dns-manager.h"
 #include "nm-connectivity.h"
@@ -352,13 +350,13 @@ _match_connection (GSList *connections,
                    gint64 default_v4_metric,
                    gint64 default_v6_metric)
 {
-	NMConnection **list;
+	gs_free NMConnection **list = NULL;
 	guint i, len;
 
 	len = g_slist_length (connections);
 	g_assert (len < 10);
 
-	list = g_alloca ((len + 1) * sizeof (NMConnection *));
+	list = g_malloc ((len + 1) * sizeof (NMConnection *));
 	for (i = 0; i < len; i++, connections = connections->next) {
 		g_assert (connections);
 		g_assert (connections->data);
@@ -1116,7 +1114,10 @@ _test_match_spec_device (const GSList *specs, const char *match_str)
 }
 
 static void
-_do_test_match_spec_device (const char *spec_str, const char **matches, const char **no_matches, const char **neg_matches)
+_do_test_match_spec_device (const char *spec_str,
+                            const char *const *matches,
+                            const char *const *no_matches,
+                            const char *const *neg_matches)
 {
 	GSList *specs, *specs_randperm = NULL, *specs_resplit, *specs_i, *specs_j;
 	guint i;
@@ -1188,102 +1189,100 @@ _do_test_match_spec_device (const char *spec_str, const char **matches, const ch
 static void
 test_match_spec_device (void)
 {
-#define S(...) ((const char *[]) { __VA_ARGS__, NULL } )
 	_do_test_match_spec_device ("em1",
-	                            S ("em1"),
+	                            NM_MAKE_STRV ("em1"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("em1,em2",
-	                            S ("em1", "em2"),
+	                            NM_MAKE_STRV ("em1", "em2"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("em1,em2,interface-name:em2",
-	                            S ("em1", "em2"),
+	                            NM_MAKE_STRV ("em1", "em2"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:em1",
-	                            S ("em1"),
+	                            NM_MAKE_STRV ("em1"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:em*",
-	                            S ("em", "em*", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"),
+	                            NM_MAKE_STRV ("em", "em*", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:em\\*",
-	                            S ("em\\", "em\\*", "em\\1", "em\\11", "em\\2"),
+	                            NM_MAKE_STRV ("em\\", "em\\*", "em\\1", "em\\11", "em\\2"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:~em\\*",
-	                            S ("em\\", "em\\*", "em\\1", "em\\11", "em\\2"),
+	                            NM_MAKE_STRV ("em\\", "em\\*", "em\\1", "em\\11", "em\\2"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("except:*",
 	                            NULL,
-	                            S (NULL),
-	                            S ("a"));
+	                            NM_MAKE_STRV (NULL),
+	                            NM_MAKE_STRV ("a"));
 	_do_test_match_spec_device ("interface-name:=em*",
-	                            S ("em*"),
+	                            NM_MAKE_STRV ("em*"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:em*,except:interface-name:em1*",
-	                            S ("em", "em*", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em2", "em3"),
+	                            NM_MAKE_STRV ("em", "em*", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em2", "em3"),
 	                            NULL,
-	                            S ("em1", "em11"));
+	                            NM_MAKE_STRV ("em1", "em11"));
 	_do_test_match_spec_device ("interface-name:em*,except:interface-name:=em*",
-	                            S ("em", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"),
+	                            NM_MAKE_STRV ("em", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"),
 	                            NULL,
-	                            S ("em*"));
+	                            NM_MAKE_STRV ("em*"));
 	_do_test_match_spec_device ("except:interface-name:em*",
-	                            S ("", "eth", "eth1", "e1"),
-	                            S (NULL),
-	                            S ("em", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"));
+	                            NM_MAKE_STRV ("", "eth", "eth1", "e1"),
+	                            NM_MAKE_STRV (NULL),
+	                            NM_MAKE_STRV ("em", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"));
 	_do_test_match_spec_device ("aa,bb,cc\\,dd,e,,",
-	                            S ("aa", "bb", "cc,dd", "e"),
+	                            NM_MAKE_STRV ("aa", "bb", "cc,dd", "e"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("aa;bb;cc\\;dd;e,;",
-	                            S ("aa", "bb", "cc;dd", "e"),
+	                            NM_MAKE_STRV ("aa", "bb", "cc;dd", "e"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("interface-name:em\\;1,em\\,2,\\,,\\\\,,em\\\\x",
-	                            S ("em;1", "em,2", ",", "\\", "em\\x"),
+	                            NM_MAKE_STRV ("em;1", "em,2", ",", "\\", "em\\x"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device ("\\s\\s,\\sinterface-name:a,\\s,",
-	                            S ("  ", " ", " interface-name:a"),
+	                            NM_MAKE_STRV ("  ", " ", " interface-name:a"),
 	                            NULL,
 	                            NULL);
 	_do_test_match_spec_device (" aa ;  bb   ; cc\\;dd  ;e , ; \t\\t  , ",
-	                            S ("aa", "bb", "cc;dd", "e", "\t"),
+	                            NM_MAKE_STRV ("aa", "bb", "cc;dd", "e", "\t"),
 	                            NULL,
 	                            NULL);
 
 	_do_test_match_spec_device ("s390-subchannels:0.0.1000\\,0.0.1001",
-	                            S (MATCH_S390"0.0.1000", MATCH_S390"0.0.1000,deadbeef", MATCH_S390"0.0.1000,0.0.1001", MATCH_S390"0.0.1000,0.0.1002"),
-	                            S (MATCH_S390"0.0.1001"),
+	                            NM_MAKE_STRV (MATCH_S390"0.0.1000", MATCH_S390"0.0.1000,deadbeef", MATCH_S390"0.0.1000,0.0.1001", MATCH_S390"0.0.1000,0.0.1002"),
+	                            NM_MAKE_STRV (MATCH_S390"0.0.1001"),
 	                            NULL);
 	_do_test_match_spec_device ("*,except:s390-subchannels:0.0.1000\\,0.0.1001",
 	                            NULL,
-	                            S (NULL),
-	                            S (MATCH_S390"0.0.1000", MATCH_S390"0.0.1000,deadbeef", MATCH_S390"0.0.1000,0.0.1001", MATCH_S390"0.0.1000,0.0.1002"));
+	                            NM_MAKE_STRV (NULL),
+	                            NM_MAKE_STRV (MATCH_S390"0.0.1000", MATCH_S390"0.0.1000,deadbeef", MATCH_S390"0.0.1000,0.0.1001", MATCH_S390"0.0.1000,0.0.1002"));
 
 	_do_test_match_spec_device ("driver:DRV",
-	                            S (MATCH_DRIVER"DRV", MATCH_DRIVER"DRV|1.6"),
-	                            S (MATCH_DRIVER"DR", MATCH_DRIVER"DR*"),
+	                            NM_MAKE_STRV (MATCH_DRIVER"DRV", MATCH_DRIVER"DRV|1.6"),
+	                            NM_MAKE_STRV (MATCH_DRIVER"DR", MATCH_DRIVER"DR*"),
 	                            NULL);
 	_do_test_match_spec_device ("driver:DRV//",
-	                            S (MATCH_DRIVER"DRV/"),
-	                            S (MATCH_DRIVER"DRV/|1.6", MATCH_DRIVER"DR", MATCH_DRIVER"DR*"),
+	                            NM_MAKE_STRV (MATCH_DRIVER"DRV/"),
+	                            NM_MAKE_STRV (MATCH_DRIVER"DRV/|1.6", MATCH_DRIVER"DR", MATCH_DRIVER"DR*"),
 	                            NULL);
 	_do_test_match_spec_device ("driver:DRV//*",
-	                            S (MATCH_DRIVER"DRV/", MATCH_DRIVER"DRV/|1.6"),
-	                            S (MATCH_DRIVER"DR", MATCH_DRIVER"DR*"),
+	                            NM_MAKE_STRV (MATCH_DRIVER"DRV/", MATCH_DRIVER"DRV/|1.6"),
+	                            NM_MAKE_STRV (MATCH_DRIVER"DR", MATCH_DRIVER"DR*"),
 	                            NULL);
 	_do_test_match_spec_device ("driver:DRV//1.5*",
-	                            S (MATCH_DRIVER"DRV/|1.5", MATCH_DRIVER"DRV/|1.5.2"),
-	                            S (MATCH_DRIVER"DRV/", MATCH_DRIVER"DRV/|1.6", MATCH_DRIVER"DR", MATCH_DRIVER"DR*"),
+	                            NM_MAKE_STRV (MATCH_DRIVER"DRV/|1.5", MATCH_DRIVER"DRV/|1.5.2"),
+	                            NM_MAKE_STRV (MATCH_DRIVER"DRV/", MATCH_DRIVER"DRV/|1.6", MATCH_DRIVER"DR", MATCH_DRIVER"DR*"),
 	                            NULL);
-#undef S
 }
 
 /*****************************************************************************/
@@ -1307,7 +1306,7 @@ _do_test_match_spec_config (const char *file, int line, const char *spec_str, gu
 	match_result = nm_match_spec_config (specs, version, NULL);
 
 	if (expected != match_result)
-		g_error ("%s:%d: faild comparing \"%s\" with %u.%u.%u. Expected %d, but got %d", file, line, spec_str, v_maj, v_min, v_mic, (int) expected, (int) match_result);
+		g_error ("%s:%d: failed comparing \"%s\" with %u.%u.%u. Expected %d, but got %d", file, line, spec_str, v_maj, v_min, v_mic, (int) expected, (int) match_result);
 
 	if (   g_slist_length (specs) == 1
 	    && !g_str_has_prefix (specs->data, "except:")) {
@@ -1439,6 +1438,82 @@ test_nm_utils_strbuf_append (void)
 	char buf[NM_STRLEN (BUF_ORIG) + 1];
 	char str[NM_STRLEN (BUF_ORIG) + 1];
 
+#define _strbuf_append(buf, len, format, ...) \
+	G_STMT_START { \
+		char **_buf = (buf); \
+		gsize *_len = (len); \
+		const char *_str_iter; \
+		gs_free char *_str = NULL; \
+		\
+		switch (nmtst_get_rand_int () % 4) { \
+		case 0: \
+			nm_utils_strbuf_append (_buf, _len, (format), __VA_ARGS__); \
+			break; \
+		case 1: \
+			_str = g_strdup_printf ((format), __VA_ARGS__); \
+			nm_utils_strbuf_append_str (_buf, _len, _str); \
+			break; \
+		case 2: \
+			_str = g_strdup_printf ((format), __VA_ARGS__); \
+			nm_utils_strbuf_append_bin (_buf, _len, _str, strlen (_str)); \
+			break; \
+		case 3: \
+			_str = g_strdup_printf ((format), __VA_ARGS__); \
+			if (!_str[0]) \
+				nm_utils_strbuf_append_str (_buf, _len, _str); \
+			for (_str_iter = _str; _str_iter[0]; _str_iter++) \
+				nm_utils_strbuf_append_c (_buf, _len, _str_iter[0]); \
+			break; \
+		} \
+	} G_STMT_END
+
+#define _strbuf_append_str(buf, len, str) \
+	G_STMT_START { \
+		char **_buf = (buf); \
+		gsize *_len = (len); \
+		const char *_str = (str); \
+		\
+		switch (nmtst_get_rand_int () % 4) { \
+		case 0: \
+			nm_utils_strbuf_append (_buf, _len, "%s", _str ?: ""); \
+			break; \
+		case 1: \
+			nm_utils_strbuf_append_str (_buf, _len, _str); \
+			break; \
+		case 2: \
+			nm_utils_strbuf_append_bin (_buf, _len, _str, _str ? strlen (_str) : 0); \
+			break; \
+		case 3: \
+			if (!_str || !_str[0]) \
+				nm_utils_strbuf_append_str (_buf, _len, _str); \
+			for (; _str && _str[0]; _str++) \
+				nm_utils_strbuf_append_c (_buf, _len, _str[0]); \
+			break; \
+		} \
+	} G_STMT_END
+
+#define _strbuf_append_c(buf, len, ch) \
+	G_STMT_START { \
+		char **_buf = (buf); \
+		gsize *_len = (len); \
+		char _ch = (ch); \
+		\
+		switch (nmtst_get_rand_int () % 4) { \
+		case 0: \
+			nm_utils_strbuf_append (_buf, _len, "%c", _ch); \
+			break; \
+		case 1: \
+			nm_utils_strbuf_append_str (_buf, _len, ((char[2]) { _ch, 0 })); \
+			break; \
+		case 2: \
+			nm_utils_strbuf_append_bin (_buf, _len, &_ch, 1); \
+			break; \
+		case 3: \
+			nm_utils_strbuf_append_c (_buf, _len, _ch); \
+			break; \
+		} \
+	} G_STMT_END
+
 	for (buf_len = 0; buf_len < 10; buf_len++) {
 		for (rep = 0; rep < 50; rep++) {
 			const int s_len = nmtst_get_rand_int () % (sizeof (str) - 5);
@@ -1462,21 +1537,21 @@ test_nm_utils_strbuf_append (void)
 			switch (test_mode) {
 			case 0:
 				if (s_len == 1) {
-					nm_utils_strbuf_append_c (&t_buf, &t_len, str[0]);
+					_strbuf_append_c (&t_buf, &t_len, str[0]);
 					break;
 				}
 				/* fall through */
 			case 1:
-				nm_utils_strbuf_append_str (&t_buf, &t_len, str);
+				_strbuf_append_str (&t_buf, &t_len, str);
 				break;
 			case 2:
 				if (s_len == 1) {
-					nm_utils_strbuf_append (&t_buf, &t_len, "%c", str[0]);
+					_strbuf_append (&t_buf, &t_len, "%c", str[0]);
 					break;
 				}
 				/* fall through */
 			case 3:
-				nm_utils_strbuf_append (&t_buf, &t_len, "%s", str);
+				_strbuf_append (&t_buf, &t_len, "%s", str);
 				break;
 			case 4:
 				g_snprintf (t_buf, t_len, "%s", str);
@@ -1589,7 +1664,7 @@ test_duplicate_decl_specifier (void)
 	/* 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] = { };
-	const const int v2 = 3;
+	const int v2 = 3;
 
 	/* Test that we don't get a compiler warning about duplicate const specifier.
 	 * C99 allows that and it can easily happen in macros. */
@@ -1858,6 +1933,25 @@ test_nm_utils_exp10 (void)
 
 /*****************************************************************************/
 
+static void
+test_utils_file_is_in_path (void)
+{
+	g_assert (!nm_utils_file_is_in_path ("/", "/"));
+	g_assert (!nm_utils_file_is_in_path ("//", "/"));
+	g_assert (!nm_utils_file_is_in_path ("/a/", "/"));
+	g_assert ( nm_utils_file_is_in_path ("/a", "/"));
+	g_assert ( nm_utils_file_is_in_path ("///a", "/"));
+	g_assert ( nm_utils_file_is_in_path ("//b/a", "/b//"));
+	g_assert ( nm_utils_file_is_in_path ("//b///a", "/b//"));
+	g_assert (!nm_utils_file_is_in_path ("//b///a/", "/b//"));
+	g_assert (!nm_utils_file_is_in_path ("//b///a/", "/b/a/"));
+	g_assert (!nm_utils_file_is_in_path ("//b///a", "/b/a/"));
+	g_assert ( nm_utils_file_is_in_path ("//b///a/.", "/b/a/"));
+	g_assert ( nm_utils_file_is_in_path ("//b///a/..", "/b/a/"));
+}
+
+/*****************************************************************************/
+
 #define _TEST_RC(searches, nameservers, options, expected) \
 	G_STMT_START { \
 		const char *const*const _searches = (searches); \
@@ -1918,11 +2012,11 @@ test_machine_id_read (void)
 	nmtst_logging_reenable (logstate);
 
 	g_assert (machine_id);
-	g_assert (_nm_utils_bin2hexstr_full (machine_id,
-	                                     sizeof (NMUuid),
-	                                     '\0',
-	                                     FALSE,
-	                                     machine_id_str) == machine_id_str);
+	g_assert (nm_utils_bin2hexstr_full (machine_id,
+	                                    sizeof (NMUuid),
+	                                    '\0',
+	                                    FALSE,
+	                                    machine_id_str) == machine_id_str);
 	g_assert (strlen (machine_id_str) == 32);
 	g_assert_cmpstr (machine_id_str, ==, nm_utils_machine_id_str ());
 
@@ -1959,14 +2053,14 @@ test_nm_utils_dhcp_client_id_systemd_node_specific (gconstpointer test_data)
 		guint64 duid_id;
 	} d_array[] = {
 		[0] = {
-			.machine_id = { 0xcb, 0xc2, 0x2e, 0x47, 0x41, 0x8e, 0x40, 0x2a, 0xa7, 0xb3, 0x0d, 0xea, 0x92, 0x83, 0x94, 0xef },
+			.machine_id.uuid = { 0xcb, 0xc2, 0x2e, 0x47, 0x41, 0x8e, 0x40, 0x2a, 0xa7, 0xb3, 0x0d, 0xea, 0x92, 0x83, 0x94, 0xef },
 			.ifname = "lo",
 			.ifname_hash_1 = 0x7297085c2b12c911llu,
 			.iaid_ifname = htobe32 (0x5985c14du),
 			.duid_id = htobe64 (0x3d769bb2c14d29e1u),
 		},
 		[1] = {
-			.machine_id = { 0x11, 0x4e, 0xb4, 0xda, 0xd3, 0x22, 0x4a, 0xff, 0x9f, 0xc3, 0x30, 0x83, 0x38, 0xa0, 0xeb, 0xb7 },
+			.machine_id.uuid = { 0x11, 0x4e, 0xb4, 0xda, 0xd3, 0x22, 0x4a, 0xff, 0x9f, 0xc3, 0x30, 0x83, 0x38, 0xa0, 0xeb, 0xb7 },
 			.ifname = "eth0",
 			.ifname_hash_1 = 0x9e1cb083b54cd7b6llu,
 			.iaid_ifname = htobe32 (0x2b506735u),
@@ -2016,6 +2110,10 @@ test_nm_utils_dhcp_client_id_systemd_node_specific (gconstpointer test_data)
 		g_assert_cmpmem (&cid[5], 2, &duid_type_en, sizeof (duid_type_en));
 		g_assert_cmpmem (&cid[7], 4, &systemd_pen, sizeof (systemd_pen));
 		g_assert_cmpmem (&cid[11], 8, &d->duid_id, sizeof (d->duid_id));
+
+		g_assert_cmpint (iaid, ==, htonl (nm_utils_create_dhcp_iaid (legacy_unstable_byteorder,
+		                                                             (const guint8 *) d->ifname,
+		                                                             strlen (d->ifname))));
 	}
 }
 
@@ -2123,6 +2221,8 @@ main (int argc, char **argv)
 
 	g_test_add_func ("/general/machine-id/read", test_machine_id_read);
 
+	g_test_add_func ("/general/test_utils_file_is_in_path", test_utils_file_is_in_path);
+
 	g_test_add_func ("/general/test_dns_create_resolv_conf", test_dns_create_resolv_conf);
 
 	g_test_add_data_func ("/general/nm_utils_dhcp_client_id_systemd_node_specific/0", GINT_TO_POINTER (0), test_nm_utils_dhcp_client_id_systemd_node_specific);
diff --git a/src/tests/test-ip4-config.c b/src/tests/test-ip4-config.c
index 9fea6af5..f98c84c3 100644
--- a/src/tests/test-ip4-config.c
+++ b/src/tests/test-ip4-config.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 
 #include "nm-ip4-config.h"
diff --git a/src/tests/test-ip6-config.c b/src/tests/test-ip6-config.c
index 5b255068..8f5f41d7 100644
--- a/src/tests/test-ip6-config.c
+++ b/src/tests/test-ip6-config.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <linux/if_addr.h>
 
@@ -252,7 +251,7 @@ test_nm_ip6_config_addresses_sort_check (NMIP6Config *config, NMSettingIP6Config
 	copy2 = nm_ip6_config_clone (config);
 	g_assert (copy2);
 
-	/* initialize the array of indeces, and keep shuffling them for every @repeat iteration. */
+	/* initialize the array of indices, and keep shuffling them for every @repeat iteration. */
 	for (i = 0; i < addr_count; i++)
 		idx[i] = i;
 
diff --git a/src/tests/test-systemd.c b/src/tests/test-systemd.c
index 4660bd0d..91edcab2 100644
--- a/src/tests/test-systemd.c
+++ b/src/tests/test-systemd.c
@@ -20,7 +20,7 @@
 #include "nm-default.h"
 
 #include "systemd/nm-sd.h"
-#include "systemd/nm-sd-utils.h"
+#include "systemd/nm-sd-utils-shared.h"
 
 #include "nm-test-utils-core.h"
 
@@ -48,10 +48,19 @@ nm_utils_get_monotonic_timestamp_s (void)
 
 NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL];
 
+gboolean
+_nm_log_enabled_impl (gboolean mt_require_locking,
+                      NMLogLevel level,
+                      NMLogDomain domain)
+{
+	return FALSE;
+}
+
 void
 _nm_log_impl (const char *file,
               guint line,
               const char *func,
+              gboolean mt_require_locking,
               NMLogLevel level,
               NMLogDomain domain,
               int error,
@@ -71,6 +80,12 @@ nm_logging_setup (const char  *level,
 	return TRUE;
 }
 
+const char *
+nm_strerror_native (int errsv)
+{
+	return g_strerror (errsv);
+}
+
 /*****************************************************************************/
 
 static void
@@ -201,16 +216,16 @@ test_path_equal (void)
 	} G_STMT_END
 
 	_path_equal_check ("",                  "",                NULL);
-	_path_equal_check (".",                 ".",               "");
+	_path_equal_check (".",                 ".",               NULL);
 	_path_equal_check ("..",                "..",              NULL);
 	_path_equal_check ("/..",               "/..",             NULL);
 	_path_equal_check ("//..",              "/..",             NULL);
 	_path_equal_check ("/.",                "/.",              "/");
-	_path_equal_check ("./",                ".",               "");
-	_path_equal_check ("./.",               "./.",             "");
-	_path_equal_check (".///.",             "./.",             "");
-	_path_equal_check (".///./",            "./.",             "");
-	_path_equal_check (".////",             ".",               "");
+	_path_equal_check ("./",                ".",               ".");
+	_path_equal_check ("./.",               "./.",             ".");
+	_path_equal_check (".///.",             "./.",             ".");
+	_path_equal_check (".///./",            "./.",             ".");
+	_path_equal_check (".////",             ".",               ".");
 	_path_equal_check ("//..//foo/",        "/../foo",         NULL);
 	_path_equal_check ("///foo//./bar/.",   "/foo/./bar/.",    "/foo/bar");
 	_path_equal_check (".//./foo//./bar/.", "././foo/./bar/.", "foo/bar");
@@ -218,6 +233,101 @@ test_path_equal (void)
 
 /*****************************************************************************/
 
+static void
+_test_unbase64char (char ch, gboolean maybe_invalid)
+{
+	int r;
+
+	r = nm_sd_utils_unbase64char (ch, FALSE);
+
+	if (ch == '=') {
+		g_assert (!maybe_invalid);
+		g_assert_cmpint (r, <, 0);
+		g_assert_cmpint (nm_sd_utils_unbase64char (ch, TRUE), ==, G_MAXINT);
+	} else {
+		g_assert_cmpint (r, ==, nm_sd_utils_unbase64char (ch, TRUE));
+		if (r >= 0)
+			g_assert_cmpint (r, <=, 255);
+		if (!maybe_invalid)
+			g_assert_cmpint (r, >=, 0);
+	}
+}
+
+static void
+_test_unbase64mem_mem (const char *base64, const guint8 *expected_arr, gsize expected_len)
+{
+	gs_free char *expected_base64 = NULL;
+	int r;
+	gs_free guint8 *exp2_arr = NULL;
+	gs_free guint8 *exp3_arr = NULL;
+	gsize exp2_len;
+	gsize exp3_len;
+	gsize i;
+
+	expected_base64 = g_base64_encode (expected_arr, expected_len);
+
+	for (i = 0; expected_base64[i]; i++)
+		_test_unbase64char (expected_base64[i], FALSE);
+
+	r = nm_sd_utils_unbase64mem (expected_base64, strlen (expected_base64), &exp2_arr, &exp2_len);
+	g_assert_cmpint (r, ==, 0);
+	g_assert_cmpmem (expected_arr, expected_len, exp2_arr, exp2_len);
+
+	if (!nm_streq (base64, expected_base64)) {
+		r = nm_sd_utils_unbase64mem (base64, strlen (base64), &exp3_arr, &exp3_len);
+		g_assert_cmpint (r, ==, 0);
+		g_assert_cmpmem (expected_arr, expected_len, exp3_arr, exp3_len);
+	}
+}
+
+#define _test_unbase64mem(base64, expected_str) _test_unbase64mem_mem (base64, (const guint8 *) ""expected_str"", NM_STRLEN (expected_str))
+
+static void
+_test_unbase64mem_inval (const char *base64)
+{
+	gs_free guint8 *exp_arr = NULL;
+	gsize exp_len = 0;
+	int r;
+
+	r = nm_sd_utils_unbase64mem (base64, strlen (base64), &exp_arr, &exp_len);
+	g_assert_cmpint (r, <, 0);
+	g_assert (!exp_arr);
+	g_assert (exp_len == 0);
+}
+
+static void
+test_nm_sd_utils_unbase64mem (void)
+{
+	gs_free char *rnd_base64 = NULL;
+	guint8 rnd_buf[30];
+	guint i, rnd_len;
+
+	_test_unbase64mem ("", "");
+	_test_unbase64mem ("  ", "");
+	_test_unbase64mem (" Y Q == ", "a");
+	_test_unbase64mem (" Y   WJjZGV mZ 2g = ", "abcdefgh");
+	_test_unbase64mem_inval (" Y   %WJjZGV mZ 2g = ");
+	_test_unbase64mem_inval (" Y   %WJjZGV mZ 2g = a");
+	_test_unbase64mem ("YQ==", "a");
+	_test_unbase64mem_inval ("YQ==a");
+
+	rnd_len = nmtst_get_rand_int () % sizeof (rnd_buf);
+	for (i = 0; i < rnd_len; i++)
+		rnd_buf[i] = nmtst_get_rand_int () % 256;
+	rnd_base64 = g_base64_encode (rnd_buf, rnd_len);
+	_test_unbase64mem_mem (rnd_base64, rnd_buf, rnd_len);
+
+	_test_unbase64char ('=', FALSE);
+	for (i = 0; i < 10; i++) {
+		char ch = nmtst_get_rand_int () % 256;
+
+		if (ch != '=')
+			_test_unbase64char (ch, TRUE);
+	}
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int
@@ -229,6 +339,7 @@ main (int argc, char **argv)
 	g_test_add_func ("/systemd/lldp/create", test_lldp_create);
 	g_test_add_func ("/systemd/sd-event", test_sd_event);
 	g_test_add_func ("/systemd/test_path_equal", test_path_equal);
+	g_test_add_func ("/systemd/test_nm_sd_utils_unbase64mem", test_nm_sd_utils_unbase64mem);
 
 	return g_test_run ();
 }
diff --git a/src/tests/test-utils.c b/src/tests/test-utils.c
index 16eb3aec..c326f790 100644
--- a/src/tests/test-utils.c
+++ b/src/tests/test-utils.c
@@ -20,8 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-#include <errno.h>
 #include <arpa/inet.h>
 
 #include "nm-test-utils-core.h"
@@ -64,10 +62,10 @@ _do_test_hw_addr (NMUtilsStableType stable_type,
                   const char *ifname,
                   const char *current_mac_address,
                   const char *generate_mac_address_mask,
-                  const char **expected)
+                  const char *const *expected)
 {
 	gs_free char *generated = NULL;
-	const char **e;
+	const char *const *e;
 	gboolean found = FALSE;
 
 	for (e = expected; *e; e++) {
@@ -95,7 +93,13 @@ _do_test_hw_addr (NMUtilsStableType stable_type,
 	g_assert (found);
 }
 #define do_test_hw_addr(stable_type, stable_id, secret_key, ifname, current_mac_address, generate_mac_address_mask, ...) \
-	_do_test_hw_addr ((stable_type), (stable_id), (const guint8 *) ""secret_key"", NM_STRLEN (secret_key), (ifname), ""current_mac_address"", generate_mac_address_mask, (const char *[]) { __VA_ARGS__, NULL })
+	_do_test_hw_addr ((stable_type), \
+	                  (stable_id), \
+	                  (const guint8 *) ""secret_key"", \
+	                  NM_STRLEN (secret_key), (ifname), \
+	                  ""current_mac_address"", \
+	                  generate_mac_address_mask, \
+	                  NM_MAKE_STRV (__VA_ARGS__))
 
 static void
 test_hw_addr_gen_stable_eth (void)
diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c
index 3b3a97f4..5acf491a 100644
--- a/src/vpn/nm-vpn-connection.c
+++ b/src/vpn/nm-vpn-connection.c
@@ -23,11 +23,9 @@
 
 #include "nm-vpn-connection.h"
 
-#include <string.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
-#include <errno.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <syslog.h>
@@ -725,7 +723,7 @@ add_ip4_vpn_gateway_route (NMIP4Config *config,
 	                              AF_INET,
 	                              &vpn_gw,
 	                              ifindex,
-	                              (NMPObject **) &route_resolved) == NM_PLATFORM_ERROR_SUCCESS) {
+	                              (NMPObject **) &route_resolved) >= 0) {
 		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (route_resolved);
 
 		if (r->ifindex == ifindex) {
@@ -807,7 +805,7 @@ add_ip6_vpn_gateway_route (NMIP6Config *config,
 	                              AF_INET6,
 	                              vpn_gw,
 	                              ifindex,
-	                              (NMPObject **) &route_resolved) == NM_PLATFORM_ERROR_SUCCESS) {
+	                              (NMPObject **) &route_resolved) >= 0) {
 		const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (route_resolved);
 
 		if (r->ifindex == ifindex) {
@@ -867,6 +865,7 @@ nm_vpn_connection_new (NMSettingsConnection *settings_connection,
                        NMDevice *parent_device,
                        const char *specific_object,
                        NMActivationReason activation_reason,
+                       NMActivationStateFlags initial_state_flags,
                        NMAuthSubject *subject)
 {
 	g_return_val_if_fail (!settings_connection || NM_IS_SETTINGS_CONNECTION (settings_connection), NULL);
@@ -880,6 +879,7 @@ nm_vpn_connection_new (NMSettingsConnection *settings_connection,
 	                                         NM_ACTIVE_CONNECTION_INT_SUBJECT, subject,
 	                                         NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON, activation_reason,
 	                                         NM_ACTIVE_CONNECTION_VPN, TRUE,
+	                                         NM_ACTIVE_CONNECTION_STATE_FLAGS, (guint) initial_state_flags,
 	                                         NULL);
 }
 
@@ -898,14 +898,15 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_vpn_plugin_failure_to_string, NMVpnPluginFai
 	NM_UTILS_LOOKUP_STR_ITEM (NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED, "connect-failed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_VPN_PLUGIN_FAILURE_BAD_IP_CONFIG,  "bad-ip-config"),
 );
-#define vpn_plugin_failure_to_string(failure) NM_UTILS_LOOKUP_STR (_vpn_plugin_failure_to_string, failure)
+
+#define vpn_plugin_failure_to_string_a(failure) NM_UTILS_LOOKUP_STR_A (_vpn_plugin_failure_to_string, failure)
 
 static void
 plugin_failed (NMVpnConnection *self, guint reason)
 {
 	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
-	_LOGW ("VPN plugin: failed: %s (%d)", vpn_plugin_failure_to_string (reason), reason);
+	_LOGW ("VPN plugin: failed: %s (%d)", vpn_plugin_failure_to_string_a (reason), reason);
 
 	switch (reason) {
 	case NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED:
@@ -930,7 +931,8 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_vpn_service_state_to_string, NMVpnServiceSta
 	NM_UTILS_LOOKUP_STR_ITEM (NM_VPN_SERVICE_STATE_STOPPING, "stopping"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_VPN_SERVICE_STATE_STOPPED,  "stopped"),
 );
-#define vpn_service_state_to_string(state) NM_UTILS_LOOKUP_STR (_vpn_service_state_to_string, state)
+
+#define vpn_service_state_to_string_a(state) NM_UTILS_LOOKUP_STR_A (_vpn_service_state_to_string, state)
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_vpn_state_to_string, VpnState,
 	NM_UTILS_LOOKUP_DEFAULT (NULL),
@@ -946,7 +948,8 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_vpn_state_to_string, VpnState,
 	NM_UTILS_LOOKUP_STR_ITEM (STATE_DISCONNECTED,  "disconnected"),
 	NM_UTILS_LOOKUP_STR_ITEM (STATE_FAILED,        "failed"),
 );
-#define vpn_state_to_string(state) NM_UTILS_LOOKUP_STR (_vpn_state_to_string, state)
+
+#define vpn_state_to_string_a(state) NM_UTILS_LOOKUP_STR_A (_vpn_state_to_string, state)
 
 static void
 plugin_state_changed (NMVpnConnection *self, NMVpnServiceState new_service_state)
@@ -955,7 +958,7 @@ plugin_state_changed (NMVpnConnection *self, NMVpnServiceState new_service_state
 	NMVpnServiceState old_service_state = priv->service_state;
 
 	_LOGI ("VPN plugin: state changed: %s (%d)",
-	       vpn_service_state_to_string (new_service_state), new_service_state);
+	       vpn_service_state_to_string_a (new_service_state), new_service_state);
 	priv->service_state = new_service_state;
 
 	if (new_service_state == NM_VPN_SERVICE_STATE_STOPPED) {
@@ -995,15 +998,16 @@ print_vpn_config (NMVpnConnection *self)
 	const NMPlatformIP6Address *address6;
 	char *dns_domain = NULL;
 	guint32 num, i;
-	char buf[NM_UTILS_INET_ADDRSTRLEN];
+	char b1[NM_UTILS_INET_ADDRSTRLEN];
+	char b2[NM_UTILS_INET_ADDRSTRLEN];
 	NMDedupMultiIter ipconf_iter;
 
 	if (priv->ip4_external_gw) {
 		_LOGI ("Data: VPN Gateway: %s",
-		       nm_utils_inet4_ntop (priv->ip4_external_gw, NULL));
+		       nm_utils_inet4_ntop (priv->ip4_external_gw, b1));
 	} else if (priv->ip6_external_gw) {
 		_LOGI ("Data: VPN Gateway: %s",
-		       nm_utils_inet6_ntop (priv->ip6_external_gw, NULL));
+		       nm_utils_inet6_ntop (priv->ip6_external_gw, b1));
 	}
 
 	_LOGI ("Data: Tunnel Device: %s%s%s", NM_PRINT_FMT_QUOTE_STRING (priv->ip_iface));
@@ -1017,22 +1021,22 @@ print_vpn_config (NMVpnConnection *self)
 		nm_assert (address4);
 
 		if (priv->ip4_internal_gw)
-			_LOGI ("Data:   Internal Gateway: %s", nm_utils_inet4_ntop (priv->ip4_internal_gw, NULL));
-		_LOGI ("Data:   Internal Address: %s", address4 ? nm_utils_inet4_ntop (address4->address, NULL) : "??");
+			_LOGI ("Data:   Internal Gateway: %s", nm_utils_inet4_ntop (priv->ip4_internal_gw, b1));
+		_LOGI ("Data:   Internal Address: %s", address4 ? nm_utils_inet4_ntop (address4->address, b1) : "??");
 		_LOGI ("Data:   Internal Prefix: %d", address4 ? (int) address4->plen : -1);
-		_LOGI ("Data:   Internal Point-to-Point Address: %s", nm_utils_inet4_ntop (address4->peer_address, NULL));
+		_LOGI ("Data:   Internal Point-to-Point Address: %s", nm_utils_inet4_ntop (address4->peer_address, b1));
 
 		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, priv->ip4_config, &route) {
 			_LOGI ("Data:   Static Route: %s/%d   Next Hop: %s",
-			       nm_utils_inet4_ntop (route->network, NULL),
+			       nm_utils_inet4_ntop (route->network, b1),
 			       route->plen,
-			       nm_utils_inet4_ntop (route->gateway, buf));
+			       nm_utils_inet4_ntop (route->gateway, b2));
 		}
 
 		num = nm_ip4_config_get_num_nameservers (priv->ip4_config);
 		for (i = 0; i < num; i++) {
 			_LOGI ("Data:   Internal DNS: %s",
-			       nm_utils_inet4_ntop (nm_ip4_config_get_nameserver (priv->ip4_config, i), NULL));
+			       nm_utils_inet4_ntop (nm_ip4_config_get_nameserver (priv->ip4_config, i), b1));
 		}
 
 		if (nm_ip4_config_get_num_domains (priv->ip4_config) > 0)
@@ -1051,22 +1055,22 @@ print_vpn_config (NMVpnConnection *self)
 		nm_assert (address6);
 
 		if (priv->ip6_internal_gw)
-			_LOGI ("Data:   Internal Gateway: %s", nm_utils_inet6_ntop (priv->ip6_internal_gw, NULL));
-		_LOGI ("Data:   Internal Address: %s", nm_utils_inet6_ntop (&address6->address, NULL));
+			_LOGI ("Data:   Internal Gateway: %s", nm_utils_inet6_ntop (priv->ip6_internal_gw, b1));
+		_LOGI ("Data:   Internal Address: %s", nm_utils_inet6_ntop (&address6->address, b1));
 		_LOGI ("Data:   Internal Prefix: %d", address6->plen);
-		_LOGI ("Data:   Internal Point-to-Point Address: %s", nm_utils_inet6_ntop (&address6->peer_address, NULL));
+		_LOGI ("Data:   Internal Point-to-Point Address: %s", nm_utils_inet6_ntop (&address6->peer_address, b1));
 
 		nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, priv->ip6_config, &route) {
 			_LOGI ("Data:   Static Route: %s/%d   Next Hop: %s",
-			       nm_utils_inet6_ntop (&route->network, NULL),
+			       nm_utils_inet6_ntop (&route->network, b1),
 			       route->plen,
-			       nm_utils_inet6_ntop (&route->gateway, buf));
+			       nm_utils_inet6_ntop (&route->gateway, b2));
 		}
 
 		num = nm_ip6_config_get_num_nameservers (priv->ip6_config);
 		for (i = 0; i < num; i++) {
 			_LOGI ("Data:   Internal DNS: %s",
-			       nm_utils_inet6_ntop (nm_ip6_config_get_nameserver (priv->ip6_config, i), NULL));
+			       nm_utils_inet6_ntop (nm_ip6_config_get_nameserver (priv->ip6_config, i), b1));
 		}
 
 		if (nm_ip6_config_get_num_domains (priv->ip6_config) > 0)
@@ -1452,11 +1456,7 @@ get_route_table (NMVpnConnection *self,
 
 	connection = _get_applied_connection (self);
 	if (connection) {
-		if (addr_family == AF_INET)
-			s_ip = nm_connection_get_setting_ip4_config (connection);
-		else
-			s_ip = nm_connection_get_setting_ip6_config (connection);
-
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 		if (s_ip)
 			route_table = nm_setting_ip_config_get_route_table  (s_ip);
 	}
@@ -1894,7 +1894,7 @@ connect_success (NMVpnConnection *self)
 	timeout = nm_setting_vpn_get_timeout (s_vpn);
 	if (timeout == 0) {
 		timeout = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-		                                                       "vpn.timeout",
+		                                                       NM_CON_DEFAULT ("vpn.timeout"),
 		                                                       NULL,
 		                                                       1, G_MAXUINT32, 60);
 	}
@@ -2703,12 +2703,12 @@ plugin_interactive_secrets_required (NMVpnConnection *self,
 	if (!NM_IN_SET (priv->vpn_state, STATE_CONNECT,
 	                                 STATE_NEED_AUTH)) {
 		_LOGD ("VPN plugin: requested secrets; state %s (%d); ignore request in current state",
-		       vpn_state_to_string (priv->vpn_state), priv->vpn_state);
+		       vpn_state_to_string_a (priv->vpn_state), priv->vpn_state);
 		return;
 	}
 
 	_LOGI ("VPN plugin: requested secrets; state %s (%d)",
-	       vpn_state_to_string (priv->vpn_state), priv->vpn_state);
+	       vpn_state_to_string_a (priv->vpn_state), priv->vpn_state);
 
 	priv->secrets_idx = SECRETS_REQ_INTERACTIVE;
 	_set_vpn_state (self, STATE_NEED_AUTH, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE);
diff --git a/src/vpn/nm-vpn-connection.h b/src/vpn/nm-vpn-connection.h
index e409cc31..e70590b2 100644
--- a/src/vpn/nm-vpn-connection.h
+++ b/src/vpn/nm-vpn-connection.h
@@ -28,8 +28,6 @@
 #include "nm-active-connection.h"
 #include "nm-vpn-plugin-info.h"
 
-#define NM_VPN_ROUTE_METRIC_DEFAULT     50
-
 #define NM_TYPE_VPN_CONNECTION            (nm_vpn_connection_get_type ())
 #define NM_VPN_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_VPN_CONNECTION, NMVpnConnection))
 #define NM_VPN_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_VPN_CONNECTION, NMVpnConnectionClass))
@@ -53,6 +51,7 @@ NMVpnConnection * nm_vpn_connection_new (NMSettingsConnection *settings_connecti
                                          NMDevice *parent_device,
                                          const char *specific_object,
                                          NMActivationReason activation_reason,
+                                         NMActivationStateFlags initial_state_flags,
                                          NMAuthSubject *subject);
 
 void                 nm_vpn_connection_activate        (NMVpnConnection *self,
diff --git a/src/vpn/nm-vpn-manager.c b/src/vpn/nm-vpn-manager.c
index d0639168..0b27b9de 100644
--- a/src/vpn/nm-vpn-manager.c
+++ b/src/vpn/nm-vpn-manager.c
@@ -23,8 +23,6 @@
 
 #include "nm-vpn-manager.h"
 
-#include <string.h>
-
 #include "nm-vpn-plugin-info.h"
 #include "nm-vpn-connection.h"
 #include "nm-setting-vpn.h"